Animação simples com o Jetpack Compose

1. Antes de começar

Neste codelab, você vai aprender a adicionar uma animação simples ao seu app Android. Animações podem tornar o app mais interativo, interessante e fácil de interpretar para os usuários. Animar atualizações individuais em uma tela cheia de informações pode ajudar o usuário a notar o que mudou.

Há muitos tipos de animações que podem ser usados na interface do usuário de um app. Os itens podem esmaecer à medida que aparecem ou desaparecem, podem ser movidos para dentro ou para fora da tela ou ser transformados de maneiras interessantes. Isso ajuda a tornar a IU do app expressiva e fácil de usar.

As animações também dão um visual sofisticado ao app, o que proporciona uma aparência elegante e ajuda o usuário:

As animações que recompensam o usuário por uma tarefa podem tornar os momentos importantes da jornada do usuário mais significativos.

Os elementos animados que respondem à entrada do teclado oferecem feedback para mostrar se a ação foi bem-sucedida.

Os itens de listas animadas são marcadores que informam que o conteúdo está sendo carregado.

Uma ação animada, como deslizar para abrir, incentiva gestos.

Ícones animados podem complementar o significado do ícone.

Pré-requisitos

  • Conhecimento sobre Kotlin, incluindo funções, lambdas e composições sem estado.
  • Conhecimento básico sobre como criar layouts no Jetpack Compose.
  • Conhecimento básico sobre como criar listas no Jetpack Compose.
  • Conhecimento básico do Material Design.

O que você vai aprender

  • Como criar uma animação de mola simples com o Jetpack Compose.

O que você vai criar

  • Você vai usar o app Woof do codelab "Temas do Material Design" com o Jetpack Compose e adicionar uma animação simples para reconhecer a ação do usuário.

O que é necessário

  • Ter a versão mais recente do Android Studio.
  • Conexão de Internet para fazer o download do código inicial.

2. Visão geral do app

No codelab "Temas do Material Design" com o Jetpack Compose, você usou o Material Design para criar o app Woof, que mostra uma lista de cachorros e as informações de cada um.

7252aa244a54ad90.png

Neste codelab, você vai adicionar uma animação ao app Woof com informações sobre hobbies que vão ser mostrados quando você expandir o item da lista. Você também vai adicionar uma animação de mola para animar o item da lista que está sendo expandido:

1e9cf1dbc490924a.gif

Acessar o código inicial

Para começar, faça o download do código inicial:

Outra opção é clonar o repositório do GitHub:

$ git clone
https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git
$ cd basic-android-kotlin-compose-training-woof
$ git checkout material

Procure o código no repositório do GitHub do Woof app (link em inglês).

3. Adicionar ícone de "Expandir mais"

O primeiro passo para criar uma animação de mola é adicionar um ícone de "Expandir mais" f88173321938c003.png. O ícone de expandir mais fornece um botão para que o usuário expanda o item da lista.

9fbd3fb0daf35fd3.png

Ícones

Ícones são símbolos que ajudam os usuários a entender a interface com uma comunicação visual da função pretendida, inspirada em objetos do mundo físico. Muitas vezes, o design do ícone reduz o nível de detalhes para o mínimo necessário para ser facilmente reconhecido pelo usuário. Por exemplo, um lápis no mundo físico é usado para escrever. Portanto, o ícone correspondente geralmente indica criar ou editar.

Foto de Angelina Litvin no Unsplash (links em inglês).

Ícone de lápis preto e branco

O Material Design oferece vários ícones organizados em categorias comuns para a maioria das suas necessidades.

bfdb896506790c69.png

Adicionar dependência do Gradle

Adicione a dependência da biblioteca material-icons-extended ao seu projeto. Você vai usar os ícones Icons.Filled.ExpandLess 30c384f00846e69b.png e Icons.Filled.ExpandMore f88173321938c003.png desta biblioteca.

  1. No painel do projeto, abra Gradle Scripts > build.gradle (Module: Woof.app).

f7fe58e936bbad3e.png

  1. Role até o fim do arquivo build.gradle (Module: Woof.app). No bloco dependencies{}, adicione esta linha:
implementation "androidx.compose.material:material-icons-extended:$compose_version"

Adicionar o ícone de composição

Adicione uma função para mostrar o ícone de expandir da biblioteca de ícones do Material Design e use-a como um botão.

  1. Em MainActivity.kt, após a função DogItem(), crie uma nova função de composição com o nome DogItemButton().
  2. Transmita um Boolean para o estado expandido, uma expressão lambda para o evento de clique do botão e um Modifier opcional, desta forma:
@Composable
private fun DogItemButton(
    expanded: Boolean,
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {

}
  1. Na função DogItemButton(), adicione uma função de composição IconButton() que aceite um parâmetro com o nome onClick, uma expressão lambda usando a sintaxe de lambda final, que é invocada quando esse ícone é pressionado e defina a lambda no argumento transmitido em onClick.
@Composable
private fun DogItemButton(
   // ...
) {
   IconButton(onClick = onClick) {

   }
}
  1. No bloco lambda IconButton(), adicione um elemento de composição Icon com um parâmetro com o nome imageVector e o defina como Icons.Filled.ExpandMore. Este é o botão de ícone f88173321938c003.png que vai aparecer no fim do item da lista. O Android Studio mostra um aviso para os parâmetros de composição Icon() que você vai corrigir nas próximas etapas.
  2. Adicione o parâmetro com o nome tint e defina a cor do ícone como MaterialTheme.colors.secondary. Adicione o parâmetro com o nome contentDescription e o defina como o recurso de string R.string.expand_button_content_description.
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore

IconButton(onClick = onClick) {
   Icon(
       imageVector = Icons.Filled.ExpandMore,
       tint = MaterialTheme.colors.secondary,
       contentDescription = stringResource(R.string.expand_button_content_description)
   )
}

Mostrar o ícone

Adicione o elemento de composição DogItemButton() ao layout para mostrá-lo.

  1. No início da função de composição DogItem(), adicione um var para salvar o estado expandido do item da lista. Defina o valor inicial como false.
var expanded by remember { mutableStateOf(false) }
  1. No final do bloco Row da função de composição DogItem(), chame a função DogItemButton() e transmita um estado expandido e uma expressão lambda vazia ao callback. Esse código mostra o botão de ícone no item da lista.
  2. Para mostrar o botão de ícone no item da lista, na função de composição DogItem() ao final do bloco Row, após uma chamada para DogInformation(), faça uma chamada para o DogItemButton(). Transmita o estado expanded e uma expressão lambda vazia ao callback. Você vai definir essa função lambda em uma etapa posterior.
Row(
   modifier = Modifier
       .fillMaxWidth()
       .padding(8.dp)
) {
   DogIcon(dog.imageResourceId)
   DogInformation(dog.name, dog.age)
   DogItemButton(
      expanded = expanded,
      onClick = { }
   )
}
  1. Crie e atualize a visualização no painel Design.

a49643f08701a8d.png

O botão "Expandir mais" não está alinhado ao fim do item da lista. Isso vai ser corrigido na próxima etapa.

Alinhar o botão "Expandir mais"

Para alinhar o botão "Expandir mais" ao fim do item da lista, é necessário adicionar um espaçador ao layout com o atributo Modifier.weight().

No app Woof, cada linha de itens da lista contém uma imagem e informações de um cachorro, além de um botão "Expandir mais". Você vai adicionar um espaçador de composição antes do botão "Expandir mais" com o peso 1f para alinhar corretamente o ícone do botão. Como o espaçador é o único elemento filho com peso na linha, ele preenche o espaço restante depois de medir o comprimento do outro elemento filho que não tem um peso.

6c2b523849f0f626.png

Adicionar o espaçador à linha do item da lista

  1. No final do bloco Row na função de composição DogItem(), adicione um Spacer. Transmita um Modifier com weight(1f). O Modifier.weight() faz com que o espaçador ocupe o espaço restante na linha.
Row(
   modifier = Modifier
       .fillMaxWidth()
       .padding(8.dp)
) {
   DogIcon(dog.imageResourceId)
   DogInformation(dog.name, dog.age)
   Spacer(Modifier.weight(1f))
   DogItemButton(
      expanded = expanded,
      onClick = { }
   )
}
  1. Crie e atualize a visualização no painel Design. O botão "Expandir mais" agora está alinhado ao fim do item da lista.

f6a140413de9ad54.png

4. Adicionar uma função de composição para mostrar um hobby

Nesta tarefa, você vai adicionar os elementos Text de composição para mostrar as informações de hobbies do cachorro.

66ea5cc5c7253d55.png

  1. Crie uma nova função de composição com o nome DogHobby(), que usa um ID de recurso de string de hobby de um cachorro e um Modifier opcional.
  2. Na função DogHobby(), crie uma coluna com os seguintes atributos de padding para adicionar espaço entre a coluna e os elementos de composição filhos.
import androidx.annotation.StringRes

@Composable
fun DogHobby(@StringRes dogHobby: Int, modifier: Modifier = Modifier) {
   Column(
       modifier = modifier.padding(
           start = 16.dp,
            top = 8.dp,
            bottom = 16.dp,
            end = 16.dp
       )
   ) { }
}
  1. Dentro do bloco de colunas, adicione dois elementos de composição Text: um para mostrar o texto About (Sobre) acima das informações do hobby e outro para as informações.

3051387c4b9c7455.png

  1. Para o texto About, defina o estilo como h3 (Título 3) e a cor como onBackground. Para as informações sobre hobbies, defina o estilo como body1 (Corpo 1).
Column(
   modifier = modifier.padding(
       //..
   )
) {
   Text(
       text = stringResource(R.string.about),
       style = MaterialTheme.typography.h3,
   )
   Text(
       text = stringResource(dogHobby),
       style = MaterialTheme.typography.body1,
   )
}
  1. A função de composição DogHobby() vai ficar assim.
@Composable
fun DogHobby(@StringRes dogHobby: Int, modifier: Modifier = Modifier) {
   Column(
       modifier = modifier.padding(
           start = 16.dp,
           top = 8.dp,
           bottom = 16.dp,
           end = 16.dp
       )
   ) {
       Text(
           text = stringResource(R.string.about),
           style = MaterialTheme.typography.h3
       )
       Text(
           text = stringResource(dogHobby),
           style = MaterialTheme.typography.body1
       )
   }
}
  1. Para mostrar a função de composição DogHobby(), em DogItem(), una a Row com uma Column. Chame a função DogHobby() transmitindo dog.hobbies como parâmetro, depois transmita a Row como o segundo elemento filho.
Column() {
   Row(
       //..
   ) {
       //..
   }
   DogHobby(dog.hobbies)
}

A função DogItem() completa vai ficar assim:

@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
   var expanded by remember { mutableStateOf(false) }
   Card(
        elevation = 4.dp,
       modifier = modifier.padding(8.dp)
   ) {
       Column() {
           Row(
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(8.dp)
           ) {
               DogIcon(dog.imageResourceId)
               DogInformation(dog.name, dog.age)
               Spacer(Modifier.weight(1f))
               DogItemButton(
                   expanded = expanded,
                   onClick = { expanded = !expanded },
               )
           }
           DogHobby(dog.hobbies)
       }
   }
}
  1. Crie e atualize a visualização no painel Design. Observe os hobbies do cachorro.

9e2e68a4bc4a8ae1.png

5. Mostrar ou ocultar o hobby ao clicar no botão

Seu app tem um botão "Expandir mais" para cada item da lista, mas ele ainda não faz nada. Nesta seção, você vai adicionar a opção de ocultar ou revelar as informações de hobbies quando o usuário clicar no botão.

  1. Na função de composição DogItem(), na chamada de função DogItemButton(), defina a expressão lambda onClick(), mude o valor do estado booleano expanded para true quando o botão receber um clique e volte o valor para false se o botão for clicado novamente.
DogItemButton(
   expanded = expanded,
   onClick = { expanded = !expanded }
)
  1. Na função DogItemButton(), junte a chamada de função DogHobby() com uma verificação if no booleano expanded.
// No need to copy over
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
   var expanded by remember { mutableStateOf(false) }
   Card(
       //..
   ) {
       Column() {
           Row(
               //..
           ) {
               //..
           }
           if (expanded) {
               DogHobby(dog.hobbies)
           }
       }
   }
}

No código acima, as informações de hobbies do cachorro só são mostradas se o valor de expanded for true.

  1. A prévia pode mostrar como a IU vai ficar e também permite interagir com ela. Para interagir com a visualização da IU, clique no botão Modo interativo 42379dbe94a7a497.png no canto direito de cima do painel Design. Isso inicia a visualização no modo interativo.

2a4ad1f3d2d0bff7.png

  1. Interaja com a visualização clicando no botão "Expandir mais". As informações sobre o hobby ficam ocultas e são reveladas quando você clica no botão "Expandir mais".

6ee6774b5b14c7e1.gif

O ícone do botão "Expandir mais" continua o mesmo quando o item da lista é expandido. Para uma melhor experiência do usuário, mude o ícone para que ExpandMore mostre a seta para baixo c761ef298c2aea5a.png e ExpandLess mostre a seta para cima b380f933be0b6ff4.png.

  1. Na função DogItemButton(), atualize o valor imageVector com base no estado expanded desta maneira:
import androidx.compose.material.icons.filled.ExpandLess

@Composable
private fun DogItemButton(
   //..
) {
   IconButton(onClick = onClick) {
       Icon(
           imageVector = if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
           //..
       )
   }
}
  1. Execute o app em um dispositivo ou emulador, ou use o modo interativo na visualização novamente. O ícone alterna entre as imagens ExpandMore c761ef298c2aea5a.png e ExpandLess b380f933be0b6ff4.png.

bf8bb280a774a6d4.gif

Bom trabalho ao atualizar o ícone!

Ao expandir o item da lista, você notou uma mudança repentina na altura? A mudança repentina de altura não dá a impressão de um app sofisticado. Para resolver esse problema, você vai adicionar uma animação ao app.

6. Adicionar animação

As animações podem adicionar dicas visuais que mostram aos usuários o que está acontecendo no app. Elas são especialmente úteis quando a IU muda de estado, por exemplo, quando um novo conteúdo é carregado ou novas ações ficam disponíveis. As animações também podem adicionar um visual sofisticado ao app.

Nesta seção, você vai adicionar uma animação de mola para a mudança na altura do item da lista.

Animação de mola

A animação de mola é baseada na física e impulsionada por uma força elástica. Com ela, o valor e a velocidade do movimento são calculados com base na força aplicada.

Por exemplo, se você arrastar um ícone do app pela tela e o soltar levantando o dedo, ele vai voltar para o local original movido por uma força invisível.

A animação abaixo mostra o efeito de mola. Quando o ícone é solto, ele pula, imitando uma mola.

7b52f63dc639c28d.gif

Efeito de mola

A força de mola é guiada pelas duas propriedades abaixo:

  • Proporção de amortecimento: a elasticidade da mola.
  • Nível de rigidez: a rigidez da mola, ou seja, a velocidade com que ela se move em direção ao final.

Confira abaixo alguns exemplos de animações com proporções de amortecimento e níveis de rigidez diferentes.

Efeito de molaElasticidade alta

Efeito de molaSem elasticidade

Alta rigidez

Rigidez muito baixa

Agora, você vai adicionar uma animação de mola ao app.

  1. Em MainActivity.kt, na função DogItem(), adicione um parâmetro modifier ao layout Column.
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
   //..
   Card(
       //..
   ) {
       Column(
          modifier = Modifier
       ){
           //..
       }
   }
}

Observe a chamada de função DogHobby() na função de composição DogItem(). As informações de hobbies do cachorro são incluídas na composição com base no valor booleano expanded. A altura do item da lista muda caso as informações de hobbies estejam visíveis ou ocultas. Você vai usar o modificador animateContentSize para adicionar uma transição entre a altura nova e a antiga.

// No need to copy over
@Composable
fun DogItem(...) {

        //..
           if (expanded) {
               DogHobby(dog.hobbies)
           }
}
  1. Encadeie o modificador com animateContentSize para animar a mudança de tamanho (altura do item da lista).
import androidx.compose.animation.animateContentSize

Column(
           modifier = Modifier
               .animateContentSize()
       ) {
            //..
       }

Com a implementação atual, você está animando a altura do item da lista no app. No entanto, a animação é tão sutil que fica difícil perceber quando você executa o app. Para resolver isso, use um parâmetro opcional animationSpec que permite personalizar a animação.

  1. Adicione o parâmetro animationSpec à chamada de função animateContentSize(). Defina-o como uma animação de mola com os parâmetros DampingRatioMediumBouncy e StiffnessLow.
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring

Column(
   modifier = Modifier
       .animateContentSize(
           animationSpec = spring(
               dampingRatio = Spring.DampingRatioMediumBouncy,
               stiffness = Spring.StiffnessLow
           )
       )
)
  1. Crie e atualize a visualização no painel Design. Use o modo interativo ou execute seu app em um emulador ou dispositivo para conferir a animação de mola em ação.

8cf711b8821b4696.gif

Execute novamente o app no emulador ou dispositivo e aproveite o belo app com animações.

1e9cf1dbc490924a.gif

7. (Opcional) Fazer experimentos com outras animações

animate*AsState

As funções animate*AsState() são uma das APIs de animação mais simples do Compose, usadas para animar um único valor. Somente o valor final (ou valor de segmentação) precisa ser informado, e a API inicia a animação do valor atual para o valor especificado.

O Compose oferece funções animate*AsState() para Float, Color, Dp, Size, Offset e Int, entre outras. Você pode adicionar suporte a outros tipos de dados facilmente com animateValueAsState(), que usa um tipo genérico.

Use a função animateColorAsState() para animar a cor quando um item da lista estiver expandido.

Dica:

  1. Declare uma cor e delegue a inicialização dela à função animateColorAsState().
  2. Defina o parâmetro targetValue, dependendo do valor booleano expanded.
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
   //..
   val color by animateColorAsState(
       targetValue = if (expanded) Green25 else MaterialTheme.colors.surface,
   )
   Card(
       //..
   ) {...}
}
  1. Defina a color declarada acima como o modificador de segundo plano como a Column.
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
   //..
   Card(
       //..
   ) {
       Column(
           modifier = Modifier
               .animateContentSize(
                   //..
                   )
               )
               .background(color = color)
       ) {...}
}

8. Acessar o código da solução

Para fazer o download do código do codelab concluído, use este comando git:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git

Se preferir, você pode fazer o download do repositório como um arquivo ZIP, descompactar e abrir no Android Studio.

Se você quiser conferir o código da solução, acesse o GitHub (link em inglês).

9. Conclusão

Parabéns! Você adicionou um botão para ocultar e mostrar informações sobre o cachorro. Você melhorou a experiência do usuário usando animações de mola. Você também aprendeu a usar o modo interativo no painel de Design.

Você também pode testar um tipo diferente de animação do Jetpack Compose. Não se esqueça de compartilhar seu trabalho nas redes sociais com a hashtag #AndroidBasics.

Saiba mais