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 sobreConstraintLayout
.
Actividades
- Cómo definir una animación con
ConstraintSets
yMotionLayout
- 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 deConstraintLayout
. Debes especificar todas las vistas que se animarán dentro de la etiquetaMotionLayout
. - Un
MotionScene,
, que es un archivo en formato XML que describe una animación paraMotionLayout.
- Un
Transition,
que forma parte deMotionScene
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.
- En
res/layout
, abreactivity_step1.xml.
. Aquí tienes unConstraintLayout
con un soloImageView
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.
- 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.
- 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.
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.
Hay tres elementos nuevos de la IU en el Editor de animaciones:
- 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 entrestart
yend
haciendo clic en la flecha entre ellos. - 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. - 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
destart
.
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
.
- Selecciona el ConstraintSet
start
en el panel de descripción general
- En el panel de selección, selecciona
red_star
. Actualmente, muestra la fuente delayout
, lo que significa que no está restringido en esteConstraintSet
. Usa el ícono de lápiz en la esquina superior derecha para Crear restricción
- Confirma que
red_star
muestre una fuente destart
cuando se seleccionestart
ConstraintSet
en el panel de descripción general. - En el panel Attributes, con
red_star
seleccionado enstart
ConstraintSet
, agrega una restricción en la parte superior y comienza haciendo clic en los botones azules +.
- 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.
- Selecciona el ConstraintSet
end
en el panel de descripción general.
- Sigue los mismos pasos que antes para agregar un
Constraint
parared_star
enend
ConstraintSet
. - Si deseas usar el Editor de animaciones para completar este paso, agrega una restricción a
bottom
yend
haciendo clic en los botones azules +.
- 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.
- 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
Animación: Video en el que se reproduce una vista previa de transición en el Editor de animaciones.
- Para abrir Motion Editor, haz clic en la flecha entre
start
yend
en el panel de descripción general para seleccionar la transición.
- 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.
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 haz clic en la flecha entre el inicio y el final en el panel de información general para seleccionar la transición.
- Haz clic en 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.
- Selecciona Control de clics en la ventana emergente.
- Cambie el valor de View To Click a
red_star
.
- Haz clic en Add. El controlador de clics se representa con un punto pequeño en Transition en Motion Editor.
- Con la transición seleccionada en el panel de descripción general, agrega un atributo
clickAction
detoggle
al controlador OnClick que acabas de agregar en el panel de atributos.
- 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
detoggle
cambiará entre el estado inicial y el final cuando se haga clic en él. Puedes ver otras opciones paraclickAction
en la documentación.
- 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.
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
- Para comenzar, abre el archivo de diseño
activity_step2.xml
, que tiene unMotionLayout
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
- Abre la escena de movimiento
xml/step2.xml
para definir la animación. - 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 valoralpha
de0.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
- 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 lasalpha
de las estrellas izquierdas y derechas 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 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
.
- 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
- Vuelve a ejecutar la app y abre la pantalla del paso 2. Verás la animación.
- 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.
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
- Abre
layout/activity_step3.xml
yxml/step3.xml
para ver el diseño y la escena de movimiento existentes. Los elementosImageView
yTextView
muestran la luna y el texto de los créditos. - Abre el archivo de escena en movimiento (
xml/step3.xml
). Verás que se definió unTransition
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 dosConstraintSets
. El texto de créditos se atenúa dealpha="0.0"
aalpha="1.0"
a medida que se mueve la Luna. - 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.
- Para habilitar las rutas de depuración, abre
layout/activity_step3.xml
y 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 ruta, cuando vuelvas a ejecutar la app, verás las rutas de todas las vistas visualizadas con una línea de puntos.
- 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.
- Abre
xml/step3.xml
y agrega unKeyPosition
a la escena. La etiquetaKeyPosition
se 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.
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 esteKeyPosition
, 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 esteKeyPosition
modifica la ruta de acceso.keyPositionType
es la forma en que esteKeyPosition
modifica la ruta. Puede serparentRelative
,pathRelative
odeltaRelative
(como se explica en el siguiente paso).percentX | percentY
es la medida en la que se debe modificar la ruta enframePosition
(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.
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?
<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
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
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
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, unaKeyPosition
enframePosition="50"
con unpercentX="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.
- Abre
xml/step4.xml
. Verás que tiene las mismas vistas y elKeyFrame
que agregaste en el último paso. - 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.
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
- 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 elKeyFrameSet
.
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.
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
- 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. - Para que la Luna se expanda y rote, agrega dos etiquetas
KeyAttribute
enKeyFrameSet
, enkeyFrame="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 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.
- Para retrasar la aparición de los créditos, define un
KeyAttribute
más que garantice quealpha
permanezca en 0 hastakeyPosition="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
- 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 elKeyFrameSet
.
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.
Define atributos personalizados
- Para comenzar, abre
xml/step6.xml
, que contiene la misma animación que compilaste en el último paso. - Para hacer que la luna cambie de color, agrega dos
KeyAttribute
conCustomAttribute
enKeyFrameSet
enkeyFrame="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>
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á asetColorFilter
enDrawable
.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
- 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 elKeyFrameSet
.
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
- Abre
xml/step7.xml
y busca la declaraciónOnSwipe
existente.
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. 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.
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í?
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.
- Para que esta animación siga los eventos táctiles, cambia
touchAnchorSide
aright
.
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
.
- 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
- 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.
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
- Para comenzar, abre
layout/activity_step8.xml
. - En
layout/activity_step8.xml
, verás que ya se compiló unCoordinatorLayout
y unAppBarLayout
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
.
- 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
- Para que la vista
MotionLayout
se desplace en cuanto se desplaceNestedScrollView
, agregamotion:minHeight
ymotion:layout_scrollFlags
aMotionLayout
.
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
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
- Abre
Step8Activity.kt
. Edita la funcióncoordinateMotion()
para indicarle aMotionLayout
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
- 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.
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.