O Compose tem muitos mecanismos de animação integrados, e pode ser difícil saber qual deles escolher. Confira abaixo uma lista de casos de uso comuns de animação. Para informações mais detalhadas sobre o conjunto completo de diferentes opções de API disponíveis para você, leia a documentação completa de animações do Compose.
Animar propriedades comuns que podem ser compostas
O Compose fornece APIs convenientes que permitem solucionar muitos casos de uso comuns de animação. Esta seção demonstra como animar propriedades comuns de um elemento combinável.
Animações aparecendo / desaparecendo
Use AnimatedVisibility
para ocultar ou mostrar um elemento combinável. Filhos dentro de
AnimatedVisibility
podem usar Modifier.animateEnterExit()
para a própria transição de entrada
ou saída.
var visible by remember { mutableStateOf(true) } // Animated visibility will eventually remove the item from the composition once the animation has finished. AnimatedVisibility(visible) { // your composable here // ... }
Os parâmetros de entrada e saída de AnimatedVisibility
permitem configurar como
um elemento combinável se comporta quando aparece e desaparece. Para saber mais, leia a documentação
completa.
Outra opção para animar a visibilidade de um elemento combinável é animar o
alfa ao longo do tempo usando animateFloatAsState
:
var visible by remember { mutableStateOf(true) } val animatedAlpha by animateFloatAsState( targetValue = if (visible) 1.0f else 0f, label = "alpha" ) Box( modifier = Modifier .size(200.dp) .graphicsLayer { alpha = animatedAlpha } .clip(RoundedCornerShape(8.dp)) .background(colorGreen) .align(Alignment.TopCenter) ) { }
No entanto, mudar o Alfa tem a ressalva de que o elemento combinável permanece
na composição e continua ocupando o espaço em que é disposto. Isso
pode fazer com que os leitores de tela e outros mecanismos de acessibilidade ainda considerem
o item na tela. Por outro lado, o método AnimatedVisibility
acabará removendo
o item da composição.
Animar cor do plano de fundo
val animatedColor by animateColorAsState( if (animateBackgroundColor) colorGreen else colorBlue, label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(animatedColor) } ) { // your composable here }
Essa opção tem melhor desempenho do que usar Modifier.background()
.
Modifier.background()
é aceitável para uma configuração de cor única, mas,
ao animar uma cor ao longo do tempo, isso pode causar mais recomposições do que
o necessário.
Para animar infinitamente a cor do plano de fundo, consulte Como repetir uma seção de animação.
Animar o tamanho de um elemento combinável
O Compose permite animar o tamanho dos elementos combináveis de algumas maneiras diferentes. Use
animateContentSize()
para animações entre mudanças de tamanho do elemento combinável.
Por exemplo, se você tiver uma caixa com texto que pode se expandir de uma para
várias linhas, use Modifier.animateContentSize()
para conseguir uma transição
mais suave:
var expanded by remember { mutableStateOf(false) } Box( modifier = Modifier .background(colorBlue) .animateContentSize() .height(if (expanded) 400.dp else 200.dp) .fillMaxWidth() .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { expanded = !expanded } ) { }
Você também pode usar AnimatedContent
, com um SizeTransform
para descrever
como as mudanças de tamanho precisam ocorrer.
Animar a posição de um elemento combinável
Para animar a posição de um elemento combinável, use Modifier.offset{ }
combinado com
animateIntOffsetAsState()
.
var moved by remember { mutableStateOf(false) } val pxToMove = with(LocalDensity.current) { 100.dp.toPx().roundToInt() } val offset by animateIntOffsetAsState( targetValue = if (moved) { IntOffset(pxToMove, pxToMove) } else { IntOffset.Zero }, label = "offset" ) Box( modifier = Modifier .offset { offset } .background(colorBlue) .size(100.dp) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { moved = !moved } )
Se você quiser garantir que os elementos combináveis não sejam renderizados sobre ou abaixo de outros
elementos combináveis ao animar a posição ou o tamanho, use Modifier.layout{ }
. Esse
modificador propaga mudanças de tamanho e posição para o pai, o que afeta
outros filhos.
Por exemplo, se você estiver movendo um Box
dentro de um Column
e os outros filhos
precisam ser movidos quando o Box
for movido, inclua as informações de deslocamento com
Modifier.layout{ }
da seguinte maneira:
var toggled by remember { mutableStateOf(false) } val interactionSource = remember { MutableInteractionSource() } Column( modifier = Modifier .padding(16.dp) .fillMaxSize() .clickable(indication = null, interactionSource = interactionSource) { toggled = !toggled } ) { val offsetTarget = if (toggled) { IntOffset(150, 150) } else { IntOffset.Zero } val offset = animateIntOffsetAsState( targetValue = offsetTarget, label = "offset" ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) Box( modifier = Modifier .layout { measurable, constraints -> val offsetValue = if (isLookingAhead) offsetTarget else offset.value val placeable = measurable.measure(constraints) layout(placeable.width + offsetValue.x, placeable.height + offsetValue.y) { placeable.placeRelative(offsetValue) } } .size(100.dp) .background(colorGreen) ) Box( modifier = Modifier .size(100.dp) .background(colorBlue) ) }
Animar o padding de um elemento combinável
Para animar o padding de um elemento combinável, use animateDpAsState
combinado com
Modifier.padding()
:
var toggled by remember { mutableStateOf(false) } val animatedPadding by animateDpAsState( if (toggled) { 0.dp } else { 20.dp }, label = "padding" ) Box( modifier = Modifier .aspectRatio(1f) .fillMaxSize() .padding(animatedPadding) .background(Color(0xff53D9A1)) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { toggled = !toggled } )
Animar a elevação de um elemento combinável
Para animar a elevação de um elemento combinável, use animateDpAsState
combinado com
Modifier.graphicsLayer{ }
. Para mudanças de elevação únicas, use
Modifier.shadow()
. Se você estiver animando a sombra, o uso do
modificador Modifier.graphicsLayer{ }
é a opção de melhor desempenho.
val mutableInteractionSource = remember { MutableInteractionSource() } val pressed = mutableInteractionSource.collectIsPressedAsState() val elevation = animateDpAsState( targetValue = if (pressed.value) { 32.dp } else { 8.dp }, label = "elevation" ) Box( modifier = Modifier .size(100.dp) .align(Alignment.Center) .graphicsLayer { this.shadowElevation = elevation.value.toPx() } .clickable(interactionSource = mutableInteractionSource, indication = null) { } .background(colorGreen) ) { }
Como alternativa, use o elemento combinável Card
e defina a propriedade de elevação como
valores diferentes por estado.
Animar escala, translação ou rotação do texto
Ao animar a escala, translação ou rotação do texto, defina o parâmetro textMotion
em TextStyle
como TextMotion.Animated
. Isso garante transições
mais suaves entre animações de texto. Use Modifier.graphicsLayer{ }
para
traduzir, girar ou dimensionar o texto.
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val scale by infiniteTransition.animateFloat( initialValue = 1f, targetValue = 8f, animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "scale" ) Box(modifier = Modifier.fillMaxSize()) { Text( text = "Hello", modifier = Modifier .graphicsLayer { scaleX = scale scaleY = scale transformOrigin = TransformOrigin.Center } .align(Alignment.Center), // Text composable does not take TextMotion as a parameter. // Provide it via style argument but make sure that we are copying from current theme style = LocalTextStyle.current.copy(textMotion = TextMotion.Animated) ) }
Animar a cor do texto
Para animar a cor do texto, use a lambda color
no elemento combinável BasicText
:
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val animatedColor by infiniteTransition.animateColor( initialValue = Color(0xFF60DDAD), targetValue = Color(0xFF4285F4), animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "color" ) BasicText( text = "Hello Compose", color = { animatedColor }, // ... )
Alternar entre diferentes tipos de conteúdo
Use AnimatedContent
para animar entre diferentes elementos combináveis. Se você
quiser apenas um esmaecimento padrão entre eles, use Crossfade
.
var state by remember { mutableStateOf(UiState.Loading) } AnimatedContent( state, transitionSpec = { fadeIn( animationSpec = tween(3000) ) togetherWith fadeOut(animationSpec = tween(3000)) }, modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { state = when (state) { UiState.Loading -> UiState.Loaded UiState.Loaded -> UiState.Error UiState.Error -> UiState.Loading } }, label = "Animated Content" ) { targetState -> when (targetState) { UiState.Loading -> { LoadingScreen() } UiState.Loaded -> { LoadedScreen() } UiState.Error -> { ErrorScreen() } } }
A AnimatedContent
pode ser personalizada para mostrar vários tipos diferentes de transições
de entrada e saída. Para mais informações, leia a documentação em
AnimatedContent
ou leia esta postagem do blog sobre
AnimatedContent
.
Anime durante a navegação para diferentes destinos
Para animar as transições entre elementos combináveis ao usar o artefato
navigation-compose, especifique o enterTransition
e o
exitTransition
em um elemento combinável. Você também pode definir a animação padrão a ser
usada para todos os destinos no nível superior NavHost
:
val navController = rememberNavController() NavHost( navController = navController, startDestination = "landing", enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None } ) { composable("landing") { ScreenLanding( // ... ) } composable( "detail/{photoUrl}", arguments = listOf(navArgument("photoUrl") { type = NavType.StringType }), enterTransition = { fadeIn( animationSpec = tween( 300, easing = LinearEasing ) ) + slideIntoContainer( animationSpec = tween(300, easing = EaseIn), towards = AnimatedContentTransitionScope.SlideDirection.Start ) }, exitTransition = { fadeOut( animationSpec = tween( 300, easing = LinearEasing ) ) + slideOutOfContainer( animationSpec = tween(300, easing = EaseOut), towards = AnimatedContentTransitionScope.SlideDirection.End ) } ) { backStackEntry -> ScreenDetails( // ... ) } }
Há muitos tipos diferentes de transições de entrada e saída que aplicam efeitos diferentes ao conteúdo de entrada e saída. Consulte a documentação para saber mais.
Repetir uma animação
Use rememberInfiniteTransition
com um infiniteRepeatable
animationSpec
para repetir continuamente a animação. Mude RepeatModes
para
especificar como ele precisa alternar.
Use finiteRepeatable
para repetir um determinado número de vezes.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Green, targetValue = Color.Blue, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(color) } ) { // your composable here }
Iniciar uma animação na inicialização de um elemento combinável
O LaunchedEffect
é executado quando um elemento combinável entra na composição. Ele inicia
uma animação na inicialização de um elemento combinável. Use esse recurso para impulsionar a mudança
do estado da animação. Use Animatable
com o método animateTo
para iniciar a
animação na inicialização:
val alphaAnimation = remember { Animatable(0f) } LaunchedEffect(Unit) { alphaAnimation.animateTo(1f) } Box( modifier = Modifier.graphicsLayer { alpha = alphaAnimation.value } )
Criar animações sequenciais
Use as APIs de corrotina Animatable
para executar animações sequenciais ou simultâneas. Chamar animateTo
em Animatable
uma após a outra faz com que
cada animação aguarde o término das animações anteriores antes de continuar .
Isso ocorre porque essa é uma função de suspensão.
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { alphaAnimation.animateTo(1f) yAnimation.animateTo(100f) yAnimation.animateTo(500f, animationSpec = tween(100)) }
Criar animações simultâneas
Use as APIs de corrotina (Animatable#animateTo()
ou animate
) ou
a API Transition
para criar animações simultâneas. Se você usar várias
funções de inicialização em um contexto de corrotina, elas vão iniciar as animações ao mesmo
tempo:
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { launch { alphaAnimation.animateTo(1f) } launch { yAnimation.animateTo(100f) } }
Você pode usar a API updateTransition
para usar o mesmo estado e gerar
muitas animações de propriedades diferentes ao mesmo tempo. O exemplo abaixo anima
duas propriedades controladas por uma mudança de estado, rect
e borderWidth
:
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "transition") val rect by transition.animateRect(label = "rect") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "borderWidth") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
Otimizar o desempenho da animação
As animações no Compose podem causar problemas de performance. Isso se deve à natureza do que é uma animação: mover ou mudar pixels na tela rapidamente, frame a frame para criar a ilusão de movimento.
Considere as diferentes fases do Compose: composição, layout e desenho. Se a animação mudar a fase de layout, ela vai exigir que todos os elementos combináveis afetados sejam de layout e redesenhados novamente. Se a animação ocorrer na fase de exibição, por padrão, ela terá um desempenho melhor do que se você executar a animação na fase de layout, já que ela teria menos trabalho a ser feito no geral.
Para garantir que o app faça o mínimo possível durante a animação, escolha a versão
lambda de uma Modifier
sempre que possível. Isso pula a recomposição e executa
a animação fora da fase de composição. Caso contrário, use
Modifier.graphicsLayer{ }
, já que esse modificador sempre é executado na fase
de exibição. Para mais informações sobre isso, consulte a seção adiamento de leituras na
documentação sobre desempenho.
Alterar o tempo da animação
Por padrão, o Compose usa animações de mola para a maioria delas. As molas, ou
animações baseadas em física, são mais naturais. Eles também podem ser interrompidos, já que
consideram a velocidade atual do objeto, em vez de um tempo fixo.
Se você quiser substituir o padrão, todas as APIs de animação demonstradas acima
podem definir uma animationSpec
para personalizar a execução de uma animação,
seja se você quiser que ela seja executada por uma determinada duração ou mais saltitante.
Confira a seguir um resumo das diferentes opções de animationSpec
:
spring
: animação baseada em física, o padrão para todas as animações. É possível mudar a rigidez ou a taxa de amortecimento para ter uma aparência de animação diferente.tween
(abreviação de between): animação baseada em duração é animada entre dois valores com uma funçãoEasing
.keyframes
: especificação para especificar valores em determinados pontos-chave em uma animação.repeatable
: especificação baseada na duração que é executada um determinado número de vezes, especificada porRepeatMode
.infiniteRepeatable
: especificação baseada na duração que é executada indefinidamente.snap
: ajusta instantaneamente ao valor final sem nenhuma animação.
Leia a documentação completa para saber mais sobre animationSpecs.
Outros recursos
Para mais exemplos de animações divertidas no Compose, consulte estes links:
- 5 animações rápidas no Compose
- Como fazer Jellyfish se mover no Compose (link em inglês)
- Como personalizar
AnimatedContent
no Compose - Como aplicar easing nas funções de easing no Compose