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 de forma secuencial, aunque no es obligatorio. Todos los codelabs del curso se indican en la página de destino de los codelabs de Aspectos avanzados de Android en Kotlin.
MotionLayout es una biblioteca que te permite agregar movimiento enriquecido a tu app para Android. Se basa en ConstraintLayout, y te permite animar cualquier elemento 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. Con XML declarativo, puedes crear animaciones coordinadas que incluyan varias vistas que sean difíciles de lograr en código.
Las animaciones son una excelente manera de mejorar la experiencia en una app. Puedes usar animaciones para lo siguiente:
- Mostrar cambios: La animación entre estados permite que el usuario realice un seguimiento natural de los cambios en la IU.
- Atraer la atención: Usa animaciones para atraer la atención a los elementos importantes de la IU.
- Crea diseños atractivos: El movimiento eficaz en el diseño hace que las apps se vean pulidas.
Requisitos previos
Este codelab se diseñó para desarrolladores con cierta experiencia en el desarrollo para Android. Antes de intentar completar este codelab, debes hacer lo siguiente:
- Saber cómo crear una app con una actividad y un diseño básico, y ejecutarla en un dispositivo o emulador con Android Studio Debes conocer
ConstraintLayout. Lee el codelab de ConstraintLayout para obtener más información sobreConstraintLayout.
Actividades
- Define una animación con
ConstraintSetsyMotionLayout - Cómo animar en función de eventos de arrastre
- Cómo cambiar la animación con
KeyPosition - Cómo cambiar atributos con
KeyAttribute - Ejecuta animaciones con código
- Anima encabezados contraíbles con
MotionLayout
Requisitos
- Android Studio 4.0 (el editor de
MotionLayoutsolo 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, crearás una animación que mueva una vista desde la parte superior inicial de la pantalla hasta la parte inferior final en respuesta a los clics del usuario.
Para crear una animación a partir del código inicial, necesitarás los siguientes elementos principales:
- Un
MotionLayout,que es una subclase deConstraintLayout. Especificas todas las vistas que se animarán dentro de la etiquetaMotionLayout. - Un
MotionScene,, que es un archivo XML que describe una animación paraMotionLayout. - Un
Transition,que forma parte delMotionSceneque especifica la duración de la animación, el activador y cómo mover las vistas. - Es un
ConstraintSetque especifica las restricciones de inicio y finalización de la transición.
Analicemos cada uno de estos elementos, comenzando por el MotionLayout.
Paso 1: Explora el código existente
MotionLayout es una subclase de ConstraintLayout, por lo que admite las mismas funciones y, además, agrega animación. Para usar MotionLayout, agrega una vista MotionLayout donde usarías ConstraintLayout.
- En
res/layout, abreactivity_step1.xml.. Aquí tienes unConstraintLayoutcon un soloImageViewde una estrella, con un tinte 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 que la estrella se muestra 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 el diseño en Motion Layout
Para animar con MotionLayout,, debes convertir el ConstraintLayout en un MotionLayout.
Para que tu diseño use una escena de movimiento, debe apuntar a ella.
- Para ello, abre la superficie de diseño. En Android Studio 4.0, puedes abrir la superficie de diseño con el ícono de división o diseño en la esquina superior derecha cuando ves un archivo XML de diseño.

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

Esto reemplaza la etiqueta ConstraintLayout por 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">
Un ambiente en movimiento es un solo archivo XML que describe una animación en un MotionLayout.
En cuanto conviertas el diseño en un MotionLayout, la superficie de diseño mostrará Motion Editor.

Hay tres elementos nuevos de la IU en el Editor de movimiento:
- Descripción general: Es una selección modal que te permite elegir diferentes partes de la animación. En esta imagen, se selecciona el
startConstraintSet. También puedes seleccionar la transición entrestartyendhaciendo clic en la flecha que se encuentra entre ellas. - Sección: Debajo de la descripción general, se encuentra una ventana de sección que cambia según el elemento de descripción general seleccionado actualmente. En esta imagen, la información de
startConstraintSetse muestra en la ventana de selección. - Atributo: El panel de atributos muestra los atributos del elemento seleccionado actualmente, ya sea desde la ventana de selección o de resumen, y te permite editarlos. En esta imagen, se muestran los atributos del
startConstraintSet.
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 determinar cómo animar entre el estado inicial y el final (con el tiempo).
MotionScene usa una etiqueta ConstraintSet para definir los estados inicial y final. Una ConstraintSet es lo que parece: un conjunto de restricciones que se pueden aplicar a las vistas. Esto incluye restricciones de ancho, alto y ConstraintLayout. También incluye algunos atributos, como alpha. No contiene las vistas en sí, solo las restricciones de esas vistas.
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 el MotionScene, solo se aplicarán las restricciones del MotionScene.
En este paso, restringirás la vista de estrella para que comience en la parte superior izquierda de la pantalla y termine en la parte inferior derecha.
Puedes completar este paso con el editor de movimiento o editando el texto de activity_step1_scene.xml directamente.
- Selecciona el ConstraintSet
starten el panel de descripción general.

- En el panel de selección, selecciona
red_star. Actualmente, se muestra la fuente delayout, lo que significa que no está restringida en esteConstraintSet. Usa el ícono de lápiz en la esquina superior derecha para Crear restricción.

- Confirma que
red_starmuestre una fuente destartcuando se seleccionastartConstraintSeten el panel de descripción general. - En el panel Attributes, con
red_starseleccionado enstartConstraintSet, agrega una restricción en la parte superior y comienza haciendo clic en los botones azules +.

- Abre
xml/activity_step1_scene.xmlpara ver el código que generó el Editor de movimiento 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>
El ConstraintSet tiene un id de @id/start y especifica todas las restricciones que se aplicarán a todas las vistas en el 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 restricciones e información de diseño. 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 a la parte superior izquierda de su elemento principal.
- Selecciona el ConstraintSet
enden el panel de descripción general.

- Sigue los mismos pasos que antes para agregar un
Constraintparared_staren elendConstraintSet. - Para usar el Editor de movimiento y completar este paso, haz clic en los botones azules de + para agregar una restricción a
bottomyend.

- El código en XML se ve así:
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 los nombres sean @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, desde el principio hasta el final.
Una transición debe especificar un ConstraintSet de inicio y de finalización. Una transición también puede especificar cómo modificar la animación de otras maneras, por ejemplo, cuánto tiempo debe ejecutarse la animación o cómo animar arrastrando vistas.
- De forma predeterminada, Motion Editor creó una transición para nosotros cuando creó el archivo MotionScene. Abre
activity_step1_scene.xmlpara 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 crear una animación. Analicemos cada atributo:
constraintSetStartse aplicará a las vistas cuando comience la animación.constraintSetEndse aplicará a las vistas al final de la animación.durationespecifica 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 período especificado.
Paso 5: Obtén una vista previa de la animación en el Editor de movimiento

Animación: Video de la reproducción de una vista previa de transición en el Editor de movimiento
- Abre el Editor de movimiento y selecciona la transición haciendo clic en la flecha que se encuentra entre
startyenden el panel de descripción general.

- El panel de selección muestra los controles de reproducción y una barra de desplazamiento 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.

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.
- Abre el editor de movimiento y selecciona la transición haciendo clic en la flecha que se encuentra entre el inicio y el final en el panel de descripción general.

- Haz clic en
Create click or swipe handler en la barra de herramientas del panel de descripción general . Esto agrega un controlador que iniciará una transición. - Selecciona Click Handler en la ventana emergente.

- Cambia View To Click a
red_star.

- Haz clic en Agregar. El controlador de clics se representa con un punto pequeño en la transición del Editor de movimiento.

- Con la transición seleccionada en el panel de descripción general, agrega un atributo
clickActiondetoggleal controlador OnClick que acabas de agregar en el panel de atributos.

- Abre
activity_step1_scene.xmlpara 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 los eventos de clic con una etiqueta <OnClick>. Analicemos cada atributo:
targetIdes la vista en la que se observan los clics.clickActiondetogglealternará entre el estado inicial y el final cuando se haga clic. Puedes ver otras opciones paraclickActionen la documentación.
- Ejecuta tu código, haz clic en 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 animación cuando hagas clic en la estrella.

El archivo de ambiente en movimiento completado define un Transition que apunta a un ConstraintSet de inicio y finalización.
Al inicio de la animación (@id/start), el ícono de estrella está restringido a la parte superior izquierda de la pantalla. Al final de la animación (@id/end), el ícono de estrella se restringe al 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. Animación basada en eventos de arrastre
En este paso, crearás una animación que responda a un evento de arrastre del usuario (cuando el usuario desliza el dedo por 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 que el movimiento sea fluido.
Paso 1: Inspecciona el código inicial
- Para comenzar, abre el archivo de diseño
activity_step2.xml, que tiene unMotionLayoutexistente. 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 tres íconos de estrellas no están restringidos en el diseño porque se animarán en la escena de movimiento.
Los créditos TextView sí 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, esta se definirá con un ConstraintSet, inicial y final, y un Transition.
Define el ConstraintSet de inicio
- Abre el ambiente en movimiento
xml/step2.xmlpara definir la animación. - Agrega las restricciones para la restricción inicial
start. Al principio, las tres estrellas se centran en la parte inferior de la pantalla. Las estrellas de la derecha y la izquierda tienen un valor dealphade0.0, lo que significa que son completamente transparentes y están 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, debes especificar un Constraint para cada una de las estrellas. MotionLayout aplicará cada restricción al comienzo de la animación.
Cada vista de estrellas se centra en la parte inferior de la pantalla con restricciones de inicio, finalización y parte inferior. Las dos estrellas @id/left_star y @id/right_star tienen un valor alfa adicional que las hace invisibles y que se aplicará al inicio 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, ya que el nombre start se usa para y, y ambos se usan en el contexto de las restricciones. Para ayudar a distinguir la diferencia, el start en layout_constraintStart hace referencia al "inicio" de la vista, que es la izquierda en un idioma de izquierda a derecha y la derecha en un idioma de derecha a izquierda. El conjunto de restricciones start hace referencia al inicio de la animación.
Define el ConstraintSet final
- Define la restricción final para usar una cadena y posicionar las tres estrellas juntas debajo de
@id/credits. Además, establecerá el valor final dealphade las estrellas izquierda y derecha en1.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 extenderán hacia arriba y hacia afuera desde el centro a medida que se animen.
Además, dado que la propiedad alpha se establece en @id/right_start y @id/left_star en ambos ConstraintSets, ambas vistas se desvanecerán a medida que avance la animación.
Animación basada en el deslizamiento del usuario
MotionLayout puede hacer un seguimiento de los eventos de arrastre del usuario o de un deslizamiento para crear una animación de "lanzamiento" basada en la física. Esto significa que las vistas seguirán desplazándose si el usuario las arroja y se ralentizarán 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.
- Reemplaza el comentario TODO para agregar una etiqueta
OnSwipecon<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.
touchAnchorIdes la vista rastreada que se mueve en respuesta al tacto.MotionLayoutmantendrá esta vista a la misma distancia del dedo que desliza.touchAnchorSidedetermina qué lado de la vista se debe hacer un seguimiento. 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.dragDirectiondetermina qué dirección importa para esta animación (arriba, abajo, izquierda o derecha).
Cuando MotionLayout escucha eventos de arrastre, el objeto de escucha se registrará en la vista MotionLayout y no en la vista especificada por touchAnchorId. Cuando un usuario comienza un gesto en cualquier parte de la pantalla, MotionLayout mantendrá constante la distancia entre su dedo y el touchAnchorSide de la vista touchAnchorId. Si tocan a 100 dp de distancia del lado del anclaje, por ejemplo, MotionLayout mantendrá ese lado a 100 dp de distancia de su dedo durante toda la animación.
Probar
- Vuelve a ejecutar la app y abre la pantalla del paso 2. Verás la animación.
- Intenta "lanzar" o soltar el dedo a mitad de la animación para explorar cómo
MotionLayoutmuestra animaciones fluidas basadas en la física.

MotionLayout puede animar entre diseños muy diferentes usando las funciones de ConstraintLayout para crear efectos enriquecidos.
En esta animación, las tres vistas se posicionan en relación con su elemento principal en la parte inferior de la pantalla para comenzar. Al final, las tres vistas se posicionan en relación con @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 una ruta compleja durante la animación y que anima los créditos durante el movimiento. MotionLayout puede modificar la ruta que tomará una vista entre el inicio y el final con un KeyPosition.
Paso 1: Explora el código existente
- Abre
layout/activity_step3.xmlyxml/step3.xmlpara ver el diseño y la escena de movimiento existentes. UnImageViewy unTextViewmuestran la luna y el texto de créditos. - Abre el archivo de ambiente en movimiento (
xml/step3.xml). Verás que se define unTransitionde@id/starta@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 dosConstraintSets. El texto de los créditos se desvanece dealpha="0.0"aalpha="1.0"a medida que se mueve la luna. - Ejecuta la app ahora y selecciona 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 rutas
Antes de agregar un arco al movimiento de la Luna, es útil habilitar la depuración de la ruta en MotionLayout.
Para ayudarte 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.
- Para habilitar las rutas de depuración, abre
layout/activity_step3.xmly agregamotion:motionDebug="SHOW_PATH"a la etiquetaMotionLayout.
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 rutas, cuando vuelvas a ejecutar la app, verás las rutas de todas las vistas visualizadas con una línea punteada.

- 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
KeyPositionque modifica la ruta.
Por ejemplo, en esta animación, el círculo central es la posición del texto de los créditos.
Paso 3: Modifica una ruta
Todas las animaciones en MotionLayout se definen con un ConstraintSet de inicio y uno de finalización que definen cómo se ve la pantalla antes de que comience la animación y después de que 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.
- Abre
xml/step3.xmly agrega unKeyPositiona la escena. La etiquetaKeyPositionse coloca dentro de la etiquetaTransition.

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.
A medida que MotionLayout calcula la ruta de la Luna entre el inicio y el final, modifica la ruta según el KeyPosition especificado en KeyFrameSet. Puedes ver cómo se modifica la ruta si vuelves a ejecutar la app.
Un KeyPosition tiene varios atributos que describen cómo modifica la ruta de acceso. Los más importantes son los siguientes:
framePositiones un número entre 0 y 100. Define en qué momento de la animación se debe aplicar esteKeyPosition. El valor 1 indica el 1% de la animación y el valor 99 indica el 99% de la animación. Por lo tanto, si el valor es 50, lo aplicas justo en el medio.motionTargetes la vista para la que esteKeyPositionmodifica la ruta de acceso.keyPositionTypees la forma en que esteKeyPositionmodifica la ruta. Puede serparentRelative,pathRelativeodeltaRelative(como se explica en el siguiente paso).percentX | percentYes el valor que indica cuánto se debe modificar la ruta enframePosition(valores entre 0.0 y 1.0, con valores negativos y valores mayores que 1 permitidos).
Puedes pensarlo de esta manera: "En framePosition modifica la ruta de motionTarget moviéndola en percentX o percentY según las coordenadas determinadas por keyPositionType".
De forma predeterminada, MotionLayout redondeará las esquinas que se introduzcan al modificar la ruta de acceso. Si observas la animación que acabas de crear, verás que la luna sigue una trayectoria curva en la curva. Para la mayoría de las animaciones, esto es lo que deseas 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.

La Luna sigue un arco porque atraviesa 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éndola en 50% Y (a mitad de la pantalla) según las coordenadas determinadas por parentRelative (todo el MotionLayout)".
Por lo tanto, a mitad de la animación, la luna debe pasar por un KeyPosition que se encuentra un 50% más abajo en la pantalla. Este KeyPosition no modifica el movimiento en X, por lo que la luna seguirá yendo de principio a fin de forma horizontal. MotionLayout encontrará una ruta fluida que pase por este KeyPosition mientras se desplaza entre el inicio y el final.
Si observas con atención, el texto de los créditos está restringido por la posición de la luna. ¿Por qué no se mueve verticalmente también?

<Constraint
android:id="@id/credits"
...
motion:layout_constraintBottom_toBottomOf="@id/moon"
motion:layout_constraintTop_toTopOf="@id/moon"
/>
Resulta que, aunque modifiques la trayectoria de la luna, las posiciones inicial y final de la luna no la mueven verticalmente en absoluto. KeyPosition no modifica la posición inicial ni la final, por lo que el texto de los créditos se limita a la posición final de la luna.
Si quieres 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. Acerca de 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 según percentX o percentY.
<KeyFrameSet>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
</KeyFrameSet>
Existen tres tipos diferentes de keyPosition posibles: parentRelative, pathRelative y deltaRelative. Si especificas un tipo, cambiará el sistema de coordenadas con el que se calculan percentX y percentY.
¿Qué es un sistema de coordenadas?
Un sistema de coordenadas proporciona una forma 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 valores mayores que 1.0. Por ejemplo, un valor percentX de -2.0 significaría ir en la dirección opuesta del eje X dos veces.
Si todo esto te suena demasiado a clase de álgebra, consulta las imágenes a continuación.
Coordenadas relativas al elemento principal

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 cuando quieras crear una animación que se mueva por todo el MotionLayout, como el arco de la luna en este ejemplo.
Sin embargo, si quieres modificar una ruta en relación con el movimiento, por ejemplo, hacer que se curve un poco, los otros dos sistemas de coordenadas son una mejor opción.
Coordenadas delta relativas

Delta es un término matemático que significa cambio, por lo que deltaRelative es una forma de decir "cambio relativo". En coordenadas de 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, y el eje Y siempre es vertical en la pantalla. 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) en un 50% y continúe animándose horizontalmente (X).
pathRelative coordinates

El último sistema de coordenadas en 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 hacer esto? A primera vista, es bastante sorprendente, sobre todo 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á exactamente con la ruta que toma la vista, puedes usar un
pathRelativeKeyPositionpara cambiar elframePositionen el que se alcanza un punto determinado de esa ruta. Por lo tanto, unKeyPositionenframePosition="50"con unpercentX="0.1"haría que la animación tardara el 50% del tiempo en recorrer el primer 10% del movimiento. - Agregar un arco sutil a una ruta de acceso Dado que la dimensión Y siempre es perpendicular al movimiento, cambiar Y modificará la ruta para que se curve en relación con el movimiento general.
- Agregar una segunda dimensión cuando
deltaRelativeno funcionará. Para el movimiento completamente horizontal y vertical,deltaRelativesolo creará una dimensión útil. Sin embargo,pathRelativesiempre creará coordenadas X e Y utilizables.
En el siguiente paso, aprenderás a crear rutas aún más complejas con más de un KeyPosition.
7. Cómo crear rutas complejas
Si observas la animación que creaste en el último paso, verás que crea una curva suave, pero la forma podría ser más parecida a la de la Luna.
Cómo modificar una ruta con varios elementos KeyPosition
MotionLayout puede modificar aún más una ruta definiendo tantos KeyPosition como sean necesarios para obtener cualquier movimiento. Para esta animación, crearás un arco, pero podrías hacer que la luna salte hacia arriba y hacia abajo en el centro de la pantalla, si quisieras.
- Abre
xml/step4.xml. Verás que tiene las mismas vistas y elKeyFrameque agregaste en el último paso. - Para redondear la parte superior de la curva, agrega dos
KeyPositionsmás a la ruta de@id/moon, uno justo antes de que llegue a la parte superior y otro después.

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 en el 25% y el 75% del recorrido de la animación, y harán que @id/moon se mueva por una ruta que se encuentra a un 60% de la parte superior de la pantalla. En combinación con el KeyPosition existente del 50%, esto crea un arco suave para que siga la luna.
En MotionLayout, puedes agregar tantos KeyPositions como necesites para obtener la ruta de movimiento que desees. MotionLayout aplicará cada KeyPosition en el framePosition especificado y determinará cómo crear un movimiento fluido que pase por todos los KeyPositions.
Probar
- Vuelve a ejecutar la app. Ve al paso 4 para ver la animación en acción. Cuando haces clic en la luna, esta sigue la ruta desde el inicio hasta el final, pasando por cada
KeyPositionque se especificó enKeyFrameSet.
Explora por tu cuenta
Antes de pasar a otros tipos de KeyFrame, intenta agregar más KeyPositions al KeyFrameSet para ver qué tipo de efectos puedes crear solo con KeyPosition.
Este es un ejemplo que muestra cómo compilar una ruta compleja que se mueve hacia adelante y hacia atrás durante la animación.

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 los atributos durante el movimiento
Crear animaciones dinámicas a menudo significa cambiar los valores de 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 con un KeyAttribute.
En este paso, usarás KeyAttribute para hacer que la luna se escale y rote. También usarás un KeyAttribute para retrasar la aparición del texto hasta que la luna casi haya completado su recorrido.
Paso 1: Cambia el tamaño y rota con KeyAttribute
- Abre
xml/step5.xml, que contiene la misma animación que creaste en el paso anterior. Para variar, esta pantalla usa una imagen del espacio diferente como fondo. - Para que la luna se expanda y rote, agrega dos etiquetas
KeyAttributeenKeyFrameSetenkeyFrame="50"ykeyFrame="100".

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 en el 50% y el 100% de la animación. El primer KeyAttribute al 50% ocurrirá en la parte superior del arco y hará que la vista se duplique en tamaño y gire -360 grados (o un círculo completo). El segundo KeyAttribute completará la segunda rotación a -720 grados (dos círculos completos) y reducirá el tamaño a la normalidad, ya que los valores scaleX y scaleY tienen el valor predeterminado 1.0.
Al igual que un KeyPosition, un KeyAttribute usa framePosition y motionTarget para especificar cuándo aplicar el KeyFrame y qué vista modificar. MotionLayout interpolará entre KeyPositions para crear animaciones fluidas.
Atributos compatibles con KeyAttributes 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, ajustar 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é casi completa.
- Para retrasar la aparición de los créditos, define un
KeyAttributemás que garantice quealphapermanecerá en 0 hastakeyPosition="85".MotionLayoutseguirá realizando una transición fluida 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 con un valor alfa de 0, esto significa que 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 coordinados con la luna que se asienta en la esquina derecha de la pantalla.
Si retrasas las animaciones en una vista mientras otra se mueve de esta manera, puedes crear animaciones impresionantes que se sientan dinámicas para el usuario.
Probar
- 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 desde el inicio hasta el final, pasando por cada
KeyAttributeque se especificó en elKeyFrameSet.

Como rotas la luna dos círculos completos, ahora hará un doble salto hacia atrás, y los créditos retrasarán su aparición hasta que la animación esté casi terminada.
Explora por tu cuenta
Antes de pasar al tipo final de KeyFrame, intenta modificar otros atributos estándares en el KeyAttributes. Por ejemplo, intenta cambiar rotation por rotationX para ver qué animación produce.
A continuación, se incluye una lista de los atributos estándar que puedes probar:
android:visibilityandroid:alphaandroid:elevationandroid:rotationandroid:rotationXandroid:rotationYandroid:scaleXandroid:scaleYandroid:translationXandroid:translationYandroid:translationZ
9. Cómo cambiar atributos personalizados
Las animaciones enriquecidas implican cambiar el color o algún otro atributo de una vista. Si bien MotionLayout puede usar un KeyAttribute para cambiar cualquiera de los atributos estándares que se enumeran en la tarea anterior, debes usar un CustomAttribute para especificar cualquier otro atributo.
Se puede usar un CustomAttribute para establecer cualquier valor que tenga un setter. Por ejemplo, puedes establecer el backgroundColor en una View con un CustomAttribute. MotionLayout usará la reflexión para encontrar el setter y, luego, lo llamará repetidamente para animar la vista.
En este paso, usarás un CustomAttribute para establecer el atributo colorFilter en la luna y compilar la animación que se muestra a continuación.

Cómo definir atributos personalizados
- Para comenzar, abre
xml/step6.xml, que contiene la misma animación que creaste en el último paso. - Para que la luna cambie de color, agrega dos
KeyAttributecon unCustomAttributeen elKeyFrameSetenkeyFrame="0",keyFrame="50"ykeyFrame="100"..

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>
Agrega un CustomAttribute dentro de un KeyAttribute. El CustomAttribute se aplicará en el framePosition especificado por el KeyAttribute.
Dentro de CustomAttribute, debes especificar un attributeName y un valor para establecer.
motion:attributeNamees el nombre del método setter que llamará este atributo personalizado. En este ejemplo, se llamará asetColorFilterenDrawable.motion:custom*Valuees 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 elemento que proporcione un setter en cualquier vista.
Probar
- 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 desde el inicio hasta el final, pasando por cada
KeyAttributeque se especificó en elKeyFrameSet.

Cuando agregas más KeyFrames, MotionLayout cambia la trayectoria de la luna de una línea recta a una curva compleja, y agrega un doble salto hacia atrás, un cambio de tamaño y un cambio de color a mitad de la animación.
En las animaciones reales, a menudo animarás varias vistas al mismo tiempo y controlarás su movimiento a lo largo de diferentes rutas y velocidades. Si especificas un KeyFrame diferente para cada vista, es posible coreografiar animaciones enriquecidas que animen 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, la animación de la luna se activó con un objeto de escucha OnClick y se ejecuta durante un período fijo.
Controlar animaciones que tienen rutas complejas con OnSwipe, como la animación de la luna que compilaste en los últimos pasos, requiere comprender cómo funciona OnSwipe.
Paso 1: Explora el comportamiento de OnSwipe
- Abre
xml/step7.xmly busca la declaraciónOnSwipeexistente.
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
/>
- Ejecuta la app en tu dispositivo y ve al paso 7. Intenta producir una animación fluida arrastrando la luna a lo largo de la trayectoria del arco.
Cuando ejecutas esta animación, no se ve muy bien. Después de que la luna alcanza la parte superior del arco, comienza a saltar.

Para comprender el error, considera lo que sucede cuando el usuario toca justo debajo de la parte superior del arco. Dado que la etiqueta OnSwipe tiene un motion:touchAnchorSide="bottom", MotionLayout intentará mantener constante la distancia entre el dedo y la parte inferior de la vista durante toda la animación.
Sin embargo, como la parte inferior de la luna no siempre va en la misma dirección, sino que sube y luego baja, MotionLayout no sabe qué hacer cuando el usuario acaba de pasar la parte superior del arco. Para tener en cuenta esto, ya que haces un seguimiento de la parte inferior de la luna, ¿dónde debería colocarse cuando el usuario toca aquí?

Paso 2: Usa el lado derecho
Para evitar errores como este, es importante elegir siempre un touchAnchorId y un touchAnchorSide que siempre avancen en una dirección durante toda la animación.
En esta animación, tanto el lado right como el lado left de la Luna avanzarán por la pantalla en una dirección.
Sin embargo, tanto bottom como top invertirán su dirección. Cuando OnSwipe intente hacer un seguimiento de ellos, se confundirá cuando cambien de dirección.
- Para que esta animación siga los eventos táctiles, cambia
touchAnchorSideporright.
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="right"
/>
Paso 3: Usa dragDirection
También puedes combinar dragDirection con touchAnchorSide para que una vía secundaria tome una dirección diferente a la habitual. Aun así, es importante que touchAnchorSide solo avance en una dirección, pero puedes indicarle a MotionLayout en qué dirección debe hacer el seguimiento. Por ejemplo, puedes conservar touchAnchorSide="bottom", pero agregar dragDirection="dragRight". Esto hará que MotionLayout haga un seguimiento de la posición de la parte inferior de la vista, pero solo tendrá 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, seguirá animándose correctamente con OnSwipe.
- Se actualizó
OnSwipepara hacer 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
- Vuelve a ejecutar la app y trata de arrastrar la luna por todo el recorrido. Aunque sigue un arco complejo,
MotionLayoutpodrá avanzar la animación en respuesta a los eventos de deslizamiento.

11. Ejecuta el movimiento con código
MotionLayout se puede usar para crear animaciones enriquecidas cuando se usa con CoordinatorLayout. En este paso, compilarás un encabezado plegable con MotionLayout.
Paso 1: Explora el código existente
- Para comenzar, abre
layout/activity_step8.xml. - En
layout/activity_step8.xml, verás que ya se compilaron unCoordinatorLayouty unAppBarLayoutque funcionan.
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 el NestedScrollView y el AppBarLayout. Por lo tanto, cuando el NestedScrollView se desplace hacia arriba, le indicará el cambio al AppBarLayout. Así es como se implementa una barra de herramientas contraíble como esta en Android: el desplazamiento del texto se "coordinará" con el encabezado contraíble.
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 que funcione con CoordinatorLayout.
- Ejecuta la app y ve al paso 8. Cuando desplazas el texto, la Luna no se mueve.
Paso 2: Haz que se desplace el MotionLayout
- Para que la vista
MotionLayoutse desplace en cuanto se desplaceNestedScrollView, agregamotion:minHeightymotion:layout_scrollFlagsaMotionLayout.
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" >
- Vuelve a ejecutar la app y ve al paso 8. Verás que
MotionLayoutse contrae a medida que te desplazas hacia arriba. Sin embargo, la animación aún no avanza según el comportamiento de desplazamiento.
Paso 3: Mueve el movimiento con código
- Abre
Step8Activity.kt. Edita la funcióncoordinateMotion()para informarle aMotionLayoutsobre 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 configurando la propiedad de progreso. Para convertir entre un verticalOffset y un porcentaje de progreso, divide por el intervalo de desplazamiento total.
Probar
- Vuelve a implementar la app y ejecuta la animación del paso 8. Verás que
MotionLayoutavanzará la animación según la posición de desplazamiento.

Es posible compilar animaciones personalizadas dinámicas de la barra de herramientas contraíble con MotionLayout. Si usas una secuencia de KeyFrames, puedes lograr efectos muy llamativos.
12. Felicitaciones
En este codelab, se abarcó la API básica de MotionLayout.
Para ver más ejemplos de MotionLayout en la práctica, consulta el ejemplo oficial. Y no olvides consultar la documentación.
Más información
MotionLayout admite aún más funciones que no se abordan en este codelab, como KeyCycle,, que te permite controlar rutas o atributos con ciclos repetitivos, y KeyTimeCycle,, que te permite crear animaciones basadas en 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 los codelabs de Aspectos avanzados de Android en Kotlin.