O Compose tem muitos mecanismos de animação integrados, e pode ser difícil saber qual escolher. Confira abaixo uma lista de casos de uso comuns de animação. Para mais informações sobre o conjunto completo de opções de API disponíveis para você, leia a documentação completa da animação do Compose.
Animar propriedades comuns que podem ser compostas
O Compose oferece APIs convenientes que permitem resolver muitos casos de uso comuns de animação. Esta seção demonstra como animar propriedades comuns de um elemento combinável.
Animar o aparecimento / desaparecimento
Use AnimatedVisibility para ocultar ou mostrar um elemento combinável. Os 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 o comportamento de um elemento combinável quando ele aparece e desaparece. Leia a documentação
completa para mais informações.
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, a mudança do alfa tem a ressalva de que o elemento combinável permanece na composição e continua ocupando o espaço em que está disposto. Isso pode fazer com que os leitores de tela e outros mecanismos de acessibilidade ainda considerem o item na tela. Por outro lado, AnimatedVisibility acaba removendo o item da composição.
Animar a 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 um desempenho melhor 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 a seção Repetir uma animação.
Animar o tamanho de um elemento combinável
O Compose permite animar o tamanho de elementos combináveis de algumas maneiras diferentes. Use
animateContentSize() para animações entre mudanças de tamanho de elementos combináveis.
Por exemplo, se você tiver uma caixa que contém texto que pode ser expandido 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 } ) { }
Também é possível usar AnimatedContent, com um SizeTransform para descrever
como as mudanças de tamanho devem ocorrer.
Animar a posição do 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 desenhados sobre ou sob outros elementos combináveis ao animar a posição ou o tamanho, use Modifier.layout{ }. Esse modificador propaga as mudanças de tamanho e posição para o pai, o que afeta outros filhos.
Por exemplo, se você estiver movendo uma Box dentro de uma Column e os outros filhos precisarem se mover quando a Box se mover, 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) ) }
Modifier.layout{ }Animar o preenchimento de um elemento combinável
Para animar o preenchimento 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, usar o 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 Card elemento combinável e defina a propriedade de elevação para
valores diferentes por estado.
Animar a escala, a translação ou a rotação do texto
Ao animar a escala, a translação ou a rotação do texto, defina o textMotion
parâmetro em TextStyle como TextMotion.Animated. Isso garante transições mais suaves entre animações de texto. Use Modifier.graphicsLayer{ } para
transladar, 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 fade padrão entre elementos combináveis, 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() } } }
AnimatedContent pode ser personalizado para mostrar muitos tipos diferentes de transições de entrada e saída. Para mais informações, leia a documentação sobre
AnimatedContent ou leia este post do blog sobre
AnimatedContent.
Animar durante a navegação para diferentes destinos
Para animar transições entre elementos combináveis ao usar o
artefato navigation-compose, especifique enterTransition e
exitTransition em um elemento combinável. Também é possível definir a animação padrão a ser usada para todos os destinos no NavHost de nível superior:
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 mais informações.
Repetir uma animação
Use rememberInfiniteTransition com um infiniteRepeatable
animationSpec para repetir continuamente a animação. Mude RepeatModes para especificar como ela deve ir e voltar.
Use repeatable para repetir um número definido 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
LaunchedEffect é executado quando um elemento combinável entra na composição. Ele inicia uma animação na inicialização de um elemento combinável. Você pode usar isso para acionar a mudança de estado da animação. Como usar 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 realizar animações sequenciais ou simultâneas. Chamar animateTo no Animatable um após o outro faz com que cada animação aguarde a conclusão das animações anteriores antes de prosseguir .
Isso ocorre porque é 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 conseguir animações simultâneas. Se você usar várias funções de inicialização em um contexto de corrotina, as animações serão iniciadas ao mesmo tempo:
val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { launch { alphaAnimation.animateTo(1f) } launch { yAnimation.animateTo(100f) } }
É possível usar a API updateTransition para usar o mesmo estado para acionar
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 a performance da animação
As animações no Compose podem causar problemas de performance. Isso ocorre devido à natureza de 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 exigirá que todos os elementos combináveis afetados sejam refeitos e redesenhados. Se a animação ocorrer na fase de desenho, ela será mais eficiente por padrão do que se você executar a animação na fase de layout, já que ela terá menos trabalho a fazer no geral.
Para garantir que seu app faça o mínimo possível durante a animação, escolha a versão lambda de um Modifier sempre que possível. Isso ignora a recomposição e realiza
a animação fora da fase de composição. Caso contrário, use
Modifier.graphicsLayer{ }, já que esse modificador sempre é executado na fase de desenho
. Para mais informações sobre isso, consulte a seção Adiar leituras na
documentação de performance.
Alterar o tempo da animação
O Compose usa animações de mola por padrão para a maioria das animações. As animações de mola ou baseadas em física parecem mais naturais. Elas também podem ser interrompidas, 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 têm a capacidade de definir um animationSpec para personalizar a execução de uma animação, seja para que ela seja executada durante um determinado período ou seja mais dinâmica.
Confira abaixo 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 conseguir uma aparência e sensação de animação diferentes.tween(abreviação de between): animação baseada na duração, anima entre dois valores com uma funçãoEasing.keyframes: especificação para definir valores em determinados pontos-chave de uma animação.repeatable: especificação baseada na duração que é executada um determinado número de vezes, especificado porRepeatMode.infiniteRepeatable: especificação baseada na duração que é executada para sempre.snap: se ajusta instantaneamente ao valor final sem nenhuma animação.
Leia a documentação completa para mais informações sobre animationSpecs.
Outros recursos
Para mais exemplos de animações divertidas no Compose, consulte o seguinte:
- 5 animações rápidas no Compose
- Como fazer Jellyfish se mover no Compose (link em inglês)
- Como personalizar
AnimatedContentno Compose (link em inglês) - Como usar funções de easing no Compose (link em inglês)