O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

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

O MotionLayout é um tipo de layout que ajuda a gerenciar movimento e widget de animação no seu app. O MotionLayout é uma subclasse do ConstraintLayout que aproveita os recursos avançados de layout dessa ferramenta. Como parte da biblioteca do ConstraintLayout, o MotionLayout está disponível como uma biblioteca de suporte e é compatível com versões anteriores da API de nível 14.

Figura 1. Movimento básico controlado por toque.

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.

Além de descrever transições entre layouts, o MotionLayout permite que você anime qualquer propriedade de layout também. Além disso, ele é compatível com 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.

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

Considerações sobre 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. O movimento não deve ser simplesmente um efeito especial desnecessário no app. É recomendável que ele seja usado 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 "Material Design" em Entendendo 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 seu 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:

        dependencies {
            implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1'
        }
            

    Se você não estiver usando o AndroidX, adicione a seguinte dependência da Biblioteca de Suporte:

        dependencies {
            implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta1'
        }
            
  2. Criar um arquivo MotionLayout: o MotionLayout é uma subclasse de ConstraintLayout; portanto, você possa transformar qualquer ConstraintLayout em um MotionLayout, substituindo o nome da classe no arquivo de recurso de layout, como mostrado nos seguintes exemplos:

    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 pode ser usado para criar o movimento da 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 refere-se a um MotionScene. Um MotionScene é um arquivo de recurso XML que 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 um único MotionScene. Observe que as definições no MotionScene têm precedência sobre quaisquer definições semelhantes no MotionLayout.

    Veja um exemplo de um arquivo MotionScene 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 da animação. Esses endpoints são definidos nos elementos <ConstraintSet> posteriormente no MotionScene.

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

    • <OnSwipe> permite controlar o movimento por toque.

      • motion:touchAnchorId refere-se à visualização que você pode deslizar e arrastar.

      • motion:touchAnchorSide significa que estamos arrastando a visualização a partir do lado direito.

      • motion:dragDirection refere-se à direção do avanço da ação de arrastar. Por exemplo, motion:dragDirection="dragRight" significa que o avanço aumenta conforme você arrasta para a direita.

    • <ConstraintSet> é onde você define as várias restrições que descrevem seu movimento. Neste exemplo, definimos um ConstraintSet para cada endpoint do nosso movimento. Esses endpoints são centralizados verticalmente (via 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 o MotionScene, consulte os Exemplos do MotionLayout.

Atributos interpolados

Em um arquivo MotionScene, os elementos do ConstraintSet podem conter atributos adicionais 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 são relacionados simplesmente a posição ou 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 deve corresponder a um objeto com métodos getter e setter. O getter e o setter correspondem muito a um padrão específico. Por exemplo, backgroundColor é compatível, porque nossa 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

Observe que, ao especificar um atributo personalizado, você precisa definir valores de endpoint nos elementos inicial e final do <ConstraintSet>.

Exemplo: alterar a cor do segundo plano

Com base em exemplo anterior, vamos fazer com que a visualização mude as cores como parte do movimento, como 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 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 acima, o MotionLayout tem outros atributos que você pode especificar:

  • app:applyMotionScene="boolean": indica se o MotionScene será aplicado. O valor padrão para este atributo é true.
  • app:showPaths="boolean": indica se os caminhos de movimento serão exibidos conforme 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. Você pode usar qualquer pontuação 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 saber mais sobre o MotionLayout, consulte os seguintes links: