Capítulo 19: Animações Fluidas no Compose: AnimatedVisibility, Animatable e Molas
Capítulo 19: Animações Fluidas no Compose: AnimatedVisibility, Animatable e Molas
Especialização em Mobile Nativo com Android • Kotlin 2.0 & Jetpack Compose
🗺️ Mapa Conceitual da Hierarquia de APIs de Animação do Compose
flowchart TD
subgraph HIGH_LEVEL["1. APIs de Alto Nível (Comportamento Declarativo Imediato)"]
AV["AnimatedVisibility (Entrada / Saída de Nós: fadeIn, slideIn, expandVertically)"]
AC["AnimatedContent (Transição Fluida entre Estados Diferentes)"]
CF["Crossfade (Alternância Simples com Opacidade Cruzada)"]
end
subgraph MID_LEVEL["2. APIs de Nível Intermediário (Animação de Valores Únicos)"]
STATE_ANIM["animate*AsState (animateFloatAsState, animateColorAsState, animateDpAsState)"]
ACS["animateContentSize (Redimensionamento Automático de Layouts)"]
end
subgraph LOW_LEVEL["3. APIs de Baixo Nível (Controle Fino e Concorrência Coroutine)"]
ANIMATABLE["Animatable<T, V> (Controle manual de snapping, stop e física de gestos)"]
TRANS["updateTransition (Sincronização de Múltiplas Propriedades Simultâneas)"]
INF["rememberInfiniteTransition (Pulsações, Shimmers e Loaders Contínuos)"]
end
subgraph SPECS["Especificações de Movimento (AnimationSpec)"]
SPRING["spring(dampingRatio, stiffness) -> Física Natural de Molas (Padrão Ouro)"]
TWEEN["tween(durationMillis, easing) -> Curvas Temporais Interpoladas"]
KEYFRAMES["keyframes { ... } -> Pontos de Animação Específicos"]
end
HIGH_LEVEL -.-> SPECS
MID_LEVEL -.-> SPECS
LOW_LEVEL -.-> SPECS
style HIGH_LEVEL fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style MID_LEVEL fill:#b3e5fc,stroke:#0288d1,stroke-width:2px
style LOW_LEVEL fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style SPRING fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. A Filosofia de Movimento no Jetpack Compose
No design de software móvel contemporâneo, a animação deixou de ser um mero enfeite decorativo para se tornar parte vital da ergonomia de software. Transições fluidas comunicam continuidade espacial, guiam o foco de atenção do usuário e confirmam fisicamente as interações de toque:
- Física Baseada em Molas (
Spring) vs Interpolações Lineares: Animações temporizadas clássicas (tweencom duração fixa de 300 ms) parecem robóticas e não reagem bem se o usuário interromper o movimento no meio do caminho. A especificaçãospring()baseia-se em leis da física clássica (rigidez e amortecimento). Se o usuário tocar ou arrastar um elemento enquanto ele ainda se move, a mola preserva a velocidade vetorial acumulada, gerando uma resposta incrivelmente natural. - Abstração Declarativa com
AnimatedVisibility: Em vez de manipularalpha,scaleevisibilitycom dezenas de linhas imperativas deViewPropertyAnimator, o Compose permite combinar transições através do operador+(ex:fadeIn() + expandVertically()), cuidando do descarte do nó na árvore de renderização apenas após o término do fade. Animatablee Concorrência: Para animações orientadas a gestos (como arrastar um card para o lado para dispensá-lo), oAnimatablepermite iniciar a animação dentro de uma coroutine viaanimateTo(). Se um novo toque ocorrer, o Compose cancela suavemente a coroutine anterior e recalcula a trajetória instantaneamente.
📊 2. Comparativo de Especificações de Animação (AnimationSpec)
| Especificação | Parâmetros Principais | Comportamento Cinético | Recomendação de Uso |
|---|---|---|---|
spring() |
dampingRatio, stiffness |
Baseado em física de massa e amortecimento | Padrão Ouro: gestos, expansão de cards, toques |
tween() |
durationMillis, easing |
Duração temporal fixa com curvas Bezier | Fades de tela cheia, splash screens, loaders temporizados |
keyframes() |
durationMillis, at() |
Define valores intermediários exatos na linha do tempo | Efeitos visuais de chacoalhar (shake) em senhas inválidas |
repeatable() |
iterations, animation |
Repete uma animação por N ciclos | Badges pulsantes e chamadas de atenção temporárias |
infiniteRepeatable() |
animation, repeatMode |
Execução contínua sem término | Shimmers de carregamento (Skeleton loaders) |
💻 3. Laboratório Prático: Card Interativo com Efeito Shimmer e Mola
O exemplo abaixo demonstra a criação de uma transição fluida combinando física de molas (spring), redimensionamento automático de layout e efeito de rotação de chevron:
package br.com.portal.mobile.android.cap19
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@Composable
fun CardExpansivelAnimado(
titulo: String,
conteudoOculto: String,
modifier: Modifier = Modifier
) {
var expandido by remember { mutableStateOf(false) }
// 1. Animação de Rotação com Mola Levemente Elástica
val anguloRotacao by animateFloatAsState(
targetValue = if (expandido) 180f else 0f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
),
label = "RotacaoChevron"
)
// 2. Animação Cromática do Fundo do Card
val corFundo by animateColorAsState(
targetValue = if (expandido) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant,
animationSpec = spring(stiffness = Spring.StiffnessLow),
label = "CorFundoAnimada"
)
Card(
modifier = modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.clickable { expandido = !expandido }
.animateContentSize( // Ajusta altura suavemente quando o texto se expande
animationSpec = spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessLow
)
),
colors = CardDefaults.cardColors(containerColor = corFundo)
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = titulo,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = null,
modifier = Modifier.rotate(anguloRotacao)
)
}
// 3. Entrada e Saída Suave do Conteúdo Condicional
AnimatedVisibility(
visible = expandido,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
Column {
Spacer(modifier = Modifier.height(12.dp))
Text(
text = conteudoOculto,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
}
⚠️ 4. Armadilhas Comuns em Animações Mobile
- Recompor a Árvore Inteira em Animações Contínuas: Se você passar um valor animado de 60 FPS diretamente para um Composable de texto ou layout, todo o nó será recomposto a cada frame. Utilize sempre modificadores na fase de desenho como
Modifier.graphicsLayer { rotationZ = angulo }ouModifier.drawBehind, que operam direto na GPU sem recompor a árvore. - Ignorar as Opções de Acessibilidade do Sistema: Usuários com labirintite ou sensibilidade motora frequentemente ativam a opção “Remover Animações” nas configurações de Acessibilidade do Android. Verifique se seu design respeita essas preferências sem quebrar a lógica de navegação.
- Animações Demasiadamente Longas: Transições que duram mais de 400 milissegundos passam a sensação de que o aplicativo é lento e travado. Para transições de interface rotineiras, mantenha durações entre 150 ms e 300 ms.