Cómo administrar animaciones de movimiento y widgets con MotionLayout

Prueba hacerlo con Compose
Jetpack Compose es el kit de herramientas de IU recomendado para Android. Obtén información para usar animaciones en Compose.

MotionLayout es un tipo de diseño que te ayuda a administrar las animaciones de movimiento y widgets en tu app. MotionLayout es una subclase de ConstraintLayout y se basa en sus capacidades de diseño enriquecido. Como parte de la biblioteca de ConstraintLayout, MotionLayout está disponible como biblioteca de compatibilidad.

MotionLayout reduce las diferencias entre las transiciones de diseño y el manejo de movimiento complejo, lo que ofrece una combinación de características entre el marco de animación de propiedades,TransitionManager, yCoordinatorLayout.

Figura 1. Movimiento básico controlado por tacto

Además de describir las transiciones entre diseños, MotionLayout también te permite animar cualquier propiedad de diseño. Además, es inherentemente compatible con las transiciones que admiten búsquedas. Esto significa que puedes mostrar de forma instantánea cualquier punto dentro de la transición en función de alguna condición, como la entrada táctil. MotionLayout también admite fotogramas clave, lo que habilita el uso de transiciones totalmente personalizadas para satisfacer tus necesidades.

MotionLayout es totalmente declarativo, lo que significa que puedes describir cualquier transición en XML, independientemente de su complejidad.

Consideraciones del diseño

MotionLayout está diseñado para mover, cambiar de tamaño y animar elementos de la IU con los que interactúan los usuarios, como botones y barras de título. No uses el movimiento en tu app como un efecto especial injustificado. Úsalo para ayudar a los usuarios a comprender lo que hace tu app. Para obtener más información sobre cómo diseñar una app con movimiento, consulta la sección Material Design en Información sobre el movimiento.

Comenzar

Sigue estos pasos para comenzar a usar MotionLayout en tu proyecto.

  1. Agrega la dependencia de ConstraintLayout: Para usar MotionLayout en tu proyecto, agrega la ConstraintLayout 2.0 dependencia al archivo build.gradle de tu app. Si usas AndroidX, agrega la siguiente dependencia:

    Groovy

    dependencies {
        implementation "androidx.constraintlayout:constraintlayout:2.2.1"
        // To use constraintlayout in compose
        implementation "androidx.constraintlayout:constraintlayout-compose:1.1.1"
    }

    Kotlin

    dependencies {
        implementation("androidx.constraintlayout:constraintlayout:2.2.1")
        // To use constraintlayout in compose
        implementation("androidx.constraintlayout:constraintlayout-compose:1.1.1")
    }
  2. Crea un archivo MotionLayout: MotionLayout es una subclase de ConstraintLayout, de modo que puedes transformar cualquier ConstraintLayout existente en un MotionLayout si reemplazas el nombre de la clase en tu archivo de recursos de diseño, como se muestra en los siguientes ejemplos:

    AndroidX

    <!-- before: ConstraintLayout -->
    <androidx.constraintlayout.widget.ConstraintLayout .../>
    <!-- after: MotionLayout -->
    <androidx.constraintlayout.motion.widget.MotionLayout .../>
              

    Biblioteca de compatibilidad

    <!-- before: ConstraintLayout -->
    <android.support.constraint.ConstraintLayout .../>
    <!-- after: MotionLayout -->
    <android.support.constraint.motion.MotionLayout .../>
              

    Este es un ejemplo completo de un archivo MotionLayout, que define el diseño que se muestra en la figura 1:

    AndroidX

    <?xml version="1.0" encoding="utf-8"?>
    <!-- activity_main.xml -->
    <androidx.constraintlayout.motion.widget.MotionLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/motionLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutDescription="@xml/scene_01"
        tools:showPaths="true">
    
        <View
            android:id="@+id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:background="@color/colorAccent"
            android:text="Button" />
    
    </androidx.constraintlayout.motion.widget.MotionLayout>
            

    Biblioteca de compatibilidad

    <?xml version="1.0" encoding="utf-8"?>
    <!-- activity_main.xml -->
    <android.support.constraint.motion.MotionLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/motionLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutDescription="@xml/scene_01"
        tools:showPaths="true">
    
        <View
            android:id="@+id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:background="@color/colorAccent"
            android:text="Button" />
    
    </android.support.constraint.motion.MotionLayout>
            
  3. Crea un archivo MotionScene: En el ejemplo de MotionLayout anterior, el atributo app:layoutDescription hace referencia a un ambiente en movimiento. Un ambiente en movimiento es un archivo XML de recursos. Dentro de su <MotionScene> elemento raíz, un ambiente en movimiento contiene todas las descripciones de movimientos para el diseño correspondiente. Para mantener la información de diseño separada de las descripciones de movimientos, cada MotionLayout hace referencia a un ambiente en movimiento separado. Las definiciones del ambiente en movimiento tienen prioridad sobre cualquier definición similar de MotionLayout.

    El siguiente es un ejemplo de archivo MotionScene en el que se describe el movimiento horizontal básico en la figura 1:

    <?xml version="1.0" encoding="utf-8"?>
    <MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:motion="http://schemas.android.com/apk/res-auto">
    
        <Transition
            motion:constraintSetStart="@+id/start"
            motion:constraintSetEnd="@+id/end"
            motion:duration="1000">
            <OnSwipe
                motion:touchAnchorId="@+id/button"
                motion:touchAnchorSide="right"
                motion:dragDirection="dragRight" />
        </Transition>
    
        <ConstraintSet android:id="@+id/start">
            <Constraint
                android:id="@+id/button"
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:layout_marginStart="8dp"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintStart_toStartOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </ConstraintSet>
    
        <ConstraintSet android:id="@+id/end">
            <Constraint
                android:id="@+id/button"
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:layout_marginEnd="8dp"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintEnd_toEndOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </ConstraintSet>
    
    </MotionScene>
        

    Ten en cuenta lo siguiente:

    • <Transition> contiene la definición básica del movimiento.

      • motion:constraintSetStart y motion:constraintSetEnd son referencias a los extremos del movimiento. Se incluye la definición de estos extremos en los <ConstraintSet> elementos más adelante, en el ambiente en movimiento.

      • motion:duration especifica la cantidad de milisegundos que tarda en completarse el movimiento.

    • <OnSwipe> te permite crear controles de tacto para el movimiento.

      • motion:touchAnchorId hace referencia a la vista que el usuario puede deslizar y arrastrar.

      • motion:touchAnchorSide significa que se arrastra la vista desde el lado derecho.

      • motion:dragDirection hace referencia a la dirección de progreso del arrastre. Por ejemplo, motion:dragDirection="dragRight" indica que el progreso aumenta a medida que se arrastra la vista hacia la derecha.

    • <ConstraintSet> es donde debes definir las diversas restricciones que describen tu movimiento. En este ejemplo, se define un <ConstraintSet> para cada extremo de tu movimiento. Estos extremos están centrados verticalmente usando app:layout_constraintTop_toTopOf="parent" y app:layout_constraintBottom_toBottomOf="parent". De manera horizontal, los extremos están en los límites izquierdo y derecho de la pantalla.

    Para obtener una vista más detallada de los diversos elementos compatibles con un ambiente en movimiento, consulta los ejemplos de MotionLayout.

Atributos interpolados

Dentro de un archivo de ambiente en movimiento, los elementos ConstraintSet pueden contener atributos adicionales que se interpolan durante la transición. Además de la posición y los límites, MotionLayout interpola los siguientes atributos:

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

Atributos personalizados

Dentro de un <Constraint>, puedes usar el elemento <CustomAttribute> para especificar una transición para los atributos que no están simplemente relacionados con la posición o los atributos View.

<Constraint
    android:id="@+id/button" ...>
    <CustomAttribute
        motion:attributeName="backgroundColor"
        motion:customColorValue="#D81B60"/>
</Constraint>

Un <CustomAttribute> contiene dos atributos propios:

  • motion:attributeName es obligatorio y debe coincidir con un objeto que tenga métodos get y set. El método get y el método set deben coincidir con un patrón específico. Por ejemplo, se admite backgroundColor, ya que la vista tiene métodos getBackgroundColor() y setBackgroundColor() subyacentes.
  • El otro atributo que debes proporcionar se basa en el tipo de valor. Elige entre los siguientes tipos admitidos:
    • motion:customColorValue para los colores
    • motion:customIntegerValue para números enteros
    • motion:customFloatValue para valores flotantes
    • motion:customStringValue para strings
    • motion:customDimension para dimensiones
    • motion:customBoolean para valores booleanos

Cuando especificas un atributo personalizado, debes definir valores de extremo en los elementos de inicio y de finalización <ConstraintSet>.

Cambiar el color del fondo

Basándonos en el ejemplo anterior, supongamos que quieres que los colores de la vista cambien como parte de su movimiento, como se muestra en la figura 2.

Figura 2. La vista cambia el color de fondo a medida que se mueve

Agrega un elemento <CustomAttribute> a cada elemento ConstraintSet, como se muestra en el siguiente fragmento de código:

<ConstraintSet android:id="@+id/start">
    <Constraint
        android:id="@+id/button"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_marginStart="8dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintStart_toStartOf="parent"
        motion:layout_constraintTop_toTopOf="parent">
        <CustomAttribute
            motion:attributeName="backgroundColor"
            motion:customColorValue="#D81B60" />
    </Constraint>
</ConstraintSet>

<ConstraintSet android:id="@+id/end">
    <Constraint
        android:id="@+id/button"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_marginEnd="8dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintEnd_toEndOf="parent"
        motion:layout_constraintTop_toTopOf="parent">
        <CustomAttribute
            motion:attributeName="backgroundColor"
            motion:customColorValue="#9999FF" />
    </Constraint>
</ConstraintSet>

Atributos adicionales de MotionLayout

Además de los atributos del ejemplo anterior, MotionLayout tiene otros atributos que quizás quieras especificar:

  • app:applyMotionScene="boolean" indica si se debe aplicar el ambiente en movimiento. El valor predeterminado para este atributo es true.
  • app:showPaths="boolean" indica si se muestran las rutas de movimiento mientras se ejecuta el movimiento. El valor predeterminado para este atributo es false.
  • app:progress="float" te permite especificar explícitamente el progreso de la transición. Puedes usar cualquier valor de punto flotante desde 0 (el inicio de la transición) hasta 1 (el final de la transición).
  • app:currentState="reference" te permite especificar un ConstraintSet específico.
  • app:motionDebug te permite mostrar información adicional de depuración sobre el movimiento. Los valores posibles son "SHOW_PROGRESS", "SHOW_PATH" o "SHOW_ALL".

Recursos adicionales

Para obtener más información sobre MotionLayout, consulta los siguientes recursos: