Android avançado no Kotlin 03.2: animação com o MotionLayout

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 sobre ConstraintLayout.

O que você aprenderá

  • Definir uma animação com ConstraintSets e MotionLayout
  • 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 MotionLayout só 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 da ConstraintLayout. Especifique todas as visualizações que serão animadas dentro da tag MotionLayout.
  • Um MotionScene,, que é um arquivo XML que descreve uma animação para MotionLayout..
  • Um Transition, que faz parte do MotionScene que especifica a duração da animação, o acionador e como mover as visualizações.
  • Uma ConstraintSet que 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..

  1. No res/layout, abra activity_step1.xml.. Aqui você tem uma ConstraintLayout com uma única ImageView de 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.

  1. 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.

a2beea710c2decb7.png

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

4fa936a98a8393b9.png

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.

66d0e80d5ab4daf8.png

Há três novos elementos de interface no Motion Editor:

  1. Visão geral: é uma seleção modal que permite escolher diferentes partes da animação. Nesta imagem, a start ConstraintSet está selecionada. Também é possível selecionar a transição entre start e end clicando na seta entre eles.
  2. 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 start ConstraintSet são exibidas na janela de seleção.
  3. 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 ConstraintSet start sã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.

  1. Selecione o ConstraintSet start no painel de visão geral

6e57661ed358b860.png

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

f9564c574b86ea8.gif

  1. Confirme se red_star mostra uma origem de start quando o start ConstraintSet está selecionado no painel de visão geral.
  2. No painel "Attributes", com red_star selecionado no start ConstraintSet, adicione uma restrição na parte de cima e clique nos botões azuis +.

2fce076cd7b04bd.png

  1. Abra xml/activity_step1_scene.xml para 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.

  1. Selecione o ConstraintSet end no painel de visão geral.

346e1248639b6f1e.png

  1. Siga as mesmas etapas de antes para adicionar um Constraint para o red_star no ConstraintSet da end.
  2. Se quiser usar o Motion Editor para concluir esta etapa, adicione uma restrição a bottom e end clicando nos botões azuis +.

fd33c779ff83c80a.png

  1. 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.

  1. O Motion Editor criou uma transição por padrão quando criou o arquivo MotionScene. Abra activity_step1_scene.xml para 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:

  • constraintSetStart será aplicado às visualizações quando a animação começar.
  • constraintSetEnd será aplicado às visualizações no final da animação.
  • duration especifica 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

dff9ecdc1f4a0740.gif

Animação: vídeo de como reproduzir uma visualização de transição no Motion Editor.

  1. Abra o Motion Editor e selecione a transição clicando na seta entre start e end no painel de visão geral.

1dc541ae8c43b250.png

  1. 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.

a0fd2593384dfb36.png

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.

  1. 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.

b6f94b344ce65290.png

  1. Clique em 699f7ae04024ccf6.png 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.
  2. Selecione Gerenciador de cliques no pop-up.

ccf92d06335105fe.png

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

b0d3f0c970604f01.png

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

cec3913e67fb4105.png

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

9af6fc60673d093d.png

  1. Abra activity_step1_scene.xml para 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.
  • clickAction de toggle alternará entre o estado inicial e final ao clicar. Confira outras opções para clickAction na documentação.
  1. 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.

7ba88af963fdfe10.gif

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

  1. Para começar, abra o arquivo de layout activity_step2.xml, que tem um MotionLayout. 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

  1. Abra a cena xml/step2.xml para definir a animação.
  2. 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 valor alpha de 0.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

  1. 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 do alpha das estrelas esquerda e direita como 1.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.

  1. Substitua o TODO para adicionar uma tag OnSwipe por <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. O MotionLayout vai manter essa visualização à mesma distância do dedo que estiver deslizando.
  • touchAnchorSide determina 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.
  • dragDirection determina 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

  1. Execute o app novamente e abra a tela da Etapa 2. A animação vai aparecer.
  2. Tente "arrastar" ou solte o dedo na metade da animação para explorar como o MotionLayout mostra animações baseadas em física fluida.

fefcdd690a0dcaec.gif

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

  1. Abra layout/activity_step3.xml e xml/step3.xml para ver o layout e a cena de movimento existentes. ImageView e TextView mostram a lua e o texto dos créditos.
  2. Abra o arquivo de cena em movimento (xml/step3.xml). Observe que uma Transition de @id/start a @id/end foi definida. A animação move a imagem da lua do canto inferior esquerdo para o canto inferior direito da tela usando duas ConstraintSets. O texto dos créditos aparece gradualmente de alpha="0.0" para alpha="1.0" conforme a lua se move.
  3. 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.

  1. Para ativar os caminhos de depuração, abra layout/activity_step3.xml e adicione motion:motionDebug="SHOW_PATH" à tag MotionLayout.

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.

23bbb604f456f65c.png

  • 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 KeyPosition que 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.

  1. Abra xml/step3.xml e adicione um KeyPosition à cena. A tag KeyPosition é colocada dentro da tag Transition.

eae4dae9a12d0410.png

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 a KeyPosition precisa 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 esse KeyPosition modifica o caminho.
  • keyPositionType é como esse KeyPosition modifica o caminho. Pode ser parentRelative, pathRelative ou deltaRelative, conforme explicado na próxima etapa.
  • percentX | percentY é o quanto modificar o caminho em framePosition (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.

46b179c01801f19e.gif

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?

1c7cf779931e45cc.gif

<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

a7b7568d46d9dec7.png

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

5680bf553627416c.png

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

f3aaadaac8b4a93f.png

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 pathRelative KeyPosition para mudar em qual framePosition determinado ponto nesse caminho é alcançado. Portanto, um KeyPosition em framePosition="50" com um percentX="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 deltaRelative não funciona. Para movimentos totalmente horizontais e verticais, deltaRelative cria apenas uma dimensão útil. No entanto, pathRelative sempre 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.

  1. Abra xml/step4.xml. Observe que ele tem as mesmas visualizações e a KeyFrame que você adicionou na etapa anterior.
  2. Para arredondar o topo da curva, adicione mais dois KeyPositions ao caminho de @id/moon, um pouco antes de alcançar o topo e outro depois.

500b5ac2db48ef87.png

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

  1. 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 KeyPosition especificado no KeyFrameSet.

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.

cd9faaffde3dfef.png

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

  1. 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.
  2. Para fazer a Lua expandir de tamanho e girar, adicione duas tags KeyAttribute na KeyFrameSet em keyFrame="50" e keyFrame="100"

bbae524a2898569.png

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.

  1. Para atrasar a exibição de créditos, defina mais um KeyAttribute que garanta que alpha permaneça 0 até keyPosition="85". A MotionLayout ainda 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

  1. 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 KeyAttribute especificado no KeyFrameSet.

2f4bfdd681c1fa98.gif

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:visibility
  • android:alpha
  • android:elevation
  • android:rotation
  • android:rotationX
  • android:rotationY
  • android:scaleX
  • android:scaleY
  • android:translationX
  • android:translationY
  • android: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.

5fb6792126a09fda.gif

Definir atributos personalizados

  1. Para começar, abra xml/step6.xml, que contém a mesma animação que você criou na etapa anterior.
  2. Para fazer a lua mudar de cor, adicione dois KeyAttribute com um CustomAttribute no KeyFrameSet em keyFrame="0", keyFrame="50" e keyFrame="100".

214699d5fdd956da.png

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, setColorFilter em Drawable será 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

  1. 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 KeyAttribute especificado no KeyFrameSet.

5fb6792126a09fda.gif

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

  1. Abra xml/step7.xml e encontre a declaração OnSwipe.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
/>
  1. 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.

ed96e3674854a548.gif

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?

56cd575c5c77eddd.png

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.

  1. Para fazer essa animação seguir eventos de toque, mude touchAnchorSide para right.

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.

  1. Atualize o OnSwipe para 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

  1. Execute o app novamente e tente arrastar a lua por todo o caminho. Mesmo que siga um arco complexo, o MotionLayout poderá avançar a animação em resposta a eventos de deslizar.

5458dff382261427.gif

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

  1. Para começar, abra layout/activity_step8.xml.
  2. Em layout/activity_step8.xml, você vai notar que já foram criados um CoordinatorLayout e um AppBarLayout funcionais.

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.

  1. 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

  1. Para fazer a visualização da MotionLayout rolar assim que a NestedScrollView rolar, adicione motion:minHeight e motion: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"  >
  1. 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

  1. Abra o Step8Activity.kt . Edite a função coordinateMotion() para informar ao MotionLayout sobre 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

  1. Implante o app novamente e execute a animação da Etapa 8. Observe que MotionLayout vai avançar a animação com base na posição de rolagem.

ee5ce4d9e33a59ca.gif

É 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.