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

O MotionLayout é um tipo de layout que ajuda a gerenciar movimentos e animações de widget no app. A MotionLayout é uma subclasse de ConstraintLayout e se baseia nos recursos avançados de layout. Como parte da biblioteca ConstraintLayout, MotionLayout está disponível como uma biblioteca de suporte.

A MotionLayout preenche a lacuna entre as transições de layout e o processamento de movimentos complexos, 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 todas as propriedades de layout. Além disso, ele é inerentemente compatível com transições procuráveis. Isso significa que você pode mostrar instantaneamente qualquer ponto dentro da transição com base em alguma condição, como a entrada por toque. O MotionLayout também oferece suporte a frames-chave, permitindo transições totalmente personalizadas para atender às suas necessidades.

O MotionLayout é totalmente declarativo, o que significa que você pode descrever qualquer transição em XML, independente da complexidade.

Considerações sobre design

O método MotionLayout serve para mover, redimensionar e animar elementos da interface 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 seu app está fazendo. Para saber mais sobre como projetar seu app com movimento, consulte a seção Noções básicas sobre movimento do Material Design.

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 seu projeto, adicione a dependência ConstraintLayout 2.0 ao arquivo build.gradle do seu app. Se você estiver usando o AndroidX, adicione a dependência abaixo:

    Groovy

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

    Kotlin

    dependencies {
        implementation("androidx.constraintlayout:constraintlayout:2.2.0-alpha13")
        // To use constraintlayout in compose
        implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha13")
    }
    
  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 .../>
              

    Veja 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. Crie um MotionScene: no exemplo de MotionLayout anterior, o atributo app:layoutDescription faz referência a uma cena de movimento. Uma cena em movimento é um arquivo de recurso XML. Dentro do 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 em movimento têm precedência sobre qualquer definição semelhante no MotionLayout.

    Veja um exemplo de arquivo de cena de movimento que descreve o movimento horizontal básico da 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 do movimento. Esses endpoints são definidos nos elementos <ConstraintSet> posteriormente na cena de movimento.

      • motion:duration especifica o número de milissegundos necessários para a conclusão do movimento.

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

      • motion:touchAnchorId refere-se à 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 refere-se à direção do progresso da ação de arrastar. Por exemplo, motion:dragDirection="dragRight" significa que o progresso aumenta à medida que 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". Na horizontal, os endpoints estão nos lados esquerdo e direito da tela.

    Para ver mais detalhes dos vários elementos compatíveis com uma cena de movimento, consulte os exemplos do MotionLayout.

Atributos interpolados

Dentro de 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 por MotionLayout:

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

Atributos personalizados

Em uma <Constraint>, você pode usar o elemento <CustomAttribute> para especificar uma transição para atributos que não sã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 os métodos getter e setter. O getter e o setter precisam corresponder a um padrão específico. Por exemplo, backgroundColor é compatível, já que a visualização tem os métodos getBackgroundColor() e setBackgroundColor() subjacentes.
  • O outro atributo que você precisa fornecer é baseado no tipo de valor. Escolha um dos seguintes tipos com suporte:
    • 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 os valores do endpoint nos elementos inicial e final <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, como mostrado na Figura 2.

Figura 2. A visualização muda a cor do plano de fundo conforme se move.

Adicione um elemento <CustomAttribute> a cada elemento ConstraintSet, conforme mostrado no snippet de código a seguir:

<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, 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 estiver sendo executado. O valor padrão para este atributo é false.
  • app:progress="float": permite que você especifique explicitamente o andamento da transição. É possível usar qualquer valor de ponto flutuante de 0 (o início da transição) a 1 (o fim da transição).
  • app:currentState="reference": permite que você especifique um ConstraintSet.
  • app:motionDebug permite exibir mais informações de depuração 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 recursos abaixo: