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 usadas na interface do usuário de um app. Os itens podem aparecer e desaparecer, podem ser movidos para dentro ou para fora da tela ou podem ser transformados de maneiras interessantes. Isso ajuda a tornar a interface 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.
Pré-requisitos
- Conhecimento sobre Kotlin, incluindo funções, lambdas e elementos combináveis 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
- A versão estável 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.
Neste codelab, você vai adicionar uma animação ao app Woof com informações sobre hobbies que serão mostrados quando você abrir o item da lista. Você também vai adicionar uma animação de mola no item da lista que está sendo aberto.
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 "Mostrar mais"
Nesta seção, você vai adicionar os ícones Mostrar mais e Mostrar menos ao app.
Ícones
Ícones são símbolos que ajudam os usuários a entender a interface do usuário com uma comunicação visual da função pretendida. Eles costumam se inspirar em objetos do mundo físico que um usuário pode já ter visto. 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). |
O Material Design oferece vários ícones organizados em categorias comuns para a maioria das suas necessidades.
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
e Icons.Filled.ExpandMore
desta biblioteca.
- No painel Project, abra Gradle Scripts > build.gradle.kts (Module :app).
- Role até o fim do arquivo
build.gradle.kts (Module :app)
. No blocodependencies{}
, adicione esta linha:
implementation("androidx.compose.material:material-icons-extended")
Adicionar o elemento combinável do ícone
Adicione uma função para exibir o ícone Mostrar mais da biblioteca de ícones do Material Design e use-a como um botão.
- Em
MainActivity.kt
, após a funçãoDogItem()
, crie uma nova função combinável com o nomeDogItemButton()
. - Transmita um
Boolean
para o estado aberto, uma expressão lambda para o gerenciador onClick do botão e umModifier
opcional, desta forma:
@Composable
private fun DogItemButton(
expanded: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
}
- Na função
DogItemButton()
, adicione um elemento combinávelIconButton()
que aceite um parâmetroonClick
, uma lambda usando a sintaxe de lambda final, que é invocada quando esse ícone é pressionado, e ummodifier
opcional. Defina os atributosIconButton's onClick
emodifier value parameters
iguais àqueles transmitidos aDogItemButton
.
@Composable
private fun DogItemButton(
expanded: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
){
IconButton(
onClick = onClick,
modifier = modifier
) {
}
}
- No bloco da lambda
IconButton()
, adicione um elemento combinávelIcon
e definaimageVector value-parameter
comoIcons.Filled.ExpandMore
. Essa informação será mostrada no final do item da lista . O Android Studio mostra um aviso para os parâmetros combináveisIcon()
que você vai corrigir na próxima etapa.
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.Icons
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
IconButton(
onClick = onClick,
modifier = modifier
) {
Icon(
imageVector = Icons.Filled.ExpandMore
)
}
- Adicione o parâmetro com o valor
tint
e defina a cor do ícone comoMaterialTheme.colorScheme.secondary
. Adicione o parâmetro com o nomecontentDescription
e o defina como o recurso de stringR.string.expand_button_content_description
.
IconButton(
onClick = onClick,
modifier = modifier
){
Icon(
imageVector = Icons.Filled.ExpandMore,
contentDescription = stringResource(R.string.expand_button_content_description),
tint = MaterialTheme.colorScheme.secondary
)
}
Mostrar o ícone
Adicione o elemento combinável DogItemButton()
ao layout para mostrá-lo.
- No início de
DogItem()
, adicione umavar
para salvar o estado aberto do item da lista. Defina o valor inicial comofalse
.
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
var expanded by remember { mutableStateOf(false) }
- Mostre o botão do ícone no item da lista. No elemento combinável
DogItem()
, adicioneDogItemButton()
no fim do blocoRow
, depois da chamada paraDogInformation()
. Transmita o estadoexpanded
e uma expressão lambda vazia ao callback. Você definirá a açãoonClick
em uma próxima etapa.
Row(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
DogItemButton(
expanded = expanded,
onClick = { /*TODO*/ }
)
}
- Confira o
WoofPreview()
no painel Design.
O botão para mostrar 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 elemento combinável Spacer
antes do botão "Mostrar 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.
Adicionar o espaçador à linha do item da lista
- Em
DogItem()
, entreDogInformation()
eDogItemButton()
, adicione umSpacer
. Transmita umModifier
comweight(1f)
. OModifier.weight()
faz com que o espaçador ocupe o espaço restante na linha.
import androidx.compose.foundation.layout.Spacer
Row(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
Spacer(modifier = Modifier.weight(1f))
DogItemButton(
expanded = expanded,
onClick = { /*TODO*/ }
)
}
- Confira o
WoofPreview()
no painel Design. O botão para mostrar mais agora está alinhado ao fim do item da lista.
4. Adicionar uma função combinável para mostrar um hobby
Nesta tarefa, você vai adicionar os elementos combináveis Text
para mostrar as informações de hobbies do cachorro.
- Crie uma nova função combinável com o nome
DogHobby()
, que usa um ID de recurso de string de hobby de um cachorro e umModifier
opcional.
@Composable
fun DogHobby(
@StringRes dogHobby: Int,
modifier: Modifier = Modifier
) {
}
- Na função
DogHobby()
, crie umaColumn
e transmita o modificador transmitido paraDogHobby()
.
@Composable
fun DogHobby(
@StringRes dogHobby: Int,
modifier: Modifier = Modifier
){
Column(
modifier = modifier
) {
}
}
- Dentro do bloco
Column
, adicione dois elementos combináveisText
: um para mostrar o texto About acima das informações do hobby e outro para as informações.
Defina o text
do primeiro arquivo como o about
do arquivo strings.xml e defina o style
como labelSmall
. Defina o text
do segundo como dogHobby
, que é transmitido e defina o style
como bodyLarge
.
Column(
modifier = modifier
) {
Text(
text = stringResource(R.string.about),
style = MaterialTheme.typography.labelSmall
)
Text(
text = stringResource(dogHobby),
style = MaterialTheme.typography.bodyLarge
)
}
- Em
DogItem()
, o elemento combinávelDogHobby()
ficará abaixo daRow
que contémDogIcon()
,DogInformation()
,Spacer()
eDogItemButton()
. Para fazer isso, una aRow
com umaColumn
para que o hobby possa ser adicionado abaixo daRow
.
Column() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
Spacer(modifier = Modifier.weight(1f))
DogItemButton(
expanded = expanded,
onClick = { /*TODO*/ }
)
}
}
- Adicione
DogHobby()
depois daRow
como um segundo filho daColumn
. Transmitadog.hobbies
, que contém o hobby exclusivo do cachorro transmitido e ummodifier
com o padding do elemento combinávelDogHobby()
.
Column() {
Row() {
...
}
DogHobby(
dog.hobbies,
modifier = Modifier.padding(
start = dimensionResource(R.dimen.padding_medium),
top = dimensionResource(R.dimen.padding_small),
end = dimensionResource(R.dimen.padding_medium),
bottom = dimensionResource(R.dimen.padding_medium)
)
)
}
A função DogItem()
completa vai ficar assim:
@Composable
fun DogItem(
dog: Dog,
modifier: Modifier = Modifier
) {
var expanded by remember { mutableStateOf(false) }
Card(
modifier = modifier
) {
Column() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
Spacer(Modifier.weight(1f))
DogItemButton(
expanded = expanded,
onClick = { /*TODO*/ },
)
}
DogHobby(
dog.hobbies,
modifier = Modifier.padding(
start = dimensionResource(R.dimen.padding_medium),
top = dimensionResource(R.dimen.padding_small),
end = dimensionResource(R.dimen.padding_medium),
bottom = dimensionResource(R.dimen.padding_medium)
)
)
}
}
}
- Confira o
WoofPreview()
no painel Design. Observe os hobbies do cachorro.
5. Mostrar ou ocultar o hobby ao clicar no botão
Seu app tem um botão Mostrar 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.
- Na função combinável
DogItem()
, na chamada de funçãoDogItemButton()
, defina a expressão lambdaonClick()
, mude o valor do estado booleanoexpanded
paratrue
quando o botão receber um clique e volte o valor parafalse
se o botão for clicado novamente.
DogItemButton(
expanded = expanded,
onClick = { expanded = !expanded }
)
- Na função
DogItem()
, junte a chamada de funçãoDogHobby()
com uma verificaçãoif
no booleanoexpanded
.
@Composable
fun DogItem(
dog: Dog,
modifier: Modifier = Modifier
) {
var expanded by remember { mutableStateOf(false) }
Card(
...
) {
Column(
...
) {
Row(
...
) {
...
}
if (expanded) {
DogHobby(
dog.hobbies, modifier = Modifier.padding(
start = dimensionResource(R.dimen.padding_medium),
top = dimensionResource(R.dimen.padding_small),
end = dimensionResource(R.dimen.padding_medium),
bottom = dimensionResource(R.dimen.padding_medium)
)
)
}
}
}
}
Agora, as informações de hobbies do cachorro só serão mostradas se o valor de expanded
for true
.
- A prévia pode mostrar como a interface vai ficar e também permite interagir com ela. Para interagir com a visualização da interface, passe o cursor sobre o texto WoofPreview no painel Design e clique no botão do Modo interativo no canto superior direito do painel Design. Isso inicia a visualização no Modo interativo.
- Interaja com a visualização clicando no botão para mostrar mais. As informações sobre o hobby ficam ocultas e são reveladas quando você clica no botão "Mostrar mais".
O ícone do botão "Mostrar mais" continua o mesmo quando o item da lista é aberto. Para uma melhor experiência do usuário, mude o ícone para que ExpandMore
mostre a seta para baixo e ExpandLess
mostre a seta para cima .
- Na função
DogItemButton()
, adicione uma instruçãoif
que atualize o valorimageVector
com base no estadoexpanded
desta forma:
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,
...
)
}
}
Note como você programou if-else
no snippet de código anterior.
if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore
Isso é o mesmo que usar as chaves { } neste código:
if (expanded) {
`Icons.Filled.ExpandLess`
} else {
`Icons.Filled.ExpandMore`
}
As chaves são opcionais quando há uma única linha de código para a instrução if
-else
.
- Execute o app em um dispositivo ou emulador, ou use o modo interativo na visualização novamente. O ícone alterna entre as imagens
ExpandMore
eExpandLess
.
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.
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.
Elasticidade alta | Sem elasticidade |
Alta rigidez | Rigidez muito baixa |
Observe a chamada de função DogHobby()
na função combinável 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. Atualmente, a transição é desagradável. Nesta seção, você vai usar o modificador animateContentSize
para adicionar uma transição mais suave entre os estados abertos e fechados.
// No need to copy over
@Composable
fun DogItem(...) {
...
if (expanded) {
DogHobby(
dog.hobbies,
modifier = Modifier.padding(
start = dimensionResource(R.dimen.padding_medium),
top = dimensionResource(R.dimen.padding_small),
end = dimensionResource(R.dimen.padding_medium),
bottom = dimensionResource(R.dimen.padding_medium)
)
)
}
}
- Em
MainActivity.kt
, na funçãoDogItem()
, adicione um parâmetromodifier
ao layoutColumn
.
@Composable
fun DogItem(
dog: Dog,
modifier: Modifier = Modifier
) {
...
Card(
...
) {
Column(
modifier = Modifier
){
...
}
}
}
- 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()
)
Na 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.
- Para o Woof, a animação precisa ser suave e não sacudir. Para fazer isso, adicione o parâmetro
animationSpec
à chamada de funçãoanimateContentSize()
. Defina-o como uma animação de mola comDampingRatioNoBouncy
para que não fique pulando e um parâmetroStiffnessMedium
para tornar a mola um pouco mais rígida.
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
Column(
modifier = Modifier
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessMedium
)
)
)
- Confira
WoofPreview()
no painel Design e use o modo interativo ou execute o app em um emulador ou dispositivo para conferir a animação de mola em ação.
Parabéns! Curta seu app com animações.
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.
Tente usar a função animateColorAsState()
para mudar a cor quando um item da lista for aberto.
- Em
DogItem()
, declare uma cor e delegue a inicialização dela à funçãoanimateColorAsState()
.
import androidx.compose.animation.animateColorAsState
@Composable
fun DogItem(
dog: Dog,
modifier: Modifier = Modifier
) {
var expanded by remember { mutableStateOf(false) }
val color by animateColorAsState()
...
}
- Defina o parâmetro
targetValue
, dependendo do valor booleanoexpanded
. Se o item da lista estiver aberto, defina-o como a cor dotertiaryContainer
. Caso contrário, defina-o como a cor doprimaryContainer
.
import androidx.compose.animation.animateColorAsState
@Composable
fun DogItem(
dog: Dog,
modifier: Modifier = Modifier
) {
var expanded by remember { mutableStateOf(false) }
val color by animateColorAsState(
targetValue = if (expanded) MaterialTheme.colorScheme.tertiaryContainer
else MaterialTheme.colorScheme.primaryContainer,
)
...
}
- Defina a
color
como o modificador do plano de fundo como aColumn
.
@Composable
fun DogItem(
dog: Dog,
modifier: Modifier = Modifier
) {
...
Card(
...
) {
Column(
modifier = Modifier
.animateContentSize(
...
)
)
.background(color = color)
) {...}
}
- Observe como a cor muda quando o item da lista é aberto. Os itens da lista não abertos têm a cor do
primaryContainer
, e os itens da lista abertos têm a cor dotertiaryContainer
.
8. Acessar o código da solução
Para baixar o 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 baixar o 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 (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
- Animação do Jetpack Compose
- Codelab: Como animar elementos no Jetpack Compose
- Vídeo: Animação reimaginada (em inglês)
- Vídeo: Jetpack Compose: animação (em inglês)