1. Antes de começar
Este codelab faz parte do curso Android avançado no Kotlin. Você aproveitará mais este curso se trabalhar com os codelabs em sequência, mas isso não é obrigatório. Todos os codelabs do curso estão listados na página inicial dos codelabs avançados do Android no Kotlin.
O MotionLayout é uma biblioteca que permite adicionar movimentos avançados ao seu app Android. Ele é baseado em ConstraintLayout, e permite animar qualquer coisa que você possa criar usando ConstraintLayout.
Você pode usar MotionLayout para animar o local, o tamanho, a visibilidade, o alfa, a cor, a elevação, a rotação e outros atributos de várias visualizações ao mesmo tempo. Com o XML declarativo, é possível criar animações coordenadas, envolvendo várias visualizações, que são difíceis de alcançar no código.
Animações são uma ótima maneira de melhorar a experiência no app. Você pode usar animações para:
- Mostrar alterações: a animação entre estados permite que o usuário rastreie naturalmente as mudanças na interface.
- Chame a atenção: use animações para destacar elementos importantes da interface.
- Crie designs bonitos. O movimento eficaz no design deixa os apps sofisticados.
Pré-requisitos
Este codelab foi projetado para desenvolvedores com alguma experiência de desenvolvimento em Android. Antes de tentar concluir este codelab, você precisa:
- Saiba como criar um app com uma atividade e um layout básico e executá-lo em um dispositivo ou emulador usando o Android Studio. Familiarize-se com o
ConstraintLayout. Leia o codelab sobre layout restrito para saber mais sobreConstraintLayout.
O que você aprenderá
- Definir uma animação com
ConstraintSetseMotionLayout - Animar com base em eventos de arrastar
- Mudar a animação com
KeyPosition - Mudar atributos com
KeyAttribute - Executar animações com código
- Animar cabeçalhos recolhíveis com
MotionLayout
O que é necessário
- Android Studio 4.0. O editor
MotionLayoutsó funciona com esta versão do Android Studio.
2. Primeiros passos
Para fazer o download do app de exemplo, você pode:
… ou clone o repositório do GitHub pela linha de comando usando o seguinte comando:
$ git clone https://github.com/googlecodelabs/motionlayout.git
3. Criar animações com o MotionLayout
Primeiro, você vai criar uma animação que move uma visualização da parte de cima da tela para a de baixo em resposta aos cliques do usuário.
Para criar uma animação usando o código inicial, você precisará das seguintes partes principais:
- Uma
MotionLayout,que é uma subclasse daConstraintLayout. Especifique todas as visualizações que serão animadas dentro da tagMotionLayout. - Um
MotionScene,, que é um arquivo XML que descreve uma animação paraMotionLayout.. - Um
Transition,que faz parte doMotionSceneque especifica a duração da animação, o acionador e como mover as visualizações. - Uma
ConstraintSetque especifica as restrições start e end da transição.
Vamos analisar cada um deles por vez, começando com MotionLayout.
Etapa 1: analisar o código existente
MotionLayout é uma subclasse de ConstraintLayout, o que significa que ele oferece suporte aos mesmos recursos durante a adição de animação. Para usar MotionLayout, adicione uma visualização MotionLayout em que você usaria ConstraintLayout..
- No
res/layout, abraactivity_step1.xml.. Aqui você tem umaConstraintLayoutcom uma únicaImageViewde uma estrela, com uma tonalidade aplicada dentro dela.
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>
Esse ConstraintLayout não tem restrições. Portanto, se você executar o app agora, a estrela vai aparecer sem restrições, o que significa que eles vão estar posicionados em um local desconhecido. O Android Studio vai mostrar um aviso sobre a falta de restrições.
Etapa 2: converter para o layout de movimento
Para animar usando MotionLayout,, você precisa converter o ConstraintLayout em um MotionLayout.
Para que seu layout use uma cena em movimento, ele precisa apontar para ela.
- Para fazer isso, abra a superfície de design. No Android Studio 4.0, você abre a superfície de design usando o ícone de divisão ou design no canto superior direito ao visualizar um arquivo XML de layout.

- Depois de abrir a superfície de design, clique com o botão direito do mouse na visualização e selecione Convert to MotionLayout.

Isso substitui a tag ConstraintLayout por uma tag MotionLayout e adiciona um motion:layoutDescription à tag MotionLayout, que aponta para @xml/activity_step1_scene.
etapa_de_atividades**.xml**
<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:layoutDescription="@xml/activity_step1_scene">
Uma cena em movimento é um único arquivo XML que descreve uma animação em uma MotionLayout.
Assim que você converter para MotionLayout, a superfície de design exibirá o Motion Editor.

Há três novos elementos de interface no Motion Editor:
- Visão geral: é uma seleção modal que permite escolher diferentes partes da animação. Nesta imagem, a
startConstraintSetestá selecionada. Também é possível selecionar a transição entrestarteendclicando na seta entre eles. - Seção: abaixo da visão geral, há uma janela de seção que muda com base no item de visão geral selecionado no momento. Nessa imagem, as informações de
startConstraintSetsão exibidas na janela de seleção. - Atributo: o painel de atributos mostra e permite que você edite os atributos do item selecionado no momento a partir da visão geral ou da janela de seleção. Nela, os atributos da
ConstraintSetstartsão mostrados.
Etapa 3: definir restrições de início e término
Todas as animações podem ser definidas em termos de início e fim. O início descreve a aparência da tela antes da animação, e o final descreve a aparência da tela após o término da animação. O MotionLayout é responsável por descobrir como animar entre os estados inicial e final (ao longo do tempo).
O MotionScene usa uma tag ConstraintSet para definir os estados inicial e final. Uma ConstraintSet é o que parece, um conjunto de restrições que podem ser aplicadas a visualizações. Isso inclui as restrições de largura, altura e ConstraintLayout. Ela também inclui alguns atributos, como alpha. Ele não contém as visualizações em si, apenas as restrições delas.
As restrições especificadas em uma ConstraintSet vão substituir as especificadas no arquivo de layout. Se você definir restrições no layout e no MotionScene, apenas as restrições no MotionScene serão aplicadas.
Nesta etapa, você vai restringir a visualização em estrela para começar na parte de cima da tela e terminar na parte de baixo.
É possível concluir essa etapa usando o Motion Editor ou editando o texto de activity_step1_scene.xml diretamente.
- Selecione o ConstraintSet
startno painel de visão geral

- No painel selection, selecione
red_star. No momento, ele mostra a origem delayout. Isso significa que não há uma restrição nesseConstraintSet. Use o ícone de lápis no canto superior direito para Criar restrição.

- Confirme se
red_starmostra uma origem destartquando ostartConstraintSetestá selecionado no painel de visão geral. - No painel "Attributes", com
red_starselecionado nostartConstraintSet, adicione uma restrição na parte de cima e clique nos botões azuis +.

- Abra
xml/activity_step1_scene.xmlpara conferir o código que o Motion Editor gerou para essa restrição.
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>
A ConstraintSet tem um id de @id/start e especifica todas as restrições a serem aplicadas a todas as visualizações na MotionLayout. Como essa MotionLayout tem apenas uma visualização, ela só precisa de uma Constraint.
O Constraint dentro de ConstraintSet especifica o ID da visualização que está restringindo, @id/red_star definido em activity_step1.xml. É importante observar que as tags Constraint especificam apenas restrições e informações de layout. A tag Constraint não sabe que está sendo aplicada a um ImageView.
Essa restrição especifica a altura, a largura e as duas outras restrições necessárias para restringir a visualização red_star ao início superior do pai.
- Selecione o ConstraintSet
endno painel de visão geral.

- Siga as mesmas etapas de antes para adicionar um
Constraintpara ored_starnoConstraintSetdaend. - Se quiser usar o Motion Editor para concluir esta etapa, adicione uma restrição a
bottomeendclicando nos botões azuis +.

- O código em XML fica assim:
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>
Assim como @id/start, esta ConstraintSet tem uma única Constraint no @id/red_star. Desta vez, ele o restringe à extremidade inferior da tela.
Você não precisa usar os nomes @id/start e @id/end, mas é conveniente fazer isso.
Etapa 4: definir uma transição
Cada MotionScene também precisa incluir pelo menos uma transição. Uma transição define cada parte de uma animação, do início ao fim.
Uma transição precisa especificar um ConstraintSet de início e de término. Uma transição também pode especificar como modificar a animação de outras formas, por exemplo, por quanto tempo será executada a animação ou como ela será animada arrastando visualizações.
- O Motion Editor criou uma transição por padrão quando criou o arquivo MotionScene. Abra
activity_step1_scene.xmlpara ver a transição gerada.
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>
Isso é tudo que o MotionLayout precisa para criar uma animação. Analisando cada atributo:
constraintSetStartserá aplicado às visualizações quando a animação começar.constraintSetEndserá aplicado às visualizações no final da animação.durationespecifica quanto tempo a animação deve levar em milissegundos.
O MotionLayout descobrirá um caminho entre as restrições de início e fim e o animará pela duração especificada.
Etapa 5: visualizar a animação no Motion Editor

Animação: vídeo de como reproduzir uma visualização de transição no Motion Editor.
- Abra o Motion Editor e selecione a transição clicando na seta entre
starteendno painel de visão geral.

- O painel de seleção mostra os controles de mídia e uma barra de reprodução quando uma transição é selecionada. Clique em "Reproduzir" ou arraste a posição atual para visualizar a animação.

Etapa 6: adicionar um gerenciador de cliques
É necessária uma maneira de iniciar a animação. Uma maneira de fazer isso é fazer com que a MotionLayout responda a eventos de clique no @id/red_star.
- Abra o editor de movimento e selecione a transição clicando na seta entre o início e o fim no painel de visão geral.

- Clique em
Criar gerenciador de cliques ou deslizamento na barra de ferramentas do painel de visão geral . Isso adiciona um gerenciador que inicia uma transição. - Selecione Gerenciador de cliques no pop-up.

- Mude a opção View To Click para
red_star.

- Clique em Add. O gerenciador de cliques é representado por um pequeno ponto na transição no Motion Editor.

- Com a transição selecionada no painel de visão geral, adicione um atributo
clickActiondetoggleao gerenciador do OnClick que você acabou de incluir no painel de atributos.

- Abra
activity_step1_scene.xmlpara ver o código gerado pelo 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>
O Transition instrui o MotionLayout a executar a animação em resposta a eventos de clique usando uma tag <OnClick>. Analisando cada atributo:
targetIdé a visualização a ser observada pelos cliques.clickActiondetogglealternará entre o estado inicial e final ao clicar. Confira outras opções paraclickActionna documentação.
- Execute o código, clique na Etapa 1, depois na estrela vermelha e confira a animação.
Etapa 5: animações em ação
Execute o app. A animação será executada quando você clicar na estrela.

O arquivo de cena em movimento concluído define um Transition que aponta para um ConstraintSet de início e de término.
No início da animação (@id/start), o ícone de estrela fica restrito ao início da parte superior da tela. Ao final da animação (@id/end), o ícone de estrela fica restrito à parte de baixo da tela.
<?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. Como animar com base em eventos de arrastar
Nesta etapa, você vai criar uma animação que responde a um evento de arrastar (quando o usuário desliza a tela) para executar a animação. O MotionLayout oferece suporte ao rastreamento de eventos de toque para mover visualizações, bem como a gestos de deslizar rápidos baseados em física para deixar o movimento fluido.
Etapa 1: inspecionar o código inicial
- Para começar, abra o arquivo de layout
activity_step2.xml, que tem umMotionLayout. Observe o 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>
Esse layout define todas as visualizações da animação. Os ícones de três estrelas não são restritos no layout porque serão animados na cena em movimento.
Os créditos TextView têm restrições aplicadas, porque ele permanece no mesmo lugar durante toda a animação e não modifica atributos.
Etapa 2: animar a cena
Assim como na última animação, a animação será definida por uma ConstraintSet, de início e fim e um Transition.
Defina o ConstraintSet inicial
- Abra a cena
xml/step2.xmlpara definir a animação. - Adicione as restrições para a restrição inicial
start. No início, as três estrelas ficam centralizadas na parte de baixo da tela. As estrelas direita e esquerda têm um valoralphade0.0, o que significa que elas são totalmente transparentes e 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>
Nesse ConstraintSet, você especifica um Constraint para cada uma das estrelas. Cada restrição será aplicada por MotionLayout no início da animação.
Cada visualização de estrela é centralizada na parte de baixo da tela com restrições de início, fim e fim. As duas estrelas @id/left_star e @id/right_star têm um valor Alfa adicional que as torna invisíveis e será aplicado no início da animação.
Os conjuntos de restrições start e end definem o início e o fim da animação. Uma restrição no início, como motion:layout_constraintStart_toStartOf, restringe o início de uma visualização ao início de outra. Isso pode ser confuso no início, porque o nome start é usado para e ambos são usados no contexto de restrições. Para ajudar a diferenciar, a start no layout_constraintStart se refere ao "início" da visualização, que é da esquerda para a direita e da direita para a esquerda. O conjunto de restrições start se refere ao início da animação.
Definir o ConstraintSet final
- Defina a restrição final para usar uma cadeia para posicionar as três estrelas abaixo de
@id/credits. Além disso, ele definirá o valor final doalphadas estrelas esquerda e direita como1.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>
O resultado final é que as visualizações se espalham e para cima a partir do centro à medida que são animadas.
Além disso, como a propriedade alpha está definida no @id/right_start e no @id/left_star nos ConstraintSets, as duas visualizações aparecem gradualmente à medida que a animação avança.
Animação com base no gesto de deslizar o usuário
O MotionLayout pode rastrear eventos de arrastar do usuário ou deslizar para criar um deslize baseado na física animação. Isso significa que as visualizações vão continuar acontecendo se o usuário as lançar e ficar mais lentas da mesma forma que um objeto físico faria ao rolar por uma superfície. É possível adicionar esse tipo de animação com uma tag OnSwipe no Transition.
- Substitua o TODO para adicionar uma tag
OnSwipepor<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 contém alguns atributos, sendo o mais importante touchAnchorId.
touchAnchorIdé a visualização rastreada que se move em resposta ao toque. OMotionLayoutvai manter essa visualização à mesma distância do dedo que estiver deslizando.touchAnchorSidedetermina qual lado da visualização deve ser rastreado. Isso é importante para visualizações que são redimensionadas, seguem caminhos complexos ou têm um lado que se move mais rápido que o outro.dragDirectiondetermina a direção que importa para essa animação (para cima, para baixo, para a esquerda ou para a direita).
Quando MotionLayout detecta eventos de arrastar, o listener é registrado na visualização MotionLayout, e não na especificada por touchAnchorId. Quando um usuário inicia um gesto em qualquer lugar da tela, o MotionLayout mantém a distância entre o dedo e o touchAnchorSide da constante de visualização touchAnchorId. Se tocarem a 100 dp de distância do lado da âncora, por exemplo, a MotionLayout vai manter esse lado a 100 dp do dedo durante toda a animação.
Faça um teste
- Execute o app novamente e abra a tela da Etapa 2. A animação vai aparecer.
- Tente "arrastar" ou solte o dedo na metade da animação para explorar como o
MotionLayoutmostra animações baseadas em física fluida.

O MotionLayout pode ser animado entre designs muito diferentes usando os recursos de ConstraintLayout para criar efeitos avançados.
Nesta animação, as três visualizações são posicionadas em relação à mãe na parte de baixo da tela para começar. No final, as três visualizações são posicionadas em relação a @id/credits em uma cadeia.
Mesmo com esses layouts muito diferentes, o MotionLayout vai criar uma animação fluida do início ao fim.
5. Como modificar um caminho
Nesta etapa, você vai criar uma animação que segue um caminho complexo durante a animação e animará os créditos durante o movimento. MotionLayout pode modificar o caminho que uma visualização vai seguir entre o início e o fim usando um KeyPosition.
Etapa 1: analisar o código existente
- Abra
layout/activity_step3.xmlexml/step3.xmlpara ver o layout e a cena de movimento existentes.ImageVieweTextViewmostram a lua e o texto dos créditos. - Abra o arquivo de cena em movimento (
xml/step3.xml). Observe que umaTransitionde@id/starta@id/endfoi definida. A animação move a imagem da lua do canto inferior esquerdo para o canto inferior direito da tela usando duasConstraintSets. O texto dos créditos aparece gradualmente dealpha="0.0"paraalpha="1.0"conforme a lua se move. - Execute o app e selecione Etapa 3. Ao clicar na Lua, você verá que a Lua segue um caminho linear (ou uma linha reta) do início ao fim.
Etapa 2: ativar a depuração de caminho
Antes de adicionar um arco ao movimento da Lua, é útil ativar a depuração de caminho no MotionLayout.
Para ajudar a desenvolver animações complexas com MotionLayout, você pode desenhar o caminho da animação de cada visualização. Isso é útil quando você quer visualizar sua animação e ajustar os pequenos detalhes do movimento.
- Para ativar os caminhos de depuração, abra
layout/activity_step3.xmle adicionemotion:motionDebug="SHOW_PATH"à tagMotionLayout.
activity_step3.xml
<!-- Add motion:motionDebug="SHOW_PATH" -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:motionDebug="SHOW_PATH" >
Depois de ativar a depuração de caminho, ao executar o app novamente, você vai encontrar os caminhos de todas as visualizações visualizadas com uma linha pontilhada.

- Os círculos representam a posição inicial ou final de uma visualização.
- Linhas representam o caminho de uma visualização.
- Os losangos representam uma
KeyPositionque modifica o caminho.
Por exemplo, nesta animação, o círculo do meio é a posição do texto dos créditos.
Etapa 3: modificar um caminho
Todas as animações em MotionLayout são definidas por uma ConstraintSet inicial e final que definem a aparência da tela antes e depois do início da animação. Por padrão, MotionLayout traça um caminho linear (uma linha reta) entre as posições inicial e final de cada visualização que muda de posição.
Para criar caminhos complexos, como o arco da lua neste exemplo, o MotionLayout usa um KeyPosition para modificar o caminho que uma visualização faz entre o início e o fim.
- Abra
xml/step3.xmle adicione umKeyPositionà cena. A tagKeyPositioné colocada dentro da tagTransition.

step3.xml
<!-- TODO: Add KeyFrameSet and KeyPosition -->
<KeyFrameSet>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
</KeyFrameSet>
Um KeyFrameSet é filho de uma Transition e é um conjunto de todos os KeyFrames, como KeyPosition, que precisam ser aplicados durante a transição.
Como o MotionLayout calcula o caminho para a lua entre o início e o fim, ele modifica o caminho com base no KeyPosition especificado no KeyFrameSet. Você pode conferir como isso modifica o caminho executando o app novamente.
Um KeyPosition tem vários atributos que descrevem como ele modifica o caminho. As mais importantes são:
framePositioné um número entre 0 e 100. Ele define quando aKeyPositionprecisa ser aplicada na animação, sendo 1 1% na animação e 99% sendo 99%. Então, se o valor for 50, você o aplica bem no meio.motionTargeté a visualização para a qual esseKeyPositionmodifica o caminho.keyPositionTypeé como esseKeyPositionmodifica o caminho. Pode serparentRelative,pathRelativeoudeltaRelative, conforme explicado na próxima etapa.percentX | percentYé o quanto modificar o caminho emframePosition(valores entre 0,0 e 1,0, com valores negativos e valores maiores que 1 são permitidos).
Você pode pensar assim: "Em framePosition modifique o caminho de motionTarget movendo-o em percentX ou percentY de acordo com as coordenadas determinadas por keyPositionType."
Por padrão, MotionLayout arredonda todos os cantos introduzidos ao modificar o caminho. Se você observar a animação que acabou de criar, pode ver que a Lua segue um caminho curvo na curva. É isso que você quer para a maioria das animações. Caso contrário, é possível especificar o atributo curveFit para personalizá-lo.
Fazer um teste
Se você executar o app novamente, a animação desta etapa será mostrada.

A lua segue um arco porque passa por um KeyPosition especificado no Transition.
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
Você pode ler esse KeyPosition como: "Em framePosition 50 (na metade da animação), modifique o caminho de motionTarget @id/moon movendo-o por 50% Y (meio da tela) de acordo com as coordenadas determinadas por parentRelative (todo o MotionLayout)."
Portanto, na metade da animação, a lua precisa passar por uma KeyPosition que está 50% abaixo na tela. Esse KeyPosition não modifica o movimento X, então a Lua ainda vai do início ao fim horizontalmente. O MotionLayout vai descobrir um caminho suave que passa por esse KeyPosition enquanto se move entre o início e o fim.
Se você prestar atenção, verá que o texto dos créditos é limitado pela posição da lua. Por que ele também não está se movendo verticalmente?

<Constraint
android:id="@id/credits"
...
motion:layout_constraintBottom_toBottomOf="@id/moon"
motion:layout_constraintTop_toTopOf="@id/moon"
/>
Acontece que, mesmo que você esteja modificando o caminho que a Lua percorre, as posições inicial e final dela não a movem verticalmente. O KeyPosition não modifica a posição inicial nem final, então o texto dos créditos fica restrito à posição final da lua.
Se você quiser que os créditos se movam com a Lua, adicione um KeyPosition a eles ou modifique as restrições iniciais no @id/credits.
Na próxima seção, você vai se aprofundar nos diferentes tipos de keyPositionType no MotionLayout.
6. Noções básicas sobre keyPositionType
Na última etapa, você usou um tipo keyPosition de parentRelative para deslocar o caminho em 50% da tela. O atributo keyPositionType determina como o MotionLayout modificará o caminho de acordo com percentX ou percentY.
<KeyFrameSet>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
</KeyFrameSet>
Há três tipos diferentes de keyPosition possíveis: parentRelative, pathRelative e deltaRelative. Especificar um tipo altera o sistema de coordenadas pelo qual percentX e percentY são calculados.
O que é um sistema de coordenadas?
Com um sistema de coordenadas, é possível especificar um ponto no espaço. Eles também são úteis para descrever uma posição na tela.
Os sistemas de coordenadas MotionLayout são um sistema de coordenadas cartesianos. Isso significa que elas têm um eixo X e um Y definido por duas linhas perpendiculares. A principal diferença entre eles é onde o eixo X vai na tela (o eixo Y é sempre perpendicular ao eixo X).
Todos os sistemas de coordenadas em MotionLayout usam valores entre 0.0 e 1.0 nos eixos X e Y. Elas permitem valores negativos e maiores que 1.0. Por exemplo, um valor percentX de -2.0 significa que você vai na direção oposta do eixo X duas vezes.
Se tudo isso parece um pouco parecido com a aula de álgebra, confira as fotos abaixo.
Coordenadas parentParent

O keyPositionType de parentRelative usa o mesmo sistema de coordenadas da tela. Ela define (0, 0) no canto superior esquerdo da MotionLayout inteira e (1, 1) no canto inferior direito.
Você pode usar parentRelative sempre que quiser criar uma animação que se mova por todo o MotionLayout, como o arco da lua neste exemplo.
No entanto, se você quiser modificar um caminho em relação ao movimento, por exemplo, torná-lo um pouco curvado, os outros dois sistemas de coordenadas são uma opção melhor.
Coordenadas deltaParent

Delta é um termo matemático para mudança, então deltaRelative é uma maneira de dizer "mudança relativa". Nas coordenadas deltaRelative, (0,0) é a posição inicial da visualização, e (1,1) é a posição final. Os eixos X e Y estão alinhados com a tela.
O eixo X está sempre horizontal na tela, e o eixo Y está sempre vertical na tela. Em comparação com parentRelative, a principal diferença é que as coordenadas descrevem apenas a parte da tela em que a visualização será movida.
deltaRelative é um ótimo sistema de coordenadas para controlar o movimento horizontal ou vertical de forma isolada. Por exemplo, você pode criar uma animação que completa apenas o movimento vertical (Y) em 50% e continua animada na horizontal (X).
Coordenadas pathParent

O último sistema de coordenadas em MotionLayout é pathRelative. É bem diferente dos outros dois, já que o eixo X segue a trajetória de animação do início ao fim. Portanto, (0,0) é a posição inicial e (1,0) é a posição final.
Por que você faria isso? É surpreendente à primeira vista, especialmente porque esse sistema de coordenadas nem sequer está alinhado ao sistema de coordenadas da tela.
O pathRelative é muito útil para algumas coisas.
- Aceleração, desaceleração ou interrupção de uma visualização durante parte da animação. Como a dimensão X sempre corresponderá exatamente ao caminho que a visualização percorre, você pode usar um
pathRelativeKeyPositionpara mudar em qualframePositiondeterminado ponto nesse caminho é alcançado. Portanto, umKeyPositionemframePosition="50"com umpercentX="0.1"faria com que a animação levasse 50% do tempo para percorrer os primeiros 10% do movimento. - Adicionar um arco sutil a um caminho. Como a dimensão Y é sempre perpendicular ao movimento, alterar Y alterará o caminho para a curva em relação ao movimento geral.
- Adicionar uma segunda dimensão quando
deltaRelativenão funciona. Para movimentos totalmente horizontais e verticais,deltaRelativecria apenas uma dimensão útil. No entanto,pathRelativesempre cria coordenadas X e Y utilizáveis.
Na próxima etapa, você vai aprender a criar caminhos ainda mais complexos usando mais de uma KeyPosition.
7. Como construir caminhos complexos
Observando a animação que você criou na última etapa, ela cria uma curva suave, mas a forma pode ser mais parecida com a da lua.
Modificar um caminho com vários elementos KeyPosition
A MotionLayout pode modificar ainda mais um caminho definindo quantas KeyPosition forem necessárias para receber qualquer movimento. Para esta animação, você vai criar um arco, mas é possível fazer a lua pular para cima e para baixo no meio da tela, se você quiser.
- Abra
xml/step4.xml. Observe que ele tem as mesmas visualizações e aKeyFrameque você adicionou na etapa anterior. - Para arredondar o topo da curva, adicione mais dois
KeyPositionsao caminho de@id/moon, um pouco antes de alcançar o topo e outro depois.

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"
/>
Esses KeyPositions são aplicados em 25% e 75% da animação e fazem com que o @id/moon se mova por um caminho a 60% da parte de cima da tela. Combinado com o KeyPosition existente em 50%, isso cria um arco suave para a Lua seguir.
No MotionLayout, você pode adicionar quantos KeyPositions forem necessários para criar a trajetória de animação que quiser. A MotionLayout aplica cada KeyPosition no framePosition especificado e descobre como criar um movimento suave que percorre todo o KeyPositions.
Fazer um teste
- Execute o app novamente. Vá para a Etapa 4 para ver a animação em ação. Quando você clica na lua, ela segue o caminho do início ao fim, passando por cada
KeyPositionespecificado noKeyFrameSet.
Explore por conta própria
Antes de passar para outros tipos de KeyFrame, tente adicionar mais alguns KeyPositions à KeyFrameSet para conferir que tipo de efeitos você pode criar usando apenas KeyPosition.
O exemplo a seguir mostra como criar um caminho complexo que se move para frente e para trás durante a animação.

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>
Quando terminar de explorar o KeyPosition, na próxima etapa, você vai acessar outros tipos de KeyFrames.
8. Como alterar atributos durante o movimento
Criar animações dinâmicas geralmente significa mudar a size, rotation ou alpha das visualizações à medida que a animação avança. MotionLayout oferece suporte à animação de muitos atributos em qualquer visualização usando um KeyAttribute.
Nesta etapa, você vai usar KeyAttribute para fazer a Lua dimensionar e girar. Você também vai usar um KeyAttribute para atrasar a exibição do texto até que a Lua esteja quase completando a viagem.
Etapa 1: redimensionar e girar com o KeyAttribute
- Abra o arquivo
xml/step5.xml, que contém a mesma animação que você criou na etapa anterior. Para aumentar a variedade, essa tela usa uma imagem do espaço diferente como plano de fundo. - Para fazer a Lua expandir de tamanho e girar, adicione duas tags
KeyAttributenaKeyFrameSetemkeyFrame="50"ekeyFrame="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"
/>
Esses KeyAttributes são aplicados em 50% e 100% da animação. O primeiro KeyAttribute em 50% acontecerá na parte superior do arco e fará com que a visualização seja dobrada de tamanho e gire -360 graus (ou um círculo completo). A segunda KeyAttribute vai terminar a segunda rotação para -720 graus (dois círculos completos) e diminuir o tamanho de volta ao normal, já que os valores scaleX e scaleY são padronizados para 1,0.
Assim como uma KeyPosition, uma KeyAttribute usa framePosition e motionTarget para especificar quando aplicar a KeyFrame e qual visualização vai ser modificada. MotionLayout interpolará entre KeyPositions para criar animações fluidas.
KeyAttributes são compatíveis com atributos que podem ser aplicados a todas as visualizações. Eles oferecem suporte à mudança de atributos básicos, como visibility, alpha ou elevation. Você também pode mudar a rotação como está fazendo aqui, girar em três dimensões com rotateX e rotateY, dimensionar o tamanho com scaleX e scaleY ou converter a posição da visualização em X, Y ou Z.
Etapa 2: adiar a exibição dos créditos
Um dos objetivos dessa etapa é atualizar a animação para que o texto dos créditos não apareça até que a animação esteja quase completa.
- Para atrasar a exibição de créditos, defina mais um
KeyAttributeque garanta quealphapermaneça 0 atékeyPosition="85". AMotionLayoutainda fará a transição tranquila do 0 para o 100 Alfa, mas fará isso nos últimos 15% da animação.
step5.xml
<!-- TODO: Add KeyAttribute to delay the appearance of @id/credits -->
<KeyAttribute
motion:framePosition="85"
motion:motionTarget="@id/credits"
android:alpha="0.0"
/>
Esse KeyAttribute mantém o alpha de @id/credits em 0,0 para os primeiros 85% da animação. Como ele começa em um Alfa de 0, isso significa que não vai ficar visível durante os primeiros 85% da animação.
O efeito final desse KeyAttribute é que os créditos aparecem no final da animação. Isso dá a aparência de eles estarem coordenados com a lua pousando no canto direito da tela.
Ao atrasar animações em uma visualização enquanto outra se move dessa forma, você pode criar animações impressionantes que parecem dinâmicas para o usuário.
Fazer um teste
- Execute o app novamente e vá para a Etapa 5 para ver a animação em ação. Quando você clica na lua, ela segue o caminho do início ao fim, passando por cada
KeyAttributeespecificado noKeyFrameSet.

Como você gira a lua em dois círculos completos, ela faz uma inversão dupla, e os créditos atrasam a exibição deles até que a animação esteja quase pronta.
Explore por conta própria
Antes de passar para o tipo final de KeyFrame, tente modificar outros atributos padrão no KeyAttributes. Por exemplo, tente mudar rotation para rotationX para ver qual animação ele produz.
Aqui está uma lista dos atributos padrão que você pode tentar:
android:visibilityandroid:alphaandroid:elevationandroid:rotationandroid:rotationXandroid:rotationYandroid:scaleXandroid:scaleYandroid:translationXandroid:translationYandroid:translationZ
9. Alterar atributos personalizados
Animações avançadas envolvem a mudança da cor ou outros atributos de uma visualização. Embora MotionLayout possa usar um KeyAttribute para mudar qualquer um dos atributos padrão listados na tarefa anterior, você usa um CustomAttribute para especificar qualquer outro atributo.
Um CustomAttribute pode ser usado para definir qualquer valor que tenha um setter. Por exemplo, você pode definir backgroundColor em uma visualização usando um CustomAttribute. O MotionLayout vai usar reflexão para encontrar o setter e o chamar repetidamente para animar a visualização.
Nesta etapa, você usará um CustomAttribute para definir o atributo colorFilter na lua e criar a animação mostrada abaixo.

Definir atributos personalizados
- Para começar, abra
xml/step6.xml, que contém a mesma animação que você criou na etapa anterior. - Para fazer a lua mudar de cor, adicione dois
KeyAttributecom umCustomAttributenoKeyFrameSetemkeyFrame="0",keyFrame="50"ekeyFrame="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>
Você adiciona um CustomAttribute dentro de um KeyAttribute. A CustomAttribute vai ser aplicada no framePosition especificado pela KeyAttribute.
Dentro de CustomAttribute, você precisa especificar um attributeName e um valor para definir.
motion:attributeNameé o nome do setter que será chamado por esse atributo personalizado. Neste exemplo,setColorFilteremDrawableserá chamado.motion:custom*Valueé um valor personalizado do tipo indicado no nome. Neste exemplo, o valor personalizado é uma cor especificada.
Os valores personalizados podem ter qualquer um dos seguintes tipos:
- Cor
- Número inteiro
- Ponto flutuante
- String
- Dimensão
- Booleano
Usando essa API, o MotionLayout pode animar qualquer coisa que forneça um setter em qualquer visualização.
Fazer um teste
- Execute o app novamente e vá para a Etapa 6 para ver a animação em ação. Quando você clica na lua, ela segue o caminho do início ao fim, passando por cada
KeyAttributeespecificado noKeyFrameSet.

Quando você adiciona mais KeyFrames, o método MotionLayout muda o caminho da lua de uma linha reta para uma curva complexa, adicionando uma cambalhota dupla, redimensionamento e uma mudança de cor no meio da animação.
Em animações reais, muitas vezes você animará várias visualizações ao mesmo tempo, controlando o movimento em diferentes caminhos e velocidades. Ao especificar um KeyFrame diferente para cada visualização, é possível coreografar animações ricas que animam várias visualizações com MotionLayout.
10. Arrastar eventos e caminhos complexos
Nesta etapa, você vai descobrir como usar OnSwipe com caminhos complexos. Até agora, a animação da lua foi acionada por um listener OnClick e executada por uma duração fixa.
Controlar animações que têm caminhos complexos usando OnSwipe, como a animação da lua que você criou nas últimas etapas, requer entender como o OnSwipe funciona.
Etapa 1: analisar o comportamento OnSwipe
- Abra
xml/step7.xmle encontre a declaraçãoOnSwipe.
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
/>
- Execute o app no dispositivo e vá para a Etapa 7. Tente produzir uma animação suave arrastando a lua ao longo do caminho do arco.
Quando você executa essa animação, ela não tem uma aparência muito boa. Depois que a lua atinge o topo do arco, ela começa a pular.

Para entender o bug, considere o que acontece quando o usuário está tocando logo abaixo do topo do arco. Como a tag OnSwipe tem um motion:touchAnchorSide="bottom", o MotionLayout tentará fazer com que a distância entre o dedo e a parte de baixo da visualização seja constante em toda a animação.
No entanto, como a parte de baixo da lua nem sempre vai na mesma direção, ela sobe e depois desce. O MotionLayout não sabe o que fazer quando o usuário acabou de passar o topo do arco. Considerando isso, já que você está rastreando o fundo da Lua, onde ele deve ser colocado quando o usuário estiver tocando aqui?

Etapa 2: use o lado direito
Para evitar bugs como esse, é importante sempre escolher uma touchAnchorId e uma touchAnchorSide que sempre avancem em uma direção durante toda a animação.
Nesta animação, os lados right e left da lua avançam pela tela em uma direção.
No entanto, tanto a bottom quanto a top vão ter a direção inversa. Quando OnSwipe tentar fazer o rastreamento, ele vai ficar confuso quando a direção mudar.
- Para fazer essa animação seguir eventos de toque, mude
touchAnchorSidepararight.
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="right"
/>
Etapa 3: usar dragDirection
Você também pode combinar dragDirection com touchAnchorSide para fazer uma faixa lateral diferente do que faria normalmente. Ainda é importante que a touchAnchorSide avance apenas em uma direção, mas você pode informar ao MotionLayout qual direção acompanhar. Por exemplo, você pode manter touchAnchorSide="bottom", mas adicionar dragDirection="dragRight". Isso fará com que MotionLayout rastreie a posição da parte de baixo da visualização, mas considere a localização apenas quando se mover para a direita (ignora o movimento vertical). Assim, mesmo que a parte de baixo sobe e desça, ela ainda será animada corretamente com OnSwipe.
- Atualize o
OnSwipepara rastrear o movimento da lua corretamente.
step7.xml
<!-- Using dragDirection to control the direction of drag tracking →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
motion:dragDirection="dragRight"
/>
Fazer um teste
- Execute o app novamente e tente arrastar a lua por todo o caminho. Mesmo que siga um arco complexo, o
MotionLayoutpoderá avançar a animação em resposta a eventos de deslizar.

11. Movimento em execução com código
MotionLayout pode ser usado para criar animações avançadas quando usado com CoordinatorLayout. Nesta etapa, você vai criar um cabeçalho que pode ser recolhido usando MotionLayout.
Etapa 1: analisar o código existente
- Para começar, abra
layout/activity_step8.xml. - Em
layout/activity_step8.xml, você vai notar que já foram criados umCoordinatorLayoute umAppBarLayoutfuncionais.
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>
Esse layout usa um CoordinatorLayout para compartilhar informações de rolagem entre o NestedScrollView e o AppBarLayout. Portanto, quando o NestedScrollView rolar para cima, ele informará ao AppBarLayout sobre a mudança. É assim que você implementa uma barra de ferramentas recolhível como esta no Android. A rolagem do texto será "coordenada" com o cabeçalho de recolhimento.
A cena em movimento para a qual @id/motion_layout aponta é semelhante à cena de movimento da última etapa. No entanto, a declaração OnSwipe foi removida para permitir que funcione com CoordinatorLayout.
- Execute o app e vá para a Etapa 8. Quando você rola o texto, a lua não se move.
Etapa 2: fazer o MotionLayout rolar
- Para fazer a visualização da
MotionLayoutrolar assim que aNestedScrollViewrolar, adicionemotion:minHeightemotion:layout_scrollFlagsàMotionLayout.
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" >
- Execute o app novamente e vá para a Etapa 8. Você verá que o
MotionLayouté recolhido conforme você rola a tela para cima. No entanto, a animação ainda não progride com base no comportamento de rolagem.
Etapa 3: mover o movimento com o código
- Abra o
Step8Activity.kt. Edite a funçãocoordinateMotion()para informar aoMotionLayoutsobre as mudanças na posição de rolagem.
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)
}
Esse código vai registrar um OnOffsetChangedListener que será chamado sempre que o usuário rolar a tela com o deslocamento atual.
O MotionLayout oferece suporte à busca da transição definindo a propriedade de progresso. Para converter entre uma verticalOffset e um progresso percentual, divida pelo intervalo de rolagem total.
Fazer um teste
- Implante o app novamente e execute a animação da Etapa 8. Observe que
MotionLayoutvai avançar a animação com base na posição de rolagem.

É possível criar animações personalizadas de barras de ferramentas de recolhimento dinâmicas usando MotionLayout. Usando uma sequência de KeyFrames, é possível criar efeitos bem chamativos.
12. Parabéns
Este codelab abordou a API básica de MotionLayout.
Para ver mais exemplos do MotionLayout na prática, confira o exemplo oficial. Não deixe de conferir a documentação.
Saiba mais
O MotionLayout oferece suporte a ainda mais recursos não abordados neste codelab, como KeyCycle,, que permite controlar caminhos ou atributos com ciclos repetidos, e KeyTimeCycle,, que permite animar com base no horário do relógio. Confira as amostras para exemplos de cada um.
Para acessar links de outros codelabs deste curso, consulte a página de destino dos codelabs avançados do Android no Kotlin.