1. Antes de começar
Este codelab faz parte do curso Android avançado no Kotlin. Você aproveitará mais este curso se trabalhar com os codelabs em sequência, mas isso não é obrigatório. Todos os codelabs do curso estão listados na página inicial dos codelabs avançados do Android no Kotlin.
O MotionLayout
é uma biblioteca que permite adicionar movimentos avançados ao seu app Android. Ele é baseado em ConstraintLayout,
e permite animar qualquer coisa que você possa criar usando ConstraintLayout
.
Você pode usar MotionLayout
para animar o local, o tamanho, a visibilidade, o alfa, a cor, a elevação, a rotação e outros atributos de várias visualizações ao mesmo tempo. Com o XML declarativo, é possível criar animações coordenadas, envolvendo várias visualizações, que são difíceis de alcançar no código.
Animações são uma ótima maneira de melhorar a experiência no app. Você pode usar animações para:
- Mostrar alterações: a animação entre estados permite que o usuário rastreie naturalmente as mudanças na interface.
- Chame a atenção: use animações para destacar elementos importantes da interface.
- Crie designs bonitos. O movimento eficaz no design deixa os apps sofisticados.
Pré-requisitos
Este codelab foi projetado para desenvolvedores com alguma experiência de desenvolvimento em Android. Antes de tentar concluir este codelab, você precisa:
- Saiba como criar um app com uma atividade e um layout básico e executá-lo em um dispositivo ou emulador usando o Android Studio. Familiarize-se com o
ConstraintLayout
. Leia o codelab sobre layout restrito para saber mais sobreConstraintLayout
.
O que você aprenderá
- Definir uma animação com
ConstraintSets
eMotionLayout
- 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 daConstraintLayout
. Especifique todas as visualizações que serão animadas dentro da tagMotionLayout
. - Um
MotionScene,
, que é um arquivo XML que descreve uma animação paraMotionLayout.
. - Um
Transition,
que faz parte doMotionScene
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.
.
- No
res/layout
, abraactivity_step1.xml.
. Aqui você tem umaConstraintLayout
com uma únicaImageView
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.
- Para fazer isso, abra a superfície de design. No Android Studio 4.0, você abre a superfície de design usando o ícone de divisão ou design no canto superior direito ao visualizar um arquivo XML de layout.
- Depois de abrir a superfície de design, clique com o botão direito do mouse na visualização e selecione Convert to MotionLayout.
Isso substitui a tag ConstraintLayout
por uma tag MotionLayout
e adiciona um motion:layoutDescription
à tag MotionLayout
, que aponta para @xml/activity_step1_scene.
etapa_de_atividades**.xml**
<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:layoutDescription="@xml/activity_step1_scene">
Uma cena em movimento é um único arquivo XML que descreve uma animação em uma MotionLayout
.
Assim que você converter para MotionLayout
, a superfície de design exibirá o Motion Editor.
Há três novos elementos de interface no Motion Editor:
- Visão geral: é uma seleção modal que permite escolher diferentes partes da animação. Nesta imagem, a
start
ConstraintSet
está selecionada. Também é possível selecionar a transição entrestart
eend
clicando na seta entre eles. - Seção: abaixo da visão geral, há uma janela de seção que muda com base no item de visão geral selecionado no momento. Nessa imagem, as informações de
start
ConstraintSet
são exibidas na janela de seleção. - Atributo: o painel de atributos mostra e permite que você edite os atributos do item selecionado no momento a partir da visão geral ou da janela de seleção. Nela, os atributos da
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.
- Selecione o ConstraintSet
start
no painel de visão geral
- No painel selection, selecione
red_star
. No momento, ele mostra a origem delayout
. Isso significa que não há uma restrição nesseConstraintSet
. Use o ícone de lápis no canto superior direito para Criar restrição.
- Confirme se
red_star
mostra uma origem destart
quando ostart
ConstraintSet
está selecionado no painel de visão geral. - No painel "Attributes", com
red_star
selecionado nostart
ConstraintSet
, adicione uma restrição na parte de cima e clique nos botões azuis +.
- 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.
- Selecione o ConstraintSet
end
no painel de visão geral.
- Siga as mesmas etapas de antes para adicionar um
Constraint
para ored_star
noConstraintSet
daend
. - Se quiser usar o Motion Editor para concluir esta etapa, adicione uma restrição a
bottom
eend
clicando nos botões azuis +.
- O código em XML fica assim:
activitiy_step1_scene.xml
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
Assim como @id/start
, esta ConstraintSet
tem uma única Constraint
no @id/red_star
. Desta vez, ele o restringe à extremidade inferior da tela.
Você não precisa usar os nomes @id/start
e @id/end
, mas é conveniente fazer isso.
Etapa 4: definir uma transição
Cada MotionScene
também precisa incluir pelo menos uma transição. Uma transição define cada parte de uma animação, do início ao fim.
Uma transição precisa especificar um ConstraintSet
de início e de término. Uma transição também pode especificar como modificar a animação de outras formas, por exemplo, por quanto tempo será executada a animação ou como ela será animada arrastando visualizações.
- O Motion Editor criou uma transição por padrão quando criou o arquivo MotionScene. Abra
activity_step1_scene.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
Animação: vídeo de como reproduzir uma visualização de transição no Motion Editor.
- Abra o Motion Editor e selecione a transição clicando na seta entre
start
eend
no painel de visão geral.
- O painel de seleção mostra os controles de mídia e uma barra de reprodução quando uma transição é selecionada. Clique em "Reproduzir" ou arraste a posição atual para visualizar a animação.
Etapa 6: adicionar um gerenciador de cliques
É necessária uma maneira de iniciar a animação. Uma maneira de fazer isso é fazer com que a MotionLayout
responda a eventos de clique no @id/red_star
.
- Abra o editor de movimento e selecione a transição clicando na seta entre o início e o fim no painel de visão geral.
- Clique em Criar gerenciador de cliques ou deslizamento na barra de ferramentas do painel de visão geral . Isso adiciona um gerenciador que inicia uma transição.
- Selecione Gerenciador de cliques no pop-up.
- Mude a opção View To Click para
red_star
.
- Clique em Add. O gerenciador de cliques é representado por um pequeno ponto na transição no Motion Editor.
- Com a transição selecionada no painel de visão geral, adicione um atributo
clickAction
detoggle
ao gerenciador do OnClick que você acabou de incluir no painel de atributos.
- 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
detoggle
alternará entre o estado inicial e final ao clicar. Confira outras opções paraclickAction
na documentação.
- Execute o código, clique na Etapa 1, depois na estrela vermelha e confira a animação.
Etapa 5: animações em ação
Execute o app. A animação será executada quando você clicar na estrela.
O arquivo de cena em movimento concluído define um Transition
que aponta para um ConstraintSet
de início e de término.
No início da animação (@id/start
), o ícone de estrela fica restrito ao início da parte superior da tela. Ao final da animação (@id/end
), o ícone de estrela fica restrito à parte de baixo da tela.
<?xml version="1.0" encoding="utf-8"?>
<!-- Describe the animation for activity_step1.xml -->
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- A transition describes an animation via start and end state -->
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<!-- MotionLayout will handle clicks on @id/star to "toggle" the animation between the start and end -->
<OnClick
motion:targetId="@id/red_star"
motion:clickAction="toggle" />
</Transition>
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
</MotionScene>
4. Como animar com base em eventos de arrastar
Nesta etapa, você vai criar uma animação que responde a um evento de arrastar (quando o usuário desliza a tela) para executar a animação. O MotionLayout
oferece suporte ao rastreamento de eventos de toque para mover visualizações, bem como a gestos de deslizar rápidos baseados em física para deixar o movimento fluido.
Etapa 1: inspecionar o código inicial
- Para começar, abra o arquivo de layout
activity_step2.xml
, que tem umMotionLayout
. Observe o código.
activity_step2.xml
<!-- initial code -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:layoutDescription="@xml/step2" >
<ImageView
android:id="@+id/left_star"
...
/>
<ImageView
android:id="@+id/right_star"
...
/>
<ImageView
android:id="@+id/red_star"
...
/>
<TextView
android:id="@+id/credits"
...
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
Esse layout define todas as visualizações da animação. Os ícones de três estrelas não são restritos no layout porque serão animados na cena em movimento.
Os créditos TextView
têm restrições aplicadas, porque ele permanece no mesmo lugar durante toda a animação e não modifica atributos.
Etapa 2: animar a cena
Assim como na última animação, a animação será definida por uma ConstraintSet,
de início e fim e um Transition
.
Defina o ConstraintSet inicial
- Abra a cena
xml/step2.xml
para definir a animação. - Adicione as restrições para a restrição inicial
start
. No início, as três estrelas ficam centralizadas na parte de baixo da tela. As estrelas direita e esquerda têm um valoralpha
de0.0
, o que significa que elas são totalmente transparentes e ocultas.
step2.xml
<!-- TODO apply starting constraints -->
<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
<Constraint
android:id="@+id/left_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.0"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
<Constraint
android:id="@+id/right_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.0"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
Nesse ConstraintSet
, você especifica um Constraint
para cada uma das estrelas. Cada restrição será aplicada por MotionLayout
no início da animação.
Cada visualização de estrela é centralizada na parte de baixo da tela com restrições de início, fim e fim. As duas estrelas @id/left_star
e @id/right_star
têm um valor Alfa adicional que as torna invisíveis e será aplicado no início da animação.
Os conjuntos de restrições start
e end
definem o início e o fim da animação. Uma restrição no início, como motion:layout_constraintStart_toStartOf
, restringe o início de uma visualização ao início de outra. Isso pode ser confuso no início, porque o nome start
é usado para e ambos são usados no contexto de restrições. Para ajudar a diferenciar, a start
no layout_constraintStart
se refere ao "início" da visualização, que é da esquerda para a direita e da direita para a esquerda. O conjunto de restrições start
se refere ao início da animação.
Definir o ConstraintSet final
- Defina a restrição final para usar uma cadeia para posicionar as três estrelas abaixo de
@id/credits
. Além disso, ele definirá o valor final doalpha
das estrelas esquerda e direita como1.0
.
step2.xml
<!-- TODO apply ending constraints -->
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/left_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="1.0"
motion:layout_constraintHorizontal_chainStyle="packed"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toStartOf="@id/red_star"
motion:layout_constraintTop_toBottomOf="@id/credits" />
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toEndOf="@id/left_star"
motion:layout_constraintEnd_toStartOf="@id/right_star"
motion:layout_constraintTop_toBottomOf="@id/credits" />
<Constraint
android:id="@+id/right_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="1.0"
motion:layout_constraintStart_toEndOf="@id/red_star"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toBottomOf="@id/credits" />
</ConstraintSet>
O resultado final é que as visualizações se espalham e para cima a partir do centro à medida que são animadas.
Além disso, como a propriedade alpha
está definida no @id/right_start
e no @id/left_star
nos ConstraintSets
, as duas visualizações aparecem gradualmente à medida que a animação avança.
Animação com base no gesto de deslizar o usuário
O MotionLayout
pode rastrear eventos de arrastar do usuário ou deslizar para criar um deslize baseado na física animação. Isso significa que as visualizações vão continuar acontecendo se o usuário as lançar e ficar mais lentas da mesma forma que um objeto físico faria ao rolar por uma superfície. É possível adicionar esse tipo de animação com uma tag OnSwipe
no Transition
.
- Substitua o TODO para adicionar uma tag
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. OMotionLayout
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
- Execute o app novamente e abra a tela da Etapa 2. A animação vai aparecer.
- Tente "arrastar" ou solte o dedo na metade da animação para explorar como o
MotionLayout
mostra animações baseadas em física fluida.
O MotionLayout
pode ser animado entre designs muito diferentes usando os recursos de ConstraintLayout
para criar efeitos avançados.
Nesta animação, as três visualizações são posicionadas em relação à mãe na parte de baixo da tela para começar. No final, as três visualizações são posicionadas em relação a @id/credits
em uma cadeia.
Mesmo com esses layouts muito diferentes, o MotionLayout
vai criar uma animação fluida do início ao fim.
5. Como modificar um caminho
Nesta etapa, você vai criar uma animação que segue um caminho complexo durante a animação e animará os créditos durante o movimento. MotionLayout
pode modificar o caminho que uma visualização vai seguir entre o início e o fim usando um KeyPosition
.
Etapa 1: analisar o código existente
- Abra
layout/activity_step3.xml
exml/step3.xml
para ver o layout e a cena de movimento existentes.ImageView
eTextView
mostram a lua e o texto dos créditos. - Abra o arquivo de cena em movimento (
xml/step3.xml
). Observe que umaTransition
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 duasConstraintSets
. O texto dos créditos aparece gradualmente dealpha="0.0"
paraalpha="1.0"
conforme a lua se move. - Execute o app e selecione Etapa 3. Ao clicar na Lua, você verá que a Lua segue um caminho linear (ou uma linha reta) do início ao fim.
Etapa 2: ativar a depuração de caminho
Antes de adicionar um arco ao movimento da Lua, é útil ativar a depuração de caminho no MotionLayout
.
Para ajudar a desenvolver animações complexas com MotionLayout
, você pode desenhar o caminho da animação de cada visualização. Isso é útil quando você quer visualizar sua animação e ajustar os pequenos detalhes do movimento.
- Para ativar os caminhos de depuração, abra
layout/activity_step3.xml
e adicionemotion:motionDebug="SHOW_PATH"
à tagMotionLayout
.
activity_step3.xml
<!-- Add motion:motionDebug="SHOW_PATH" -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:motionDebug="SHOW_PATH" >
Depois de ativar a depuração de caminho, ao executar o app novamente, você vai encontrar os caminhos de todas as visualizações visualizadas com uma linha pontilhada.
- Os círculos representam a posição inicial ou final de uma visualização.
- Linhas representam o caminho de uma visualização.
- Os losangos representam uma
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.
- Abra
xml/step3.xml
e adicione umKeyPosition
à cena. A tagKeyPosition
é colocada dentro da tagTransition
.
step3.xml
<!-- TODO: Add KeyFrameSet and KeyPosition -->
<KeyFrameSet>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
</KeyFrameSet>
Um KeyFrameSet
é filho de uma Transition
e é um conjunto de todos os KeyFrames
, como KeyPosition
, que precisam ser aplicados durante a transição.
Como o MotionLayout
calcula o caminho para a lua entre o início e o fim, ele modifica o caminho com base no KeyPosition
especificado no KeyFrameSet
. Você pode conferir como isso modifica o caminho executando o app novamente.
Um KeyPosition
tem vários atributos que descrevem como ele modifica o caminho. As mais importantes são:
framePosition
é um número entre 0 e 100. Ele define quando aKeyPosition
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 esseKeyPosition
modifica o caminho.keyPositionType
é como esseKeyPosition
modifica o caminho. Pode serparentRelative
,pathRelative
oudeltaRelative
, conforme explicado na próxima etapa.percentX | percentY
é o quanto modificar o caminho emframePosition
(valores entre 0,0 e 1,0, com valores negativos e valores maiores que 1 são permitidos).
Você pode pensar assim: "Em framePosition
modifique o caminho de motionTarget
movendo-o em percentX
ou percentY
de acordo com as coordenadas determinadas por keyPositionType
."
Por padrão, MotionLayout
arredonda todos os cantos introduzidos ao modificar o caminho. Se você observar a animação que acabou de criar, pode ver que a Lua segue um caminho curvo na curva. É isso que você quer para a maioria das animações. Caso contrário, é possível especificar o atributo curveFit
para personalizá-lo.
Fazer um teste
Se você executar o app novamente, a animação desta etapa será mostrada.
A lua segue um arco porque passa por um KeyPosition
especificado no Transition
.
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
Você pode ler esse KeyPosition
como: "Em framePosition 50
(na metade da animação), modifique o caminho de motionTarget
@id/moon
movendo-o por 50% Y
(meio da tela) de acordo com as coordenadas determinadas por parentRelative
(todo o MotionLayout
)."
Portanto, na metade da animação, a lua precisa passar por uma KeyPosition
que está 50% abaixo na tela. Esse KeyPosition
não modifica o movimento X, então a Lua ainda vai do início ao fim horizontalmente. O MotionLayout
vai descobrir um caminho suave que passa por esse KeyPosition
enquanto se move entre o início e o fim.
Se você prestar atenção, verá que o texto dos créditos é limitado pela posição da lua. Por que ele também não está se movendo verticalmente?
<Constraint
android:id="@id/credits"
...
motion:layout_constraintBottom_toBottomOf="@id/moon"
motion:layout_constraintTop_toTopOf="@id/moon"
/>
Acontece que, mesmo que você esteja modificando o caminho que a Lua percorre, as posições inicial e final dela não a movem verticalmente. O KeyPosition
não modifica a posição inicial nem final, então o texto dos créditos fica restrito à posição final da lua.
Se você quiser que os créditos se movam com a Lua, adicione um KeyPosition
a eles ou modifique as restrições iniciais no @id/credits
.
Na próxima seção, você vai se aprofundar nos diferentes tipos de keyPositionType
no MotionLayout
.
6. Noções básicas sobre keyPositionType
Na última etapa, você usou um tipo keyPosition
de parentRelative
para deslocar o caminho em 50% da tela. O atributo keyPositionType
determina como o MotionLayout modificará o caminho de acordo com percentX
ou percentY
.
<KeyFrameSet>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
</KeyFrameSet>
Há três tipos diferentes de keyPosition
possíveis: parentRelative
, pathRelative
e deltaRelative
. Especificar um tipo altera o sistema de coordenadas pelo qual percentX
e percentY
são calculados.
O que é um sistema de coordenadas?
Com um sistema de coordenadas, é possível especificar um ponto no espaço. Eles também são úteis para descrever uma posição na tela.
Os sistemas de coordenadas MotionLayout
são um sistema de coordenadas cartesianos. Isso significa que elas têm um eixo X e um Y definido por duas linhas perpendiculares. A principal diferença entre eles é onde o eixo X vai na tela (o eixo Y é sempre perpendicular ao eixo X).
Todos os sistemas de coordenadas em MotionLayout
usam valores entre 0.0
e 1.0
nos eixos X e Y. Elas permitem valores negativos e maiores que 1.0
. Por exemplo, um valor percentX
de -2.0
significa que você vai na direção oposta do eixo X duas vezes.
Se tudo isso parece um pouco parecido com a aula de álgebra, confira as fotos abaixo.
Coordenadas parentParent
O keyPositionType
de parentRelative
usa o mesmo sistema de coordenadas da tela. Ela define (0, 0)
no canto superior esquerdo da MotionLayout
inteira e (1, 1)
no canto inferior direito.
Você pode usar parentRelative
sempre que quiser criar uma animação que se mova por todo o MotionLayout
, como o arco da lua neste exemplo.
No entanto, se você quiser modificar um caminho em relação ao movimento, por exemplo, torná-lo um pouco curvado, os outros dois sistemas de coordenadas são uma opção melhor.
Coordenadas deltaParent
Delta é um termo matemático para mudança, então deltaRelative
é uma maneira de dizer "mudança relativa". Nas coordenadas deltaRelative
, (0,0)
é a posição inicial da visualização, e (1,1)
é a posição final. Os eixos X e Y estão alinhados com a tela.
O eixo X está sempre horizontal na tela, e o eixo Y está sempre vertical na tela. Em comparação com parentRelative
, a principal diferença é que as coordenadas descrevem apenas a parte da tela em que a visualização será movida.
deltaRelative
é um ótimo sistema de coordenadas para controlar o movimento horizontal ou vertical de forma isolada. Por exemplo, você pode criar uma animação que completa apenas o movimento vertical (Y) em 50% e continua animada na horizontal (X).
Coordenadas pathParent
O último sistema de coordenadas em MotionLayout
é pathRelative
. É bem diferente dos outros dois, já que o eixo X segue a trajetória de animação do início ao fim. Portanto, (0,0)
é a posição inicial e (1,0)
é a posição final.
Por que você faria isso? É surpreendente à primeira vista, especialmente porque esse sistema de coordenadas nem sequer está alinhado ao sistema de coordenadas da tela.
O pathRelative
é muito útil para algumas coisas.
- Aceleração, desaceleração ou interrupção de uma visualização durante parte da animação. Como a dimensão X sempre corresponderá exatamente ao caminho que a visualização percorre, você pode usar um
pathRelative
KeyPosition
para mudar em qualframePosition
determinado ponto nesse caminho é alcançado. Portanto, umKeyPosition
emframePosition="50"
com umpercentX="0.1"
faria com que a animação levasse 50% do tempo para percorrer os primeiros 10% do movimento. - Adicionar um arco sutil a um caminho. Como a dimensão Y é sempre perpendicular ao movimento, alterar Y alterará o caminho para a curva em relação ao movimento geral.
- Adicionar uma segunda dimensão quando
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.
- Abra
xml/step4.xml
. Observe que ele tem as mesmas visualizações e aKeyFrame
que você adicionou na etapa anterior. - 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.
step4.xml
<!-- TODO: Add two more KeyPositions to the KeyFrameSet here -->
<KeyPosition
motion:framePosition="25"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
/>
<KeyPosition
motion:framePosition="75"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
/>
Esses KeyPositions
são aplicados em 25% e 75% da animação e fazem com que o @id/moon
se mova por um caminho a 60% da parte de cima da tela. Combinado com o KeyPosition
existente em 50%, isso cria um arco suave para a Lua seguir.
No MotionLayout
, você pode adicionar quantos KeyPositions
forem necessários para criar a trajetória de animação que quiser. A MotionLayout
aplica cada KeyPosition
no framePosition
especificado e descobre como criar um movimento suave que percorre todo o KeyPositions
.
Fazer um teste
- Execute o app novamente. Vá para a Etapa 4 para ver a animação em ação. Quando você clica na lua, ela segue o caminho do início ao fim, passando por cada
KeyPosition
especificado noKeyFrameSet
.
Explore por conta própria
Antes de passar para outros tipos de KeyFrame
, tente adicionar mais alguns KeyPositions
à KeyFrameSet
para conferir que tipo de efeitos você pode criar usando apenas KeyPosition
.
O exemplo a seguir mostra como criar um caminho complexo que se move para frente e para trás durante a animação.
step4.xml
<!-- Complex paths example: Dancing moon -->
<KeyFrameSet>
<KeyPosition
motion:framePosition="25"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
motion:percentX="0.1"
/>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
motion:percentX="0.3"
/>
<KeyPosition
motion:framePosition="75"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
motion:percentX="0.1"
/>
</KeyFrameSet>
Quando terminar de explorar o KeyPosition
, na próxima etapa, você vai acessar outros tipos de KeyFrames
.
8. Como alterar atributos durante o movimento
Criar animações dinâmicas geralmente significa mudar a size
, rotation
ou alpha
das visualizações à medida que a animação avança. MotionLayout
oferece suporte à animação de muitos atributos em qualquer visualização usando um KeyAttribute
.
Nesta etapa, você vai usar KeyAttribute
para fazer a Lua dimensionar e girar. Você também vai usar um KeyAttribute
para atrasar a exibição do texto até que a Lua esteja quase completando a viagem.
Etapa 1: redimensionar e girar com o KeyAttribute
- Abra o arquivo
xml/step5.xml
, que contém a mesma animação que você criou na etapa anterior. Para aumentar a variedade, essa tela usa uma imagem do espaço diferente como plano de fundo. - Para fazer a Lua expandir de tamanho e girar, adicione duas tags
KeyAttribute
naKeyFrameSet
emkeyFrame="50"
ekeyFrame="100"
step5.xml
<!-- TODO: Add KeyAttributes to rotate and resize @id/moon -->
<KeyAttribute
motion:framePosition="50"
motion:motionTarget="@id/moon"
android:scaleY="2.0"
android:scaleX="2.0"
android:rotation="-360"
/>
<KeyAttribute
motion:framePosition="100"
motion:motionTarget="@id/moon"
android:rotation="-720"
/>
Esses KeyAttributes
são aplicados em 50% e 100% da animação. O primeiro KeyAttribute
em 50% acontecerá na parte superior do arco e fará com que a visualização seja dobrada de tamanho e gire -360 graus (ou um círculo completo). A segunda KeyAttribute
vai terminar a segunda rotação para -720 graus (dois círculos completos) e diminuir o tamanho de volta ao normal, já que os valores scaleX
e scaleY
são padronizados para 1,0.
Assim como uma KeyPosition
, uma KeyAttribute
usa framePosition
e motionTarget
para especificar quando aplicar a KeyFrame
e qual visualização vai ser modificada. MotionLayout
interpolará entre KeyPositions
para criar animações fluidas.
KeyAttributes
são compatíveis com atributos que podem ser aplicados a todas as visualizações. Eles oferecem suporte à mudança de atributos básicos, como visibility
, alpha
ou elevation
. Você também pode mudar a rotação como está fazendo aqui, girar em três dimensões com rotateX
e rotateY
, dimensionar o tamanho com scaleX
e scaleY
ou converter a posição da visualização em X, Y ou Z.
Etapa 2: adiar a exibição dos créditos
Um dos objetivos dessa etapa é atualizar a animação para que o texto dos créditos não apareça até que a animação esteja quase completa.
- Para atrasar a exibição de créditos, defina mais um
KeyAttribute
que garanta quealpha
permaneça 0 atékeyPosition="85"
. AMotionLayout
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
- 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 noKeyFrameSet
.
Como você gira a lua em dois círculos completos, ela faz uma inversão dupla, e os créditos atrasam a exibição deles até que a animação esteja quase pronta.
Explore por conta própria
Antes de passar para o tipo final de KeyFrame
, tente modificar outros atributos padrão no KeyAttributes
. Por exemplo, tente mudar rotation
para rotationX
para ver qual animação ele produz.
Aqui está uma lista dos atributos padrão que você pode tentar:
android: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.
Definir atributos personalizados
- Para começar, abra
xml/step6.xml
, que contém a mesma animação que você criou na etapa anterior. - Para fazer a lua mudar de cor, adicione dois
KeyAttribute
com umCustomAttribute
noKeyFrameSet
emkeyFrame="0"
,keyFrame="50"
ekeyFrame="100".
step6.xml
<!-- TODO: Add Custom attributes here -->
<KeyAttribute
motion:framePosition="0"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFFFFF"
/>
</KeyAttribute>
<KeyAttribute
motion:framePosition="50"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFB612"
/>
</KeyAttribute>
<KeyAttribute
motion:framePosition="100"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFFFFF"
/>
</KeyAttribute>
Você adiciona um CustomAttribute
dentro de um KeyAttribute
. A CustomAttribute
vai ser aplicada no framePosition
especificado pela KeyAttribute
.
Dentro de CustomAttribute
, você precisa especificar um attributeName
e um valor para definir.
motion:attributeName
é o nome do setter que será chamado por esse atributo personalizado. Neste exemplo,setColorFilter
emDrawable
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
- 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 noKeyFrameSet
.
Quando você adiciona mais KeyFrames
, o método MotionLayout
muda o caminho da lua de uma linha reta para uma curva complexa, adicionando uma cambalhota dupla, redimensionamento e uma mudança de cor no meio da animação.
Em animações reais, muitas vezes você animará várias visualizações ao mesmo tempo, controlando o movimento em diferentes caminhos e velocidades. Ao especificar um KeyFrame
diferente para cada visualização, é possível coreografar animações ricas que animam várias visualizações com MotionLayout
.
10. Arrastar eventos e caminhos complexos
Nesta etapa, você vai descobrir como usar OnSwipe
com caminhos complexos. Até agora, a animação da lua foi acionada por um listener OnClick
e executada por uma duração fixa.
Controlar animações que têm caminhos complexos usando OnSwipe
, como a animação da lua que você criou nas últimas etapas, requer entender como o OnSwipe
funciona.
Etapa 1: analisar o comportamento OnSwipe
- Abra
xml/step7.xml
e encontre a declaraçãoOnSwipe
.
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
/>
- Execute o app no dispositivo e vá para a Etapa 7. Tente produzir uma animação suave arrastando a lua ao longo do caminho do arco.
Quando você executa essa animação, ela não tem uma aparência muito boa. Depois que a lua atinge o topo do arco, ela começa a pular.
Para entender o bug, considere o que acontece quando o usuário está tocando logo abaixo do topo do arco. Como a tag OnSwipe
tem um motion:touchAnchorSide="bottom"
, o MotionLayout
tentará fazer com que a distância entre o dedo e a parte de baixo da visualização seja constante em toda a animação.
No entanto, como a parte de baixo da lua nem sempre vai na mesma direção, ela sobe e depois desce. O MotionLayout
não sabe o que fazer quando o usuário acabou de passar o topo do arco. Considerando isso, já que você está rastreando o fundo da Lua, onde ele deve ser colocado quando o usuário estiver tocando aqui?
Etapa 2: use o lado direito
Para evitar bugs como esse, é importante sempre escolher uma touchAnchorId
e uma touchAnchorSide
que sempre avancem em uma direção durante toda a animação.
Nesta animação, os lados right
e left
da lua avançam pela tela em uma direção.
No entanto, tanto a bottom
quanto a top
vão ter a direção inversa. Quando OnSwipe
tentar fazer o rastreamento, ele vai ficar confuso quando a direção mudar.
- Para fazer essa animação seguir eventos de toque, mude
touchAnchorSide
pararight
.
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="right"
/>
Etapa 3: usar dragDirection
Você também pode combinar dragDirection
com touchAnchorSide
para fazer uma faixa lateral diferente do que faria normalmente. Ainda é importante que a touchAnchorSide
avance apenas em uma direção, mas você pode informar ao MotionLayout
qual direção acompanhar. Por exemplo, você pode manter touchAnchorSide="bottom"
, mas adicionar dragDirection="dragRight"
. Isso fará com que MotionLayout
rastreie a posição da parte de baixo da visualização, mas considere a localização apenas quando se mover para a direita (ignora o movimento vertical). Assim, mesmo que a parte de baixo sobe e desça, ela ainda será animada corretamente com OnSwipe
.
- Atualize o
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
- 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.
11. Movimento em execução com código
MotionLayout
pode ser usado para criar animações avançadas quando usado com CoordinatorLayout
. Nesta etapa, você vai criar um cabeçalho que pode ser recolhido usando MotionLayout
.
Etapa 1: analisar o código existente
- Para começar, abra
layout/activity_step8.xml
. - Em
layout/activity_step8.xml
, você vai notar que já foram criados umCoordinatorLayout
e umAppBarLayout
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
.
- Execute o app e vá para a Etapa 8. Quando você rola o texto, a lua não se move.
Etapa 2: fazer o MotionLayout rolar
- Para fazer a visualização da
MotionLayout
rolar assim que aNestedScrollView
rolar, adicionemotion:minHeight
emotion:layout_scrollFlags
àMotionLayout
.
activity_step8.xml
<!-- Add minHeight and layout_scrollFlags to the MotionLayout -->
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/motion_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
motion:layoutDescription="@xml/step8"
motion:motionDebug="SHOW_PATH"
android:minHeight="80dp"
motion:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed" >
- Execute o app novamente e vá para a Etapa 8. Você verá que o
MotionLayout
é recolhido conforme você rola a tela para cima. No entanto, a animação ainda não progride com base no comportamento de rolagem.
Etapa 3: mover o movimento com o código
- Abra o
Step8Activity.kt
. Edite a funçãocoordinateMotion()
para informar aoMotionLayout
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
- 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.
É 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.