Gerenciar movimento e animação de widget com o MotionLayout

MotionLayout é um tipo de layout que ajuda a gerenciar movimento e animação de widgets no seu app. O MotionLayout é uma subclasse de ConstraintLayout e aproveita os recursos avançados de layout. Como parte da biblioteca ConstraintLayout, MotionLayout está disponível como uma biblioteca de suporte.

O MotionLayout preenche a lacuna entre as transições de layout e o processamento complexo de movimentos, oferecendo uma combinação de recursos entre o framework de animação de propriedade, TransitionManager e CoordinatorLayout.

Figura 1. Movimento básico controlado por toque.

Além de descrever transições entre layouts, o MotionLayout permite animar qualquer propriedade de layout. Além disso, ele oferece suporte inerente a transições pesquisáveis. Isso significa que você pode mostrar instantaneamente qualquer ponto dentro da transição com base em alguma condição, por exemplo, a entrada por toque. O MotionLayout também é compatível com frames-chave, permitindo transições totalmente personalizadas para atender às suas necessidades.

MotionLayout é totalmente declarativo, o que significa que você pode descrever qualquer transição em XML, não importa a complexidade.

Considerações sobre o design

O MotionLayout destina-se a mover, redimensionar e animar elementos de IU com que os usuários interagem, como botões e barras de título. Não use movimento no app como um efeito especial desnecessário. Use-o para ajudar os usuários a entender o que o app está fazendo. Para saber mais sobre como projetar seu app com movimento, consulte a seção do Material Design Como entender o movimento.

Primeiros passos

Siga estas etapas para começar a usar o MotionLayout no seu projeto.

  1. Adicione a dependência ConstraintLayout:para usar MotionLayout no projeto, adicione a dependência ConstraintLayout 2.0 ao arquivo build.gradle do app. Se você estiver usando o AndroidX, adicione a seguinte dependência:

    Groovy

    dependencies {
        implementation "androidx.constraintlayout:constraintlayout:2.2.0-beta01"
        // To use constraintlayout in compose
        implementation "androidx.constraintlayout:constraintlayout-compose:1.1.0-beta01"
    }
    

    Kotlin

    dependencies {
        implementation("androidx.constraintlayout:constraintlayout:2.2.0-beta01")
        // To use constraintlayout in compose
        implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-beta01")
    }
    
  2. Crie um arquivo MotionLayout: MotionLayout é uma subclasse de ConstraintLayout, então você pode transformar qualquer ConstraintLayout existente em um MotionLayout, substituindo o nome da classe no seu arquivo de recurso de layout, conforme mostrado nos exemplos a seguir:

    AndroidX

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

    Biblioteca de Suporte

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

    Confira um exemplo completo de um arquivo MotionLayout, que define o layout mostrado na 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 Suporte

    <?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. Criar um MotionScene:no exemplo anterior de MotionLayout, o atributo app:layoutDescription faz referência a um MotionScene. Uma cena em movimento é um arquivo de recurso XML. No elemento raiz <MotionScene>, uma cena de movimento contém todas as descrições de movimento para o layout correspondente. Para manter as informações de layout separadas das descrições de movimento, cada MotionLayout faz referência a uma cena de movimento separada. As definições na cena de movimento têm precedência sobre definições semelhantes no MotionLayout.

    Confira um exemplo de arquivo de cena em movimento que descreve o movimento horizontal básico na 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>
        

    Observe o seguinte:

    • <Transition> contém a definição básica do movimento.

      • motion:constraintSetStart e motion:constraintSetEnd são referências aos endpoints da movimentação. Esses endpoints são definidos nos elementos <ConstraintSet> mais tarde na cena de movimento.

      • motion:duration especifica o número de milissegundos necessários para que a animação seja concluída.

    • <OnSwipe> permite criar um controle por toque para o movimento.

      • motion:touchAnchorId se refere à visualização que o usuário pode deslizar e arrastar.

      • motion:touchAnchorSide significa que a visualização está sendo arrastada do lado direito.

      • motion:dragDirection se refere à direção do avanço da ação de arrastar. Por exemplo, motion:dragDirection="dragRight" significa que o progresso aumenta conforme a visualização é arrastada para a direita.

    • <ConstraintSet> é onde você define as várias restrições que descrevem seu movimento. Neste exemplo, um <ConstraintSet> é definido para cada endpoint do movimento. Esses endpoints são centralizados verticalmente usando app:layout_constraintTop_toTopOf="parent" e app:layout_constraintBottom_toBottomOf="parent". Horizontalmente, os endpoints estão nas extremidades esquerda e direita da tela.

    Para ter uma visão mais detalhada dos diversos elementos compatíveis com uma cena de movimento, consulte os Exemplos do MotionLayout.

Atributos interpolados

Em um arquivo de cena em movimento, os elementos ConstraintSet podem conter outros atributos que são interpolados durante a transição. Além da posição e dos limites, os seguintes atributos são interpolados pelo MotionLayout:

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

Atributos personalizados

Em um <Constraint>, você pode usar o elemento <CustomAttribute> para especificar uma transição para atributos que não estão simplesmente relacionados à posição ou aos atributos View.

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

Um <CustomAttribute> contém dois atributos próprios:

  • motion:attributeName: é obrigatório e precisa corresponder a um objeto com métodos getter e setter. O getter e o setter precisam corresponder a um padrão específico. Por exemplo, backgroundColor é compatível, porque a visualização tem os métodos getBackgroundColor() e setBackgroundColor().
  • O outro atributo que você precisa fornecer é baseado no tipo de valor. Escolha um dos seguintes tipos compatíveis:
    • motion:customColorValue para cores
    • motion:customIntegerValue para números inteiros
    • motion:customFloatValue para flutuantes
    • motion:customStringValue para strings
    • motion:customDimension para dimensões
    • motion:customBoolean para booleanos

Ao especificar um atributo personalizado, defina valores de endpoint nos elementos inicial e final do <ConstraintSet>.

Mudar cor do plano de fundo

Com base no exemplo anterior, suponha que você queira que as cores da visualização mudem como parte do movimento, conforme mostrado na Figura 2.

Figura 2. A visualização muda a cor de fundo à medida que se move.

Adicione um elemento <CustomAttribute> a cada elemento ConstraintSet, conforme mostrado no sequência de comandos abaixo:

<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 adicionais do MotionLayout

Além dos atributos no exemplo anterior, o MotionLayout tem outros atributos que você pode especificar:

  • app:applyMotionScene="boolean": indica se a cena de movimento será aplicada. O valor padrão para este atributo é true.
  • app:showPaths="boolean" indica se as trajetórias de animação serão mostradas enquanto o movimento está em execução. O valor padrão para este atributo é false.
  • app:progress="float": permite que você especifique explicitamente o andamento da transição. Você pode usar qualquer valor de ponto flutuante de 0 (o início da transição) a 1 (o final da transição).
  • app:currentState="reference": permite que você especifique um ConstraintSet.
  • app:motionDebug: permite exibir informações de depuração adicionais sobre o movimento. Os valores possíveis são "SHOW_PROGRESS", "SHOW_PATH" ou "SHOW_ALL".

Outros recursos

Para mais informações sobre MotionLayout, consulte os seguintes recursos: