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ê vai aproveitar mais este curso se fizer os codelabs em sequência, mas isso não é obrigatório. Todos os codelabs do curso estão listados na página de destino dos codelabs do Android avançado em Kotlin.

O MotionLayout é uma biblioteca que permite adicionar movimentos avançados ao seu app Android. Ela é baseada no ConstraintLayout, e permite animar qualquer coisa que você possa criar usando o ConstraintLayout.

É possível usar o 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 que envolvem várias visualizações difíceis de atingir no código.

As animações são uma ótima maneira de melhorar a experiência de um app. Você pode usar animações para:

  • Mostrar mudanças: a animação entre estados permite que o usuário acompanhe naturalmente as mudanças na sua interface.
  • Chame a atenção: use animações para destacar elementos importantes da interface.
  • Crie designs bonitos: movimentos eficazes no design deixam os apps com uma aparência refinada.

Pré-requisitos

Este codelab foi projetado para desenvolvedores com alguma experiência em desenvolvimento para Android. Antes de tentar concluir este codelab, você precisa:

  • Saber como criar um app com uma atividade, um layout básico e executá-lo em um dispositivo ou emulador usando o Android Studio. Conheça o ConstraintLayout. Leia o codelab de 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 essa 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 do início da parte de cima da tela até a parte de baixo em resposta aos cliques do usuário.

Para criar uma animação com base no código inicial, você precisa dos seguintes elementos principais:

  • Um MotionLayout, que é uma subclasse de ConstraintLayout. Você especifica todas as visualizações a serem animadas na 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, o gatilho e como mover as visualizações da animação.
  • Um ConstraintSet que especifica as restrições de início e fim da transição.

Vamos analisar cada um deles, começando pelo MotionLayout.

Etapa 1: analisar o código atual

MotionLayout é uma subclasse de ConstraintLayout, então ele é compatível com todos os mesmos recursos ao adicionar animação. Para usar MotionLayout, adicione uma visualização MotionLayout onde você usaria ConstraintLayout..

  1. Em res/layout, abra activity_step1.xml.. Aqui você tem um ConstraintLayout com um único 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ê executasse o app agora, veria a estrela sem restrições, o que significa que ela seria posicionada em um local desconhecido. O Android Studio vai mostrar um aviso sobre a falta de restrições.

Etapa 2: converter para Motion Layout

Para animar usando MotionLayout,, é necessário converter o ConstraintLayout em um MotionLayout.

Para que seu layout use uma cena de movimento, ele precisa apontar para ela.

  1. Para fazer isso, abra a superfície de design. No Android Studio 4.0, abra a superfície de design usando o ícone de divisão ou design no canto superior direito ao analisar 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 prévia e selecione Converter em 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..

activity_step1**.xml**

<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/activity_step1_scene">

Uma cena de movimento é um único arquivo XML que descreve uma animação em um MotionLayout.

Assim que você fizer a conversão para um MotionLayout, a superfície de design vai mostrar 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, o start ConstraintSet está selecionado. Você também pode 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 de acordo com o item selecionado. Nesta imagem, as informações start ConstraintSet são mostradas na janela de seleção.
  3. Atributo: o painel de atributos mostra e permite editar os atributos do item selecionado na visão geral ou na janela de seleção. Nesta imagem, os atributos do start ConstraintSet estão sendo mostrados.

Etapa 3: definir restrições de início e fim

Todas as animações podem ser definidas em termos de um início e um fim. O início descreve a aparência da tela antes da animação, e o fim descreve a aparência da tela após a conclusão da animação. A MotionLayout é responsável por descobrir como animar entre o estado inicial e final (ao longo do tempo).

O MotionScene usa uma tag ConstraintSet para definir os estados inicial e final. Um ConstraintSet é exatamente o que parece: um conjunto de restrições que podem ser aplicadas a visualizações. Isso inclui restrições de largura, altura e ConstraintLayout. Ele 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 um ConstraintSet substituem as restrições 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 da estrela para que ela comece na parte superior da tela e termine na parte inferior.

Você pode concluir esta etapa usando o Motion Editor ou editando o texto de activity_step1_scene.xml diretamente.

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

6e57661ed358b860.png

  1. No painel seleção, escolha red_star. No momento, ele mostra "Origem de layout", o que significa que não está restrito a este 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 é selecionado no painel de visão geral.
  2. No painel "Atributos", com red_star selecionado no start ConstraintSet, adicione uma restrição na parte de cima e comece clicando 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>

O ConstraintSet tem um id de @id/start e especifica todas as restrições a serem aplicadas a todas as visualizações no MotionLayout. Como esse MotionLayout tem apenas uma vista, ele só precisa de um Constraint.

O Constraint dentro do ConstraintSet especifica o ID da visualização que está sendo restringida, @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 outras duas restrições necessárias para restringir a visualização red_star à parte superior inicial do elemento pai.

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

346e1248639b6f1e.png

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

fd33c779ff83c80a.png

  1. O código em XML é semelhante a este:

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, esse ConstraintSet tem um único Constraint em @id/red_star. Desta vez, ele restringe a parte de baixo da tela.

Não é necessário nomeá-los como @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 todas as partes de uma animação, do início ao fim.

Uma transição precisa especificar um ConstraintSet de início e de fim. Uma transição também pode especificar como modificar a animação de outras maneiras, como por quanto tempo executar a animação ou como animar arrastando visualizações.

  1. O Motion Editor criou uma transição para nós por padrão ao criar 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 o que o MotionLayout precisa para criar uma animação. Analisando cada atributo:

  • O 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 vai durar em milissegundos.

Em seguida, o MotionLayout vai descobrir um caminho entre as restrições de início e fim e animá-lo pela duração especificada.

Etapa 5: visualizar a animação no Motion Editor

dff9ecdc1f4a0740.gif

Animação:vídeo mostrando uma prévia 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 Seleção mostra controles de reprodução e uma barra de limpeza quando uma transição é selecionada. Clique em "Reproduzir" ou arraste a posição atual para conferir uma prévia da animação.

a0fd2593384dfb36.png

Etapa 6: adicionar um manipulador de cliques

Você precisa de uma maneira de iniciar a animação. Uma maneira de fazer isso é fazer com que o MotionLayout responda a eventos de clique em @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 manipulador de clique ou deslize na barra de ferramentas do painel de visão geral . Isso adiciona um manipulador que vai iniciar uma transição.
  2. Selecione Manipulador de cliques no pop-up.

ccf92d06335105fe.png

  1. Mude a Visualização para clique para red_star.

b0d3f0c970604f01.png

  1. Clique em Adicionar. O manipulador 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 OnClick que você acabou de adicionar 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 informa ao MotionLayout para executar a animação em resposta a eventos de clique usando uma tag <OnClick>. Analisando cada atributo:

  • targetId é a visualização a ser observada para cliques.
  • clickAction de toggle vai alternar entre o estado inicial e final ao clicar. Confira outras opções para clickAction na documentação.
  1. Execute o código, clique em Etapa 1 e na estrela vermelha para ver 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 fim.

No início da animação (@id/start), o ícone de estrela fica restrito à parte superior da tela. No final da animação (@id/end), o ícone de estrela é 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. Animação com base em eventos de arrastar

Nesta etapa, você vai criar uma animação que responde a um evento de arrastar do usuário (quando ele desliza a tela) para executar a animação. A MotionLayout oferece suporte ao rastreamento de eventos de toque para mover visualizações, bem como gestos de rolagem rápida baseados em física para tornar 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. Confira 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 três ícones de estrela não são restritos no layout porque serão animados na cena de movimento.

Os créditos TextView têm restrições aplicadas porque permanecem no mesmo lugar durante toda a animação e não modificam nenhum atributo.

Etapa 2: animar a cena

Assim como a última animação, ela será definida por um ConstraintSet, inicial e final e um Transition.

Defina o ConstraintSet inicial.

  1. Abra a cena em movimento xml/step2.xml para definir a animação.
  2. Adicione as restrições para a restrição inicial start. No início, todas as três estrelas ficam centralizadas na parte de baixo da tela. As estrelas da direita e da esquerda têm um valor alpha de 0.0, o que significa que elas são totalmente transparentes e estão 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>

Neste ConstraintSet, especifique 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 usando restrições de início, fim e parte de baixo. As duas estrelas @id/left_star e @id/right_star têm um valor alfa adicional que as torna invisíveis e que 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, e ambos são usados no contexto de restrições. Para ajudar a distinguir, o start em layout_constraintStart se refere ao "início" da visualização, que é a esquerda em um idioma da esquerda para a direita e a direita em um idioma 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 de fim para usar uma corrente e posicionar as três estrelas juntas abaixo de @id/credits. Além disso, ele vai 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 vão se espalhar para cima e para fora do centro à medida que são animadas.

Além disso, como a propriedade alpha é definida em @id/right_start e @id/left_star nos dois ConstraintSets, as duas visualizações vão aparecer gradualmente à medida que a animação avança.

Animação com base no deslizar do usuário

O MotionLayout pode rastrear eventos de arrastar do usuário ou um deslizar para criar uma animação de "movimento rápido" baseada em física. Isso significa que as visualizações vão continuar se o usuário rolar rapidamente e vão diminuir a velocidade como um objeto físico faria ao rolar em 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 na mesma distância do dedo que está 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 qual direção é importante 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 visualização especificada por touchAnchorId. Quando um usuário inicia um gesto em qualquer lugar da tela, o MotionLayout mantém constante a distância entre o dedo e o touchAnchorSide da visualização touchAnchorId. Se eles tocarem a 100 dp de distância do lado da âncora, por exemplo, MotionLayout vai manter esse lado a 100 dp de distância 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 "jogar" ou soltar o dedo na metade da animação para conferir como o MotionLayout mostra animações fluidas baseadas em física.

fefcdd690a0dcaec.gif

O MotionLayout pode animar entre designs muito diferentes usando os recursos do ConstraintLayout para criar efeitos avançados.

Nesta animação, todas as três visualizações são posicionadas em relação ao elemento pai 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.

Apesar desses layouts muito diferentes, MotionLayout vai criar uma animação fluida entre o início e o fim.

5. Como modificar um caminho

Nesta etapa, você vai criar uma animação que segue um caminho complexo durante a animação e anima os créditos durante o movimento. O MotionLayout pode modificar o caminho que uma visualização vai percorrer entre o início e o fim usando um KeyPosition.

Etapa 1: analisar o código atual

  1. Abra layout/activity_step3.xml e xml/step3.xml para conferir o layout e a cena de movimento atuais. Um ImageView e um TextView mostram a lua e o texto de créditos.
  2. Abra o arquivo de cena em movimento (xml/step3.xml). Você vai ver que uma Transition de @id/start para @id/end está definida. A animação move a imagem da lua do canto inferior esquerdo para o canto inferior direito da tela usando dois ConstraintSets. O texto dos créditos aparece gradualmente de alpha="0.0" para alpha="1.0" à medida que a lua se move.
  3. Execute o app e selecione Etapa 3. Você vai notar que a lua segue um caminho linear (ou uma linha reta) do início ao fim quando você clica nela.

Etapa 2: ativar a depuração de caminhos

Antes de adicionar um arco ao movimento da lua, é útil ativar a depuração de caminhos em MotionLayout.

Para ajudar a desenvolver animações complexas com o MotionLayout, é possível desenhar o caminho da animação de cada visualização. Isso é útil quando você quer visualizar a 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 caminhos, quando você executar o app novamente, vai ver os caminhos de todas as visualizações com uma linha pontilhada.

23bbb604f456f65c.png

  • Os círculos representam a posição inicial ou final de uma visualização.
  • As linhas representam o caminho de uma visualização.
  • Os diamantes representam um 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 um início e um fim ConstraintSet, que definem a aparência da tela antes e depois da animação. Por padrão, MotionLayout cria 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 um Transition e é um conjunto de todos os KeyFrames, como KeyPosition, que devem ser aplicados durante a transição.

À medida que MotionLayout calcula o caminho da lua entre o início e o fim, ele modifica o caminho com base no KeyPosition especificado no KeyFrameSet. Para ver como isso modifica o caminho, execute o app novamente.

Uma KeyPosition tem vários atributos que descrevem como ela modifica o caminho. Os mais importantes são:

  • framePosition é um número entre 0 e 100. Ele define quando na animação esse KeyPosition deve ser aplicado, sendo 1 1% da animação e 99 99% da animação. Então, se o valor for 50, aplique-o bem no meio.
  • motionTarget é a visualização para a qual 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 >1 permitidos).

Pense 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 pela modificação do caminho. Se você observar a animação que acabou de criar, vai notar que a lua segue um caminho curvo na curva. Para a maioria das animações, é isso que você quer. Caso contrário, especifique o atributo curveFit para personalizar.

Fazer um teste

Se você executar o app novamente, vai ver a animação dessa etapa.

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 este KeyPosition como: "Em framePosition 50 (metade da animação), modifique o caminho de motionTarget @id/moon movendo-o em 50% Y (metade da tela) de acordo com as coordenadas determinadas por parentRelative (todo o MotionLayout)".

Assim, na metade da animação, a lua precisa passar por um 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 na horizontal. O MotionLayout vai descobrir um caminho suave que passa por esse KeyPosition enquanto se move entre o início e o fim.

Se você olhar de perto, vai notar que o texto dos créditos é limitado pela posição da lua. Por que ele não se move verticalmente também?

1c7cf779931e45cc.gif

<Constraint
       android:id="@id/credits"
       ...
       motion:layout_constraintBottom_toBottomOf="@id/moon"
       motion:layout_constraintTop_toTopOf="@id/moon"
/>

Acontece que, mesmo modificando o caminho da lua, as posições inicial e final dela não a movem verticalmente. O KeyPosition não modifica a posição inicial ou final. Portanto, o texto dos créditos é 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 de início em @id/credits.

Na próxima seção, você vai conhecer os diferentes tipos de keyPositionType em MotionLayout.

6. Noções básicas sobre keyPositionType

Na última etapa, você usou um tipo keyPosition de parentRelative para compensar o caminho em 50% da tela. O atributo keyPositionType determina como o MotionLayout vai 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 muda o sistema de coordenadas pelo qual percentX e percentY são calculados.

O que é um sistema de coordenadas?

Um sistema de coordenadas oferece uma maneira de 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 cartesianas. Isso significa que eles têm um eixo X e um eixo Y definidos por duas linhas perpendiculares. A principal diferença entre eles é onde o eixo X fica 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. Eles permitem valores negativos e maiores que 1.0. Por exemplo, um valor percentX de -2.0 significa ir na direção oposta do eixo X duas vezes.

Se tudo isso parecer muito com uma aula de álgebra, confira as imagens abaixo.

Coordenadas parentRelative

a7b7568d46d9dec7.png

O keyPositionType de parentRelative usa o mesmo sistema de coordenadas da tela. Ele define (0, 0) como o canto superior esquerdo de todo o MotionLayout e (1, 1) como o canto inferior direito.

Use parentRelative sempre que quiser fazer 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, fazer uma pequena curva, os outros dois sistemas de coordenadas serão uma opção melhor.

coordenadas deltaRelative

5680bf553627416c.png

Delta é um termo matemático para mudança. Portanto, 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 é sempre horizontal na tela, e o eixo Y é sempre vertical. Em comparação com parentRelative, a principal diferença é que as coordenadas descrevem apenas a parte da tela em que a visualização vai se mover.

deltaRelative é um ótimo sistema de coordenadas para controlar o movimento horizontal ou vertical isoladamente. Por exemplo, é possível criar uma animação que conclui apenas o movimento vertical (Y) em 50% e continua animando na horizontal (X).

pathRelative coordinates

f3aaadaac8b4a93f.png

O último sistema de coordenadas em MotionLayout é pathRelative. Ele é 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? Isso é surpreendente à primeira vista, principalmente porque esse sistema de coordenadas não está alinhado ao sistema de coordenadas da tela.

Acontece que pathRelative é muito útil para algumas coisas.

  • Acelerar, desacelerar ou parar uma visualização durante parte da animação. Como a dimensão X sempre corresponde exatamente ao caminho percorrido pela visualização, é possível usar um pathRelative KeyPosition para mudar qual framePosition um ponto específico nesse caminho é alcançado. Assim, 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, mudar Y vai alterar o caminho para curvar em relação ao movimento geral.
  • Não é possível adicionar uma segunda dimensão quando deltaRelative não funciona. Para movimentos completamente horizontais e verticais, deltaRelative cria apenas uma dimensão útil. No entanto, pathRelative sempre vai criar coordenadas X e Y utilizáveis.

Na próxima etapa, você vai aprender a criar caminhos ainda mais complexos usando mais de um KeyPosition.

7. Como criar caminhos complexos

Analisando a animação criada na última etapa, ela cria uma curva suave, mas o formato poderia ser mais parecido com uma lua.

Modificar um caminho com vários elementos KeyPosition

MotionLayout pode modificar ainda mais um caminho definindo quantos KeyPosition forem necessários para obter qualquer movimento. Para esta animação, você vai criar um arco, mas pode fazer a lua pular para cima e para baixo no meio da tela, se quiser.

  1. Abra xml/step4.xml. Ele tem as mesmas visualizações e o KeyFrame que você adicionou na etapa anterior.
  2. Para completar a parte de cima da curva, adicione mais dois KeyPositions ao caminho de @id/moon, um pouco antes de chegar ao 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 serão aplicados 25% e 75% do caminho da animação e farão com que @id/moon se mova por um caminho que fica a 60% da parte de cima da tela. Combinado com o KeyPosition atual em 50%, isso cria um arco suave para a lua seguir.

No MotionLayout, adicione quantos KeyPositions forem necessários para criar a trajetória de animação desejada. O MotionLayout vai aplicar cada KeyPosition no framePosition especificado e descobrir como criar um movimento suave que passe por todos os KeyPositions.

Fazer um teste

  1. Execute o app novamente. Acesse 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 KeyPositions ao KeyFrameSet para ver que tipo de efeitos você pode criar usando apenas KeyPosition.

Confira um exemplo de 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>

Depois de explorar KeyPosition, na próxima etapa você vai conhecer outros tipos de KeyFrames.

8. Mudança de atributos durante o movimento

Criar animações dinâmicas geralmente significa mudar o size, rotation ou alpha das visualizações à medida que a animação avança. O MotionLayout permite animar vários atributos em qualquer visualização usando um KeyAttribute.

Nesta etapa, você vai usar KeyAttribute para dimensionar e girar a lua. Você também vai usar um KeyAttribute para atrasar o aparecimento do texto até que a lua quase complete a jornada.

Etapa 1: redimensionar e girar com KeyAttribute

  1. Abra xml/step5.xml, que contém a mesma animação criada na etapa anterior. Para variar, essa tela usa uma imagem diferente do espaço como plano de fundo.
  2. Para fazer a lua aumentar de tamanho e girar, adicione duas tags KeyAttribute no 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 a 50% vai acontecer na parte de cima do arco, fazendo com que a visualização dobre de tamanho e gire -360 graus (ou um círculo completo). O segundo KeyAttribute vai concluir a segunda rotação para -720 graus (dois círculos completos) e reduzir o tamanho de volta ao normal, já que os valores scaleX e scaleY são definidos como 1,0 por padrão.

Assim como um KeyPosition, um KeyAttribute usa framePosition e motionTarget para especificar quando aplicar o KeyFrame e qual visualização modificar. O MotionLayout vai fazer a interpolação entre KeyPositions para criar animações fluidas.

O KeyAttributes oferece suporte a atributos que podem ser aplicados a todas as visualizações. Eles permitem mudar 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 traduzir a posição da visualização em X, Y ou Z.

Etapa 2: atrasar o aparecimento dos créditos

Uma das metas desta etapa é atualizar a animação para que o texto dos créditos não apareça até que a animação esteja quase concluída.

  1. Para atrasar o aparecimento dos créditos, defina mais um KeyAttribute que garanta que alpha permaneça 0 até keyPosition="85". MotionLayout ainda vai fazer uma transição suave de 0 para 100 alfa, mas isso vai acontecer 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 nos primeiros 85% da animação. Como ele começa com um alfa de 0, isso significa que ele vai ficar invisível nos primeiros 85% da animação.

O efeito final desse KeyAttribute é que os créditos aparecem no final da animação. Isso dá a impressão de que eles estão coordenados com a Lua, que se acomoda no canto direito da tela.

Ao atrasar as animações em uma visualização enquanto outra se move assim, 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ê clicar na lua, ela vai seguir o caminho do início ao fim, passando por cada KeyAttribute especificado no KeyFrameSet.

2f4bfdd681c1fa98.gif

Como você gira a lua dois círculos completos, ela vai dar um salto duplo para trás, e os créditos vão atrasar a aparição até que a animação esteja quase concluída.

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.

Confira uma lista dos atributos padrão que você pode testar:

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

9. Mudar atributos personalizados

Animações avançadas envolvem a mudança da cor ou de outros atributos de uma visualização. Enquanto MotionLayout pode 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, é possível definir o backgroundColor em uma View usando um CustomAttribute. O MotionLayout usa reflexão para encontrar o setter e o chama repetidamente para animar a visualização.

Nesta etapa, você vai 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 criada 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. O CustomAttribute será aplicado no framePosition especificado pelo KeyAttribute.

Dentro do CustomAttribute, especifique um attributeName e um valor a ser definido.

  • 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

Com 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 acesse Etapa 6 para conferir a animação em ação. Quando você clicar na lua, ela vai seguir o caminho do início ao fim, passando por cada KeyAttribute especificado no KeyFrameSet.

5fb6792126a09fda.gif

Quando você adiciona mais KeyFrames, o MotionLayout muda o caminho da lua de uma linha reta para uma curva complexa, adicionando um salto duplo para trás, redimensionamento e uma mudança de cor no meio da animação.

Em animações reais, geralmente você anima várias visualizações ao mesmo tempo, controlando o movimento delas em diferentes caminhos e velocidades. Ao especificar um KeyFrame diferente para cada visualização, é possível coreografar animações avançadas que animam várias visualizações com MotionLayout.

10. Eventos de arrastar e caminhos complexos

Nesta etapa, você vai aprender a 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.

Para controlar animações com trajetórias complexas usando OnSwipe, como a animação da lua que você criou nas últimas etapas, é necessário entender como OnSwipe funciona.

Etapa 1: analisar o comportamento do 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 seu dispositivo e acesse a Etapa 7. Arraste a lua ao longo do caminho do arco para criar uma animação suave.

Quando você executa essa animação, ela não fica 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 toca logo abaixo da parte de cima do arco. Como a tag OnSwipe tem um motion:touchAnchorSide="bottom", o MotionLayout tentará manter constante a distância entre o dedo e a parte de baixo da visualização durante toda a animação.

Mas, 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 pelo topo do arco. Para considerar isso, já que você está rastreando a parte de baixo da lua, onde ela deve ser colocada quando o usuário tocar aqui?

56cd575c5c77eddd.png

Etapa 2: use o lado direito

Para evitar bugs como esse, é importante sempre escolher um touchAnchorId e um touchAnchorSide que sempre avancem em uma direção durante toda a animação.

Nesta animação, o lado right e o lado left da lua vão avançar pela tela em uma direção.

No entanto, bottom e top vão inverter a direção. Quando o OnSwipe tenta rastreá-los, ele fica confuso quando a direção muda.

  1. Para fazer com que a animação siga os 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

Também é possível combinar dragDirection com touchAnchorSide para fazer com que uma faixa secundária siga uma direção diferente do normal. Ainda é importante que o touchAnchorSide avance apenas em uma direção, mas você pode informar ao MotionLayout qual direção rastrear. Por exemplo, você pode manter o touchAnchorSide="bottom", mas adicionar dragDirection="dragRight". Isso fará com que MotionLayout rastreie a posição da parte de baixo da visualização, mas só considere a localização dela ao mover para a direita (ignorando o movimento vertical). Assim, mesmo que a parte de baixo suba e desça, ela ainda será animada corretamente com OnSwipe.

  1. Atualize 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 seguindo um arco complexo, o MotionLayout poderá progredir na animação em resposta a eventos de deslizar.

5458dff382261427.gif

11. Executar movimento com código

MotionLayout pode ser usado para criar animações avançadas com CoordinatorLayout. Nesta etapa, você vai criar um cabeçalho recolhível usando MotionLayout.

Etapa 1: analisar o código atual

  1. Para começar, abra layout/activity_step8.xml.
  2. Em layout/activity_step8.xml, você vê que um CoordinatorLayout e um AppBarLayout funcionais já estão criados.

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. Assim, quando o NestedScrollView rolar para cima, ele vai informar o AppBarLayout sobre a mudança. É assim que você implementa uma barra de ferramentas recolhível como esta no Android: a rolagem do texto é "coordenada" com o cabeçalho recolhível.

A cena de movimento apontada por @id/motion_layout é semelhante à cena de movimento da última etapa. No entanto, a declaração OnSwipe foi removida para permitir que ela funcione com CoordinatorLayout.

  1. Execute o app e vá para a Etapa 8. Você vai perceber que, ao rolar o texto, a Lua não se move.

Etapa 2: fazer o MotionLayout rolar

  1. Para fazer com que a visualização MotionLayout role assim que NestedScrollView rolar, adicione motion:minHeight e motion:layout_scrollFlags a 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ê vai notar que o MotionLayout é recolhido à medida que você rola para cima. No entanto, a animação ainda não avança com base no comportamento de rolagem.

Etapa 3: mover o movimento com código

  1. Abra Step8Activity.kt . Edite a função coordinateMotion() para informar 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 com o deslocamento de rolagem atual.

O MotionLayout oferece suporte à busca da transição definindo a propriedade de progresso. Para converter entre um verticalOffset e uma porcentagem de progresso, divida pelo intervalo total de rolagem.

Fazer um teste

  1. Implante o app novamente e execute a animação da Etapa 8. Você vai notar que MotionLayout vai progredir a animação com base na posição de rolagem.

ee5ce4d9e33a59ca.gif

É possível criar animações personalizadas de barras de ferramentas dinâmicas recolhíveis usando MotionLayout. Usando uma sequência de KeyFrames, é possível alcançar efeitos muito ousados.

12. Parabéns

Este codelab abordou a API básica do MotionLayout.

Para ver mais exemplos de MotionLayout na prática, confira a amostra oficial. Confira a documentação.

Saiba mais

O MotionLayout oferece 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 na hora do relógio. Confira os exemplos de cada um.

Para acessar links de outros codelabs neste curso, consulte a página de destino dos codelabs do curso Android avançado no Kotlin.