Aspectos avanzados de Android en Kotlin 03.2: Animación con MotionLayout

1. Antes de comenzar

Este codelab es parte del curso Aspectos avanzados de Android en Kotlin. Aprovecharás al máximo este curso si trabajas con los codelabs en secuencia, pero no es obligatorio. Todos los codelabs del curso se indican en la página de destino de codelabs avanzados de Android en Kotlin.

MotionLayout es una biblioteca que te permite agregar movimientos enriquecidos a tu app para Android. Se basa en ConstraintLayout, y te permite animar todo lo que puedas compilar con ConstraintLayout.

Puedes usar MotionLayout para animar la ubicación, el tamaño, la visibilidad, los alfas, el color, la elevación, la rotación y otros atributos de varias vistas al mismo tiempo. Si usas XML declarativo, puedes crear animaciones coordinadas que incluyan varias vistas, que son difíciles de lograr en código.

Las animaciones son una excelente manera de mejorar la experiencia de una app. Puedes usar animaciones para lo siguiente:

  • Muestra los cambios: Animar entre estados le permite al usuario hacer un seguimiento natural de los cambios en tu IU.
  • Llama la atención: Usa animaciones para destacar elementos importantes de la IU.
  • Crea hermosos diseños: el movimiento eficaz en los diseños hace que las aplicaciones se vean pulidas.

Requisitos previos

Este codelab se diseñó para desarrolladores con cierta experiencia en desarrollo de Android. Antes de intentar completar este codelab, debes hacer lo siguiente:

  • Aprende a crear una app con una actividad y un diseño básico, y a ejecutarla en un dispositivo o emulador con Android Studio. Familiarízate con ConstraintLayout. Lee el codelab de diseño de restricciones para obtener más información sobre ConstraintLayout.

Actividades

  • Cómo definir una animación con ConstraintSets y MotionLayout
  • Animaciones basadas en eventos de arrastre
  • Cambia la animación con KeyPosition
  • Cambia los atributos con KeyAttribute
  • Cómo ejecutar animaciones con código
  • Cómo animar encabezados que se pueden contraer con MotionLayout

Requisitos

  • Android Studio 4.0 (el editor MotionLayout solo funciona con esta versión de Android Studio)

2. Comenzar

Para descargar la app de ejemplo, puedes optar por una de las dos opciones siguientes:

… o clona el repositorio de GitHub desde la línea de comandos con el siguiente comando:

$ git clone https://github.com/googlecodelabs/motionlayout.git

3. Cómo crear animaciones con MotionLayout

Primero, compilarás una animación que mueva una vista desde el inicio superior de la pantalla hasta el extremo inferior en respuesta a los clics del usuario.

Para crear una animación a partir del código de partida, necesitarás las siguientes piezas principales:

  • Un MotionLayout,, que es una subclase de ConstraintLayout. Debes especificar todas las vistas que se animarán dentro de la etiqueta MotionLayout.
  • Un MotionScene,, que es un archivo en formato XML que describe una animación para MotionLayout.
  • Un Transition, que forma parte de MotionScene que especifica la duración de la animación, el activador y cómo mover las vistas.
  • Un ConstraintSet que especifica las restricciones de inicio y final de la transición.

Veamos cada uno de ellos a la vez, empezando por el MotionLayout.

Paso 1: Explora el código existente

MotionLayout es una subclase de ConstraintLayout, por lo que admite todas las mismas funciones al mismo tiempo que agrega animación. Para usar MotionLayout, debes agregar una vista de MotionLayout en la que usarías ConstraintLayout.

  1. En res/layout, abre activity_step1.xml.. Aquí tienes un ConstraintLayout con un solo ImageView de una estrella, con un tono aplicado en su interior.

activity_step1.xml

<!-- initial code -->
<androidx.constraintlayout.widget.ConstraintLayout
       ...
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       >

   <ImageView
           android:id="@+id/red_star"
           ...
   />

</androidx.constraintlayout.motion.widget.MotionLayout>

Este ConstraintLayout no tiene ninguna restricción, por lo que si ejecutaras la app ahora, verías la pantalla de estrella sin restricciones, lo que significa que se posicionaría en una ubicación desconocida. Android Studio te mostrará una advertencia sobre la falta de restricciones.

Paso 2: Convierte a Motion Layout

Para animar con MotionLayout,, debes convertir ConstraintLayout en MotionLayout.

Para que tu diseño use una escena en movimiento, debe apuntar hacia ella.

  1. Para ello, abre la superficie de diseño. En Android Studio 4.0, puedes abrir la superficie de diseño usando el ícono de división o diseño en la parte superior derecha cuando veas un archivo de diseño en formato XML.

a2beea710c2decb7.png

  1. Una vez que abras la superficie de diseño, haz clic con el botón derecho en la vista previa y selecciona Convert to MotionLayout.

4fa936a98a8393b9.png

Esto reemplaza la etiqueta ConstraintLayout con una etiqueta MotionLayout y agrega un motion:layoutDescription a la etiqueta MotionLayout que apunta a @xml/activity_step1_scene..

activity_step1**.xml**

<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/activity_step1_scene">

Una escena en movimiento es un único archivo en formato XML que describe una animación en un MotionLayout.

En cuanto la conviertas a MotionLayout, la superficie de diseño mostrará el Editor de animaciones.

66d0e80d5ab4daf8.png

Hay tres elementos nuevos de la IU en el Editor de animaciones:

  1. Overview: Esta es una selección modal que te permite seleccionar diferentes partes de la animación. En esta imagen, se seleccionó start ConstraintSet. También puedes seleccionar la transición entre start y end haciendo clic en la flecha entre ellos.
  2. Sección: Debajo de la descripción general, hay una ventana de sección que cambia en función del elemento de resumen seleccionado actualmente. En esta imagen, se muestra la información de start ConstraintSet en la ventana de selección.
  3. Atributo: El panel de atributos muestra y te permite editar los atributos del elemento seleccionado actualmente desde la ventana de descripción general o la ventana de selección. En esta imagen, se muestran los atributos del ConstraintSet de start.

Paso 3: Define las restricciones de inicio y finalización

Todas las animaciones se pueden definir en términos de un inicio y un final. El inicio describe cómo se ve la pantalla antes de la animación y el final describe cómo se ve la pantalla después de que se completa la animación. MotionLayout es responsable de averiguar cómo animar entre el estado inicial y el final (con el tiempo).

MotionScene usa una etiqueta ConstraintSet para definir los estados de inicio y finalización. Un ConstraintSet es lo que parece, un conjunto de restricciones que se pueden aplicar a las vistas. Esto incluye las restricciones de ancho, alto y ConstraintLayout. También incluye algunos atributos, como alpha. No contiene las vistas en sí, solo sus restricciones.

Cualquier restricción especificada en un ConstraintSet anulará las restricciones especificadas en el archivo de diseño. Si defines restricciones en el diseño y en MotionScene, solo se aplicarán las restricciones en MotionScene.

En este paso, restringirás la vista de estrella para que comience en la parte superior de la pantalla y finalice en el extremo inferior.

Para completar este paso, puedes usar el Editor de animaciones o editar directamente el texto de activity_step1_scene.xml.

  1. Selecciona el ConstraintSet start en el panel de descripción general

6e57661ed358b860.png

  1. En el panel de selección, selecciona red_star. Actualmente, muestra la fuente de layout, lo que significa que no está restringido en este ConstraintSet. Usa el ícono de lápiz en la esquina superior derecha para Crear restricción

f9564c574b86ea8.gif

  1. Confirma que red_star muestre una fuente de start cuando se seleccione start ConstraintSet en el panel de descripción general.
  2. En el panel Attributes, con red_star seleccionado en start ConstraintSet, agrega una restricción en la parte superior y comienza haciendo clic en los botones azules +.

2fce076cd7b04bd.png

  1. Abre xml/activity_step1_scene.xml para ver el código que generó el Editor de animaciones para esta restricción.

activity_step1_scene.xml

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>

ConstraintSet tiene un id de @id/start y especifica todas las restricciones que se aplicarán a todas las vistas en MotionLayout. Como este MotionLayout solo tiene una vista, solo necesita un Constraint.

El Constraint dentro de ConstraintSet especifica el ID de la vista que está restringiendo, @id/red_star definido en activity_step1.xml. Es importante tener en cuenta que las etiquetas Constraint solo especifican información de diseño y restricciones. La etiqueta Constraint no sabe que se está aplicando a un ImageView.

Esta restricción especifica la altura, el ancho y las otras dos restricciones necesarias para restringir la vista red_star al inicio superior de su elemento superior.

  1. Selecciona el ConstraintSet end en el panel de descripción general.

346e1248639b6f1e.png

  1. Sigue los mismos pasos que antes para agregar un Constraint para red_star en end ConstraintSet.
  2. Si deseas usar el Editor de animaciones para completar este paso, agrega una restricción a bottom y end haciendo clic en los botones azules +.

fd33c779ff83c80a.png

  1. El código en XML se ve de la siguiente manera:

activitiy_step1_scene.xml

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

Al igual que @id/start, este ConstraintSet tiene un solo Constraint en @id/red_star. Esta vez, la restringe al extremo inferior de la pantalla.

No es necesario que les asignes los nombres @id/start y @id/end, pero es conveniente hacerlo.

Paso 4: Define una transición

Cada MotionScene también debe incluir, al menos, una transición. Una transición define cada parte de una animación, de principio a fin.

Una transición debe especificar un ConstraintSet de inicio y fin para la transición. Una transición también puede especificar cómo modificar la animación de otras maneras, por ejemplo, durante cuánto tiempo se ejecutará o cómo animarla arrastrando vistas.

  1. El Editor de animaciones creó una transición para nosotros de forma predeterminada cuando creó el archivo MotionScene. Abre activity_step1_scene.xml para ver la transición generada.

activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
   motion:constraintSetEnd="@+id/end"
   motion:constraintSetStart="@id/start"
   motion:duration="1000">
  <KeyFrameSet>
  </KeyFrameSet>
</Transition>

Esto es todo lo que MotionLayout necesita para compilar una animación. Observa cada atributo:

  • Se aplicará constraintSetStart a las vistas cuando comience la animación.
  • Se aplicará constraintSetEnd a las vistas al final de la animación.
  • duration especifica cuánto debería tardar la animación en milisegundos.

Luego, MotionLayout determinará una ruta entre las restricciones de inicio y finalización, y la animará durante el tiempo especificado.

Paso 5: Obtén una vista previa de la animación en el Editor de animaciones

dff9ecdc1f4a0740.gif

Animación: Video en el que se reproduce una vista previa de transición en el Editor de animaciones.

  1. Para abrir Motion Editor, haz clic en la flecha entre start y end en el panel de descripción general para seleccionar la transición.

1dc541ae8c43b250.png

  1. El panel de selección muestra los controles de reproducción y una barra de arrastre cuando se selecciona una transición. Haz clic en reproducir o arrastra la posición actual para obtener una vista previa de la animación.

a0fd2593384dfb36.png

Paso 6: Agrega un controlador de clics

Necesitas una forma de iniciar la animación. Una forma de hacerlo es hacer que MotionLayout responda a los eventos de clic en @id/red_star.

  1. Abre el editor de movimiento y haz clic en la flecha entre el inicio y el final en el panel de información general para seleccionar la transición.

b6f94b344ce65290.png

  1. Haz clic en 699f7ae04024ccf6.png Crear controlador de clic o deslizamiento en la barra de herramientas del panel de descripción general . Esto agrega un controlador que iniciará una transición.
  2. Selecciona Control de clics en la ventana emergente.

ccf92d06335105fe.png

  1. Cambie el valor de View To Click a red_star.

b0d3f0c970604f01.png

  1. Haz clic en Add. El controlador de clics se representa con un punto pequeño en Transition en Motion Editor.

cec3913e67fb4105.png

  1. Con la transición seleccionada en el panel de descripción general, agrega un atributo clickAction de toggle al controlador OnClick que acabas de agregar en el panel de atributos.

9af6fc60673d093d.png

  1. Abre activity_step1_scene.xml para ver el código que generó Motion Editor

activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
    motion:constraintSetStart="@+id/start"
    motion:constraintSetEnd="@+id/end"
    motion:duration="1000">
    <!-- MotionLayout will handle clicks on @id/red_star to "toggle" the animation between the start and end -->
    <OnClick
        motion:targetId="@id/red_star"
        motion:clickAction="toggle" />
</Transition>

El Transition le indica a MotionLayout que ejecute la animación en respuesta a eventos de clic con una etiqueta <OnClick>. Observa cada atributo:

  • targetId es la vista que se debe mirar en busca de clics.
  • clickAction de toggle cambiará entre el estado inicial y el final cuando se haga clic en él. Puedes ver otras opciones para clickAction en la documentación.
  1. Ejecuta tu código, haz clic en el Paso 1 y, luego, en la estrella roja para ver la animación.

Paso 5: Animaciones en acción

Ejecuta la app. Deberías ver la ejecución de la animación cuando hagas clic en la estrella.

7ba88af963fdfe10.gif

El archivo de escena de movimiento completo define un elemento Transition que apunta a un ConstraintSet inicial y final.

Al comienzo de la animación (@id/start), el ícono de estrella tiene restricciones respecto del inicio superior de la pantalla. Al final de la animación (@id/end), el ícono de estrella tiene restricciones en el extremo inferior de la pantalla.

<?xml version="1.0" encoding="utf-8"?>

<!-- Describe the animation for activity_step1.xml -->
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:android="http://schemas.android.com/apk/res/android">
   <!-- A transition describes an animation via start and end state -->
   <Transition
           motion:constraintSetStart="@+id/start"
           motion:constraintSetEnd="@+id/end"
           motion:duration="1000">
       <!-- MotionLayout will handle clicks on @id/star to "toggle" the animation between the start and end -->
       <OnClick
               motion:targetId="@id/red_star"
               motion:clickAction="toggle" />
   </Transition>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/start">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintStart_toStartOf="parent"
               motion:layout_constraintTop_toTopOf="parent" />
   </ConstraintSet>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/end">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintEnd_toEndOf="parent"
               motion:layout_constraintBottom_toBottomOf="parent" />
   </ConstraintSet>
</MotionScene>

4. Animaciones basadas en eventos de arrastre

En este paso, compilarás una animación que responda a un evento de arrastre del usuario (cuando el usuario desliza la pantalla) para ejecutar la animación. MotionLayout admite el seguimiento de eventos táctiles para mover vistas, así como gestos de deslizamiento basados en la física para hacer que el movimiento sea fluido.

Paso 1: Inspecciona el código inicial

  1. Para comenzar, abre el archivo de diseño activity_step2.xml, que tiene un MotionLayout existente. Echa un vistazo al código.

activity_step2.xml

<!-- initial code -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/step2" >

   <ImageView
           android:id="@+id/left_star"
           ...
   />

   <ImageView
           android:id="@+id/right_star"
           ...
   />

   <ImageView
           android:id="@+id/red_star"
           ...
   />

   <TextView
           android:id="@+id/credits"
           ...
           motion:layout_constraintTop_toTopOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.motion.widget.MotionLayout>

Este diseño define todas las vistas de la animación. Los iconos de tres estrellas no están limitados en el diseño porque se animarán en la escena de movimiento.

Los créditos TextView tienen restricciones aplicadas, ya que permanecen en el mismo lugar durante toda la animación y no modifican ningún atributo.

Paso 2: Anima la escena

Al igual que la última animación, la animación se definirá con un ConstraintSet, de inicio y fin y un Transition.

Define el ConstraintSet de inicio

  1. Abre la escena de movimiento xml/step2.xml para definir la animación.
  2. Agrega las restricciones para la restricción inicial start. Al comienzo, las tres estrellas están centradas en la parte inferior de la pantalla. Las estrellas izquierda y derecha tienen un valor alpha de 0.0, lo que significa que son completamente transparentes y ocultas.

step2.xml

<!-- TODO apply starting constraints -->

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

En este ConstraintSet, especificas un Constraint para cada una de las estrellas. MotionLayout aplicará cada restricción al comienzo de la animación.

Cada vista de estrella se centra en la parte inferior de la pantalla con las restricciones inicial, final e inferior. Las dos estrellas, @id/left_star y @id/right_star, tienen un valor alfa adicional que las hace invisibles y se aplican al comienzo de la animación.

Los conjuntos de restricciones start y end definen el inicio y el final de la animación. Una restricción en el inicio, como motion:layout_constraintStart_toStartOf, restringirá el inicio de una vista al inicio de otra vista. Esto puede ser confuso al principio, porque el nombre start se usa para y ambos se usan en el contexto de restricciones. Para ayudar a establecer la distinción, el start en layout_constraintStart se refiere al "inicio" de la vista, que está a la izquierda en un idioma de izquierda a derecha y de derecha en un idioma de derecha a izquierda. El conjunto de restricciones start hace referencia al inicio de la animación.

Define el final ConstraintSet

  1. Define la restricción final para usar una cadena a fin de posicionar las tres estrellas juntas debajo de @id/credits. Además, establecerá el valor final de las alpha de las estrellas izquierdas y derechas en 1.0.

step2.xml

<!-- TODO apply ending constraints -->

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintHorizontal_chainStyle="packed"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toStartOf="@id/red_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toEndOf="@id/left_star"
           motion:layout_constraintEnd_toStartOf="@id/right_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintStart_toEndOf="@id/red_star"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintTop_toBottomOf="@id/credits" />
</ConstraintSet>

El resultado final es que las vistas se extienden hacia arriba desde el centro a medida que se animan.

Además, como la propiedad alpha se establece en @id/right_start y @id/left_star en ambos ConstraintSets, se aplicará un fundido de entrada de ambas vistas a medida que avance la animación.

Animaciones basadas en el deslizamiento del usuario

MotionLayout puede realizar un seguimiento de los eventos de arrastre del usuario, o de un deslizamiento, para crear un "lanzamiento" basado en la física. animación. Esto significa que las vistas continuarán si el usuario las arroja y se volverán más lentas como lo haría un objeto físico al rodar por una superficie. Puedes agregar este tipo de animación con una etiqueta OnSwipe en Transition.

  1. Reemplaza el comentario TODO para agregar una etiqueta OnSwipe con <OnSwipe motion:touchAnchorId="@id/red_star" />.

step2.xml

<!-- TODO add OnSwipe tag -->

<!-- A transition describes an animation via start and end state -->
<Transition
       motion:constraintSetStart="@+id/start"
       motion:constraintSetEnd="@+id/end">
   <!-- MotionLayout will track swipes relative to this view -->
   <OnSwipe motion:touchAnchorId="@id/red_star" />
</Transition>

OnSwipe contiene algunos atributos. El más importante es touchAnchorId.

  • touchAnchorId es la vista con seguimiento que se mueve en respuesta al tacto. MotionLayout mantendrá esta vista a la misma distancia del dedo que deslizas el dedo.
  • touchAnchorSide: Determina qué lado de la vista debe rastrearse. Esto es importante para las vistas que cambian de tamaño, siguen rutas complejas o tienen un lado que se mueve más rápido que el otro.
  • dragDirection: Determina qué dirección es importante para esta animación (arriba, abajo, izquierda o derecha).

Cuando MotionLayout escuche eventos de arrastre, se registrará el objeto de escucha en la vista MotionLayout y no en la que especifica touchAnchorId. Cuando un usuario inicie un gesto en cualquier lugar de la pantalla, MotionLayout mantendrá la distancia entre el dedo y el touchAnchorSide de la vista touchAnchorId constante. Si tocan a 100 dp de distancia del lado del ancla, por ejemplo, MotionLayout mantendrá ese lado a 100 dp de distancia del dedo durante toda la animación.

Probar

  1. Vuelve a ejecutar la app y abre la pantalla del paso 2. Verás la animación.
  2. Intenta "deslizar". o levanta el dedo a mitad de la animación para explorar cómo MotionLayout muestra animaciones basadas en la física fluida.

fefcdd690a0dcaec.gif

MotionLayout puede animar entre diseños muy diferentes usando las funciones de ConstraintLayout para crear efectos enriquecidos.

En esta animación, para comenzar, las tres vistas se posicionan relativas a su elemento superior en la parte inferior de la pantalla. Al final, las tres vistas se posicionan relativas a @id/credits en una cadena.

A pesar de estos diseños muy diferentes, MotionLayout creará una animación fluida entre el inicio y el final.

5. Cómo modificar una ruta

En este paso, compilarás una animación que sigue un trazado complejo durante la animación y anima los créditos durante el movimiento. MotionLayout puede modificar la ruta de acceso que tomará una vista entre el inicio y el final mediante un KeyPosition.

Paso 1: Explora el código existente

  1. Abre layout/activity_step3.xml y xml/step3.xml para ver el diseño y la escena de movimiento existentes. Los elementos ImageView y TextView muestran la luna y el texto de los créditos.
  2. Abre el archivo de escena en movimiento (xml/step3.xml). Verás que se definió un Transition de @id/start a @id/end. La animación mueve la imagen de la luna desde la parte inferior izquierda de la pantalla hasta la parte inferior derecha con dos ConstraintSets. El texto de créditos se atenúa de alpha="0.0" a alpha="1.0" a medida que se mueve la Luna.
  3. Ejecuta la app ahora y selecciona el Paso 3. Verás que la Luna sigue una trayectoria lineal (o una línea recta) de principio a fin cuando hagas clic en ella.

Paso 2: Habilita la depuración de ruta de acceso.

Antes de agregar un arco al movimiento de la luna, es útil habilitar la depuración de ruta en MotionLayout.

Para ayudar a desarrollar animaciones complejas con MotionLayout, puedes dibujar la ruta de animación de cada vista. Esto es útil cuando quieres visualizar tu animación y ajustar los pequeños detalles del movimiento.

  1. Para habilitar las rutas de depuración, abre layout/activity_step3.xml y agrega motion:motionDebug="SHOW_PATH" a la etiqueta MotionLayout.

activity_step3.xml

<!-- Add motion:motionDebug="SHOW_PATH" -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:motionDebug="SHOW_PATH" >

Después de habilitar la depuración de ruta, cuando vuelvas a ejecutar la app, verás las rutas de todas las vistas visualizadas con una línea de puntos.

23bbb604f456f65c.png

  • Los círculos representan la posición inicial o final de una vista.
  • Las líneas representan la ruta de una vista.
  • Los diamantes representan un KeyPosition que modifica la ruta.

Por ejemplo, en esta animación, el círculo del medio es la posición del texto de créditos.

Paso 3: Modifica una ruta

Todas las animaciones en MotionLayout se definen con un ConstraintSet de inicio y de fin que define cómo se ve la pantalla antes de comenzar la animación y después de que esta finalice. De forma predeterminada, MotionLayout traza una ruta lineal (una línea recta) entre la posición inicial y final de cada vista que cambia de posición.

Para crear rutas complejas como el arco de la Luna en este ejemplo, MotionLayout usa un KeyPosition para modificar la ruta que toma una vista entre el inicio y el final.

  1. Abre xml/step3.xml y agrega un KeyPosition a la escena. La etiqueta KeyPosition se coloca dentro de la etiqueta Transition.

eae4dae9a12d0410.png

step3.xml

<!-- TODO: Add KeyFrameSet and KeyPosition -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

Un KeyFrameSet es un elemento secundario de un Transition y es un conjunto de todos los KeyFrames, como KeyPosition, que se deben aplicar durante la transición.

Como MotionLayout calcula la ruta de la luna entre el inicio y el final, modifica la ruta según el KeyPosition especificado en el KeyFrameSet. Puedes volver a ejecutar la app para ver cómo modifica la ruta de acceso.

Una KeyPosition tiene varios atributos que describen cómo modifica la ruta de acceso. Las más importantes son las siguientes:

  • framePosition es un número entre 0 y 100. Define en qué momento de la animación se debe aplicar este KeyPosition, donde 1 es el 1% a través de la animación y 99 es el 99% a través de la animación. Entonces, si el valor es 50, lo aplicarás justo en el medio.
  • motionTarget es la vista para la que este KeyPosition modifica la ruta de acceso.
  • keyPositionType es la forma en que este KeyPosition modifica la ruta. Puede ser parentRelative, pathRelative o deltaRelative (como se explica en el siguiente paso).
  • percentX | percentY es la medida en la que se debe modificar la ruta en framePosition (valores entre 0.0 y 1.0, con valores negativos y superiores a 1).

Considéralo de esta manera: "En framePosition modifica la ruta de motionTarget mueve el campo percentX o percentY de acuerdo con las coordenadas que determina keyPositionType".

De forma predeterminada, MotionLayout redondeará cualquier esquina que se ingrese modificando la ruta. Si observas la animación que acabas de crear, verás que la Luna sigue una curva en la curva. En la mayoría de las animaciones, lo que quieres es esto y, si no es así, puedes especificar el atributo curveFit para personalizarlo.

Probar

Si vuelves a ejecutar la app, verás la animación de este paso.

46b179c01801f19e.gif

La luna sigue un arco porque pasa por un KeyPosition especificado en el Transition.

<KeyPosition
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.5"
/>

Puedes leer este KeyPosition de la siguiente manera: "En framePosition 50 (a mitad de la animación), modifica la ruta de motionTarget @id/moon moviéndolo 50% Y (a mitad de la pantalla) según las coordenadas determinadas por parentRelative (el MotionLayout completo)".

Por lo tanto, en la mitad de la animación, la Luna debe pasar por un KeyPosition que esté un 50% menos en la pantalla. Este KeyPosition no modifica el movimiento X en absoluto, por lo que la Luna seguirá yendo de principio a fin en sentido horizontal. MotionLayout descubrirá una ruta suave que atraviesa este KeyPosition mientras se mueve entre el inicio y el final.

Si te fijas bien, el texto de créditos está restringido por la posición de la Luna. ¿Por qué no se mueve de manera vertical también?

1c7cf779931e45cc.gif

<Constraint
       android:id="@id/credits"
       ...
       motion:layout_constraintBottom_toBottomOf="@id/moon"
       motion:layout_constraintTop_toTopOf="@id/moon"
/>

Resulta que, aunque estás modificando el recorrido que toma la Luna, las posiciones de inicio y finalización de la Luna no la mueven de forma vertical en absoluto. KeyPosition no modifica la posición inicial ni final, por lo que el texto de créditos se restringe a la posición final final de la luna.

Si deseas que los créditos se muevan con la luna, puedes agregar un KeyPosition a los créditos o modificar las restricciones de inicio en @id/credits.

En la siguiente sección, profundizarás en los diferentes tipos de keyPositionType en MotionLayout.

6. Información sobre keyPositionType

En el último paso, usaste un tipo keyPosition de parentRelative para desplazar la ruta en un 50% de la pantalla. El atributo keyPositionType determina cómo MotionLayout modificará la ruta de acuerdo con percentX o percentY.

<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

Hay tres tipos diferentes de keyPosition posibles: parentRelative, pathRelative y deltaRelative. Si especificas un tipo, cambiará el sistema de coordenadas por el que se calculan percentX y percentY.

¿Qué es un sistema de coordenadas?

Un sistema de coordenadas ofrece una manera de especificar un punto en el espacio. También son útiles para describir una posición en la pantalla.

Los sistemas de coordenadas MotionLayout son un sistema de coordenadas cartesianas. Esto significa que tienen un eje X y un eje Y definidos por dos líneas perpendiculares. La diferencia clave entre ellos es dónde se ubica el eje X en la pantalla (el eje Y siempre es perpendicular al eje X).

Todos los sistemas de coordenadas en MotionLayout usan valores entre 0.0 y 1.0 en los ejes X e Y. Permiten valores negativos y mayores que 1.0. Por ejemplo, un valor percentX de -2.0 significaría ir dos veces en la dirección opuesta al eje X.

Si todo eso suena demasiado parecido a la clase de álgebra, ¡mira las imágenes a continuación!

Coordenadas relativas principales

a7b7568d46d9dec7.png

El keyPositionType de parentRelative usa el mismo sistema de coordenadas que la pantalla. Define (0, 0) en la parte superior izquierda de todo el MotionLayout y (1, 1) en la parte inferior derecha.

Puedes usar parentRelative siempre que quieras crear una animación que se mueva por todo el MotionLayout, como el arco lunar en este ejemplo.

Sin embargo, si deseas modificar una ruta relativa al movimiento, por ejemplo, hacer que se curva un poco, los otros dos sistemas de coordenadas son una mejor opción.

coordenadas deltaRelative

5680bf553627416c.png

Delta es un término matemático para el cambio, por lo que deltaRelative es una forma de decir “cambio relativo”. En coordenadas deltaRelative, (0,0) es la posición inicial de la vista y (1,1) es la posición final. Los ejes X e Y están alineados con la pantalla.

El eje X siempre es horizontal en la pantalla, mientras que el eje Y siempre es vertical. En comparación con parentRelative, la principal diferencia es que las coordenadas describen solo la parte de la pantalla en la que se moverá la vista.

deltaRelative es un excelente sistema de coordenadas para controlar el movimiento horizontal o vertical de forma aislada. Por ejemplo, puedes crear una animación que complete solo su movimiento vertical (Y) al 50% y siga animando horizontalmente (X).

coordenadas relativas

f3aaadaac8b4a93f.png

El último sistema de coordenadas de MotionLayout es pathRelative. Es bastante diferente de los otros dos, ya que el eje X sigue la ruta de movimiento de principio a fin. Por lo tanto, (0,0) es la posición inicial y (1,0) es la posición final.

¿Por qué querrías esto? Es bastante sorprendente a primera vista, especialmente porque este sistema de coordenadas ni siquiera está alineado con el sistema de coordenadas de la pantalla.

Resulta que pathRelative es muy útil para algunas cosas.

  • Acelerar, ralentizar o detener una vista durante parte de la animación. Dado que la dimensión X siempre coincidirá de forma exacta con la ruta que toma la vista, puedes usar un pathRelative KeyPosition para cambiar a qué framePosition se llega un punto específico de esa ruta. Por lo tanto, una KeyPosition en framePosition="50" con un percentX="0.1" haría que la animación demore el 50% del tiempo en recorrer el primer 10% del movimiento.
  • Agrega un arco sutil a una ruta. Dado que la dimensión Y es siempre perpendicular al movimiento, cambiar Y cambiará la ruta a la curva en relación con el movimiento general.
  • Agregar una segunda dimensión cuando deltaRelative no funcione Si el movimiento es completamente horizontal y vertical, deltaRelative solo creará una dimensión útil. Sin embargo, pathRelative siempre creará coordenadas Y y X utilizables.

En el siguiente paso, aprenderás a compilar rutas aún más complejas con más de una KeyPosition.

7. Cómo crear rutas complejas

Al observar la animación que creaste en el último paso, se crea una curva suave, pero la forma podría ser más similar a la de una luna.

Cómo modificar una ruta con varios elementos KeyPosition

MotionLayout puede modificar más una ruta definiendo tantos KeyPosition como sea necesario para obtener cualquier movimiento. Para esta animación, crearás un arco, pero, si lo deseas, podrías hacer que la luna salte hacia arriba y hacia abajo en el centro de la pantalla.

  1. Abre xml/step4.xml. Verás que tiene las mismas vistas y el KeyFrame que agregaste en el último paso.
  2. Para redondear la parte superior de la curva, agrega dos KeyPositions más a la ruta de @id/moon, una justo antes de que llegue a la parte superior y otra después.

500b5ac2db48ef87.png

step4.xml

<!-- TODO: Add two more KeyPositions to the KeyFrameSet here -->
<KeyPosition
       motion:framePosition="25"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>
<KeyPosition
       motion:framePosition="75"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>

Estos KeyPositions se aplicarán el 25% y el 75% del recorrido de la animación y harán que @id/moon se mueva por una ruta que esté a un 60% de la parte superior de la pantalla. Combinado con el KeyPosition existente al 50%, se crea un arco suave para que la Luna lo siga.

En MotionLayout, puedes agregar tantos KeyPositions como necesites para obtener la ruta de movimiento que desees. MotionLayout aplicará cada KeyPosition en la framePosition especificada y determinará cómo crear un movimiento suave que pase por todos los KeyPositions.

Probar

  1. Vuelve a ejecutar la app. Ve al Paso 4 para ver la animación en acción. Cuando haces clic en la luna, sigue la ruta de principio a fin y pasa por cada KeyPosition que se especificó en el KeyFrameSet.

Explora por tu cuenta

Antes de pasar a otros tipos de KeyFrame, intenta agregar más KeyPositions a KeyFrameSet para ver qué tipo de efectos puedes crear solo con KeyPosition.

En este ejemplo, se muestra cómo crear una ruta compleja que se mueva hacia adelante y hacia atrás durante la animación.

cd9faaffde3dfef.png

step4.xml

<!-- Complex paths example: Dancing moon -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="25"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
           motion:percentX="0.3"
   />
   <KeyPosition
           motion:framePosition="75"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
</KeyFrameSet>

Cuando termines de explorar KeyPosition, en el siguiente paso, pasarás a otros tipos de KeyFrames.

8. Cómo cambiar atributos durante el movimiento

Para compilar animaciones dinámicas, debes cambiar los valores size, rotation o alpha de las vistas a medida que avanza la animación. MotionLayout admite la animación de muchos atributos en cualquier vista mediante un KeyAttribute.

En este paso, usarás KeyAttribute para hacer que la escala lunar rote. También usarás un KeyAttribute para retrasar la aparición del texto hasta que la Luna esté por completar su viaje.

Paso 1: Cambia el tamaño y rota con KeyAttribute

  1. Abre xml/step5.xml, que contiene la misma animación que creaste en el último paso. Para variar, esta pantalla usa una imagen espacial diferente como fondo.
  2. Para que la Luna se expanda y rote, agrega dos etiquetas KeyAttribute en KeyFrameSet, en keyFrame="50" y keyFrame="100"

bbae524a2898569.png

step5.xml

<!-- TODO: Add KeyAttributes to rotate and resize @id/moon -->

<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       android:scaleY="2.0"
       android:scaleX="2.0"
       android:rotation="-360"
/>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon"
       android:rotation="-720"
/>

Estos KeyAttributes se aplican al 50% y al 100% de la animación. La primera KeyAttribute al 50% ocurrirá en la parte superior del arco y hará que la vista se duplique de tamaño y rote -360 grados (o un círculo completo). El segundo KeyAttribute finalizará la segunda rotación a -720 grados (dos círculos completos) y reducirá el tamaño a normal, ya que los valores scaleX y scaleY se establecen de forma predeterminada en 1.0.

Al igual que una KeyPosition, una KeyAttribute usa framePosition y motionTarget para especificar cuándo aplicar la KeyFrame y qué vista modificar. MotionLayout interpolará entre KeyPositions para crear animaciones fluidas.

KeyAttributes admite atributos que se pueden aplicar a todas las vistas. Admiten el cambio de atributos básicos, como visibility, alpha o elevation. También puedes cambiar la rotación como lo haces aquí, rotar en tres dimensiones con rotateX y rotateY, escalar el tamaño con scaleX y scaleY, o trasladar la posición de la vista en X, Y o Z.

Paso 2: Retrasa la aparición de los créditos

Uno de los objetivos de este paso es actualizar la animación para que el texto de los créditos no aparezca hasta que la animación esté completamente completa.

  1. Para retrasar la aparición de los créditos, define un KeyAttribute más que garantice que alpha permanezca en 0 hasta keyPosition="85". MotionLayout pasará sin problemas de 0 a 100 alfa, pero lo hará durante el último 15% de la animación.

step5.xml

<!-- TODO: Add KeyAttribute to delay the appearance of @id/credits -->

<KeyAttribute
       motion:framePosition="85"
       motion:motionTarget="@id/credits"
       android:alpha="0.0"
/>

Este KeyAttribute mantiene el alpha de @id/credits en 0.0 durante el primer 85% de la animación. Como comienza en un alfa de 0, será invisible durante el primer 85% de la animación.

El efecto final de este KeyAttribute es que los créditos aparecen hacia el final de la animación. Esto da la apariencia de que están coordinadas con la luna asentándose en la esquina derecha de la pantalla.

Al retrasar las animaciones en una vista mientras que otra se mueve de esta manera, puedes crear animaciones impresionantes que se sienten dinámicas para el usuario.

Probar

  1. Vuelve a ejecutar la app y ve al Paso 5 para ver la animación en acción. Cuando hagas clic en la luna, seguirá la ruta de principio a fin y pasará por cada KeyAttribute que se especificó en el KeyFrameSet.

2f4bfdd681c1fa98.gif

Debido a que giras la luna dos círculos completos, ahora hará un doble giro hacia atrás, y los créditos retrasarán su aparición hasta que la animación esté casi lista.

Explora por tu cuenta

Antes de pasar al último tipo de KeyFrame, intenta modificar otros atributos estándares en KeyAttributes. Por ejemplo, intenta cambiar rotation a rotationX para ver qué animación produce.

A continuación, le mostramos una lista de los atributos estándares que puede probar:

  • android:visibility
  • android:alpha
  • android:elevation
  • android:rotation
  • android:rotationX
  • android:rotationY
  • android:scaleX
  • android:scaleY
  • android:translationX
  • android:translationY
  • android:translationZ

9. Cambia los atributos personalizados

Las animaciones enriquecidas implican cambiar el color u otros atributos de una vista. Si bien MotionLayout puede usar un KeyAttribute para cambiar cualquiera de los atributos estándares enumerados en la tarea anterior, debes usar un CustomAttribute para especificar cualquier otro atributo.

Se puede usar un CustomAttribute para configurar cualquier valor que tenga un método set. Por ejemplo, puedes establecer el backgroundColor en una vista con un CustomAttribute. MotionLayout usará reflection para encontrar el método set y, luego, lo llamará repetidamente para animar la vista.

En este paso, usarás un CustomAttribute para configurar el atributo colorFilter en la luna y compilar la animación que se muestra a continuación.

5fb6792126a09fda.gif

Define atributos personalizados

  1. Para comenzar, abre xml/step6.xml, que contiene la misma animación que compilaste en el último paso.
  2. Para hacer que la luna cambie de color, agrega dos KeyAttribute con CustomAttribute en KeyFrameSet en keyFrame="0", keyFrame="50" y keyFrame="100".

214699d5fdd956da.png

step6.xml

<!-- TODO: Add Custom attributes here -->
<KeyAttribute
       motion:framePosition="0"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFB612"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>

Agregas un CustomAttribute dentro de una KeyAttribute. CustomAttribute se aplicará en el framePosition especificado por el KeyAttribute.

Dentro del CustomAttribute, debes especificar un attributeName y un valor para configurar.

  • motion:attributeName es el nombre del método set al que llamará este atributo personalizado. En este ejemplo, se llamará a setColorFilter en Drawable.
  • motion:custom*Value es un valor personalizado del tipo que se indica en el nombre. En este ejemplo, el valor personalizado es un color especificado.

Los valores personalizados pueden tener cualquiera de los siguientes tipos:

  • Color
  • Número entero
  • Número de punto flotante
  • String
  • Dimensión
  • Booleano

Con esta API, MotionLayout puede animar cualquier cosa que proporcione un método set en cualquier vista.

Probar

  1. Vuelve a ejecutar la app y ve al Paso 6 para ver la animación en acción. Cuando hagas clic en la luna, seguirá la ruta de principio a fin y pasará por cada KeyAttribute que se especificó en el KeyFrameSet.

5fb6792126a09fda.gif

Cuando agregas más KeyFrames, MotionLayout cambia la ruta de la Luna de una línea recta a una curva compleja, lo que agrega un doble giro hacia atrás, el tamaño y un cambio de color en la animación.

En las animaciones reales, a menudo animarás varias vistas al mismo tiempo para controlar su movimiento en diferentes rutas y velocidades. Si especificas un KeyFrame diferente para cada vista, se podrán combinar animaciones enriquecidas que animan varias vistas con MotionLayout.

10. Eventos de arrastre y rutas complejas

En este paso, explorarás el uso de OnSwipe con rutas complejas. Hasta ahora, un objeto de escucha OnClick activó la animación de la luna y se ejecuta durante un tiempo fijo.

Para controlar animaciones que tienen rutas complejas con OnSwipe, como la animación lunar que creaste en los últimos pasos, debes comprender el funcionamiento de OnSwipe.

Paso 1: Explora el comportamiento de OnSwipe

  1. Abre xml/step7.xml y busca la declaración OnSwipe existente.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
/>
  1. Ejecuta la app en tu dispositivo y ve al Paso 7. Comprueba si puedes producir una animación fluida arrastrando la luna a lo largo de la ruta del arco.

Cuando ejecutes esta animación, no se verá muy bien. Cuando la Luna llega a la cima del arco, comienza a saltar.

ed96e3674854a548.gif

Para entender el error, considera lo que sucede cuando el usuario toca justo debajo de la parte superior del arco. Como la etiqueta OnSwipe tiene un motion:touchAnchorSide="bottom", MotionLayout intentará que la distancia entre el dedo y la parte inferior de la vista sea constante durante toda la animación.

Sin embargo, como la parte inferior de la Luna no siempre va en la misma dirección, sube y baja, MotionLayout no sabe qué hacer cuando el usuario acaba de pasar la parte superior del arco. Para tener en cuenta esto, dado que se está rastreando la parte inferior de la Luna, ¿dónde debería colocarse cuando el usuario toque aquí?

56cd575c5c77eddd.png

Paso 2: Usa el lado derecho

Para evitar errores como este, es importante elegir siempre una touchAnchorId y una touchAnchorSide que siempre avancen en una dirección durante toda la animación.

En esta animación, los lados right y left de la Luna progresarán en la pantalla en una dirección.

Sin embargo, bottom y top invertirán la dirección. Cuando OnSwipe intente rastrearlo, se confundirá cuando cambie su dirección.

  1. Para que esta animación siga los eventos táctiles, cambia touchAnchorSide a right.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="right"
/>

Paso 3: Usa dragDirections

También puedes combinar dragDirection con touchAnchorSide para hacer que un seguimiento lateral tenga una dirección diferente de la habitual. Es importante que touchAnchorSide solo avance en una dirección, pero puedes indicarle a MotionLayout en qué dirección hacer el seguimiento. Por ejemplo, puedes mantener el touchAnchorSide="bottom", pero agregar dragDirection="dragRight". Esto hará que MotionLayout realice un seguimiento de la posición de la parte inferior de la vista, pero solo tenga en cuenta su ubicación cuando se mueva hacia la derecha (ignora el movimiento vertical). Por lo tanto, aunque la parte inferior suba y baje, se animará correctamente con OnSwipe.

  1. Actualiza OnSwipe para que realice un seguimiento correcto del movimiento de la Luna.

step7.xml

<!-- Using dragDirection to control the direction of drag tracking 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
       motion:dragDirection="dragRight"
/>

Probar

  1. Vuelve a ejecutar la app y, luego, intenta arrastrar la luna por toda la ruta. Aunque sigue un arco complejo, MotionLayout podrá avanzar la animación en respuesta a eventos de deslizamiento.

5458dff382261427.gif

11. Ejecución de movimiento con código

Se puede usar MotionLayout para crear animaciones enriquecidas cuando se usa con CoordinatorLayout. En este paso, compilarás un encabezado que se puede contraer con MotionLayout.

Paso 1: Explora el código existente

  1. Para comenzar, abre layout/activity_step8.xml.
  2. En layout/activity_step8.xml, verás que ya se compiló un CoordinatorLayout y un AppBarLayout en funcionamiento.

activity_step8.xml

<androidx.coordinatorlayout.widget.CoordinatorLayout
       ...>
   <com.google.android.material.appbar.AppBarLayout
           android:id="@+id/appbar_layout"
           android:layout_width="match_parent"
           android:layout_height="180dp">
       <androidx.constraintlayout.motion.widget.MotionLayout
               android:id="@+id/motion_layout"
               ... >
           ...
       </androidx.constraintlayout.motion.widget.MotionLayout>
   </com.google.android.material.appbar.AppBarLayout>
  
   <androidx.core.widget.NestedScrollView
           ...
           motion:layout_behavior="@string/appbar_scrolling_view_behavior" >
           ...
   </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

Este diseño usa un CoordinatorLayout para compartir información de desplazamiento entre NestedScrollView y AppBarLayout. Por lo tanto, cuando NestedScrollView se desplace hacia arriba, le indicará a AppBarLayout sobre el cambio. Así es como implementas una barra de herramientas que se puede contraer como esta en Android: el desplazamiento del texto se “coordinará”. con el encabezado que se puede contraer.

La escena de movimiento a la que apunta @id/motion_layout es similar a la escena de movimiento del último paso. Sin embargo, se quitó la declaración OnSwipe para permitir que funcionara con CoordinatorLayout.

  1. Ejecuta la app y ve al Paso 8. Verás que cuando te desplazas por el texto, la Luna no se mueve.

Paso 2: Haz que MotionLayout se desplace

  1. Para que la vista MotionLayout se desplace en cuanto se desplace NestedScrollView, agrega motion:minHeight y motion:layout_scrollFlags a MotionLayout.

activity_step8.xml

<!-- Add minHeight and layout_scrollFlags to the MotionLayout -->

<androidx.constraintlayout.motion.widget.MotionLayout
       android:id="@+id/motion_layout"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       motion:layoutDescription="@xml/step8"
       motion:motionDebug="SHOW_PATH"
       android:minHeight="80dp"
       motion:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed"  >
  1. Vuelve a ejecutar la app y ve al Paso 8. Verás que MotionLayout se contrae a medida que te desplazas hacia arriba. Sin embargo, la animación aún no progresa en función del comportamiento de desplazamiento.

Paso 3: Mueve el movimiento con código

  1. Abre Step8Activity.kt . Edita la función coordinateMotion() para indicarle a MotionLayout sobre los cambios en la posición de desplazamiento.

Step8Activity.kt

// TODO: set progress of MotionLayout based on an AppBarLayout.OnOffsetChangedListener

private fun coordinateMotion() {
    val appBarLayout: AppBarLayout = findViewById(R.id.appbar_layout)
    val motionLayout: MotionLayout = findViewById(R.id.motion_layout)

    val listener = AppBarLayout.OnOffsetChangedListener { unused, verticalOffset ->
        val seekPosition = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
        motionLayout.progress = seekPosition
    }

    appBarLayout.addOnOffsetChangedListener(listener)
}

Este código registrará un OnOffsetChangedListener al que se llamará cada vez que el usuario se desplace con el desplazamiento actual.

MotionLayout admite la búsqueda de su transición estableciendo la propiedad de progreso. Para convertir entre un verticalOffset y un progreso porcentual, divide por el rango de desplazamiento total.

Probar

  1. Vuelve a implementar la app y ejecuta la animación del Paso 8. Verás que MotionLayout avanzará la animación en función de la posición de desplazamiento.

ee5ce4d9e33a59ca.gif

Es posible compilar animaciones dinámicas y personalizadas de la barra de herramientas que se contraen mediante MotionLayout. Si usas una secuencia de KeyFrames, puedes lograr efectos muy llamativos.

12. Felicitaciones

En este codelab, se trató la API básica de MotionLayout.

Para ver más ejemplos de MotionLayout en la práctica, consulta la muestra oficial. Asegúrate de consultar la documentación.

Más información

MotionLayout admite aún más funciones que no se tratan en este codelab, como KeyCycle,, que te permite controlar rutas o atributos con ciclos repetidos, y KeyTimeCycle,, que te permite animar en función de la hora del reloj. Consulta las muestras para ver ejemplos de cada uno.

Para obtener vínculos a otros codelabs de este curso, consulta la página de destino de codelabs avanzados de Android en Kotlin.