Guia rápido sobre animações no Compose

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 informações mais detalhadas sobre o conjunto completo de diferentes opções de API disponíveis para você, leia a documentação completa da Compose Animation.

Animar propriedades combináveis comuns

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

Elemento combinável verde aparecendo e se ocultando
Figura 1. Como animar a aparição e o desaparecimento de um item em uma coluna

Use AnimatedVisibility para ocultar ou mostrar um elemento combinável. Os filhos em 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. 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, mudar o alfa vem com a ressalva de que o elemento combinável permanece na composição e continua ocupando o espaço em que foi definido. Isso pode fazer com que 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.

Como animar a Alfa de um elemento combinável
Figura 2. Como animar o Alfa de um elemento combinável

Animar a cor do plano de fundo

Elemento combinável com a cor de plano de fundo mudando ao longo do tempo como uma animação, em que as cores vão se desvanecendo umas nas outras.
Figura 3. Como animar a cor de segundo plano de um elemento combinável

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 o uso de 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 necessário.

Para animar a cor de fundo infinitamente, consulte Repetir uma seção de animação.

Animar o tamanho de um elemento combinável

Elemento combinável verde animando a mudança de tamanho sem problemas.
Figura 4. Elemento combinável animado suavemente entre um tamanho pequeno e um maior

O Compose permite animar o tamanho dos elementos combináveis de algumas maneiras diferentes. Use animateContentSize() para animações entre mudanças de tamanho combináveis.

Por exemplo, se você tiver uma caixa que contenha 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
        }

) {
}

Você também pode usar AnimatedContent, com um SizeTransform para descrever como as mudanças de tamanho devem ocorrer.

Animar a posição do elemento combinável

Elemento combinável verde animado suavemente para baixo e para a direita
Figura 5. Elemento combinável movido por um deslocamento

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 abaixo de 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 elemento pai, que afeta outros filhos.

Por exemplo, se você estiver movendo um Box dentro de um Column e os outros filhos precisarem 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)
    )
}

Duas caixas com a segunda caixa animando a posição X,Y, a terceira caixa respondendo se movendo pela quantidade Y também.
Figura 6. Como animar com Modifier.layout{ }

Animar o padding de um elemento combinável

Elemento combinável verde ficando menor e maior ao clicar, com o padding sendo animado
Figura 7. Elemento combinável com a animação de padding

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

Figura 8. A animação de elevação do elemento combinável ao clicar

Para animar a elevação de um elemento combinável, use animateDpAsState com Modifier.graphicsLayer{ }. Para mudanças únicas de elevação, use Modifier.shadow(). Se você estiver animando a sombra, usar o modificador Modifier.graphicsLayer{ } é a opção com 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 para valores diferentes por estado.

Animar escala, translação ou rotação de texto

Elemento combinável de texto dizendo
Figura 9. Texto animado suavemente entre dois tamanhos

Ao animar a escala, a tradução ou a 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 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

As palavras
Figura 10. Exemplo mostrando a animação da 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

Tela verde dizendo
Figura 11. Como usar o AnimatedContent para animar mudanças entre diferentes elementos combináveis (mais lento)

Use AnimatedContent para animar entre elementos combináveis diferentes. Se você quiser apenas um desbotamento 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()
        }
    }
}

O 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 esta postagem do blog sobre AnimatedContent.

Animar durante a navegação para diferentes destinos

Dois elementos combináveis, um verde com a palavra "Landing" e outro azul com a palavra "Detail", animados deslizando o elemento combinável de detalhes sobre o elemento combinável de destino.
Figura 12. Como animar entre elementos combináveis usando o Navigation Compose

Para animar transições entre elementos combináveis ao usar o artefato navigation-compose, especifique o 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 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

Um plano de fundo verde que se transforma em um plano de fundo azul, infinitamente, animando entre as duas cores.
Figura 13. A cor de plano de fundo está sendo animada entre dois valores, de forma infinita

Use rememberInfiniteTransition com um infiniteRepeatable animationSpec para repetir a animação continuamente. Mude RepeatModes para especificar como ele deve ir e voltar.

Use finiteRepeatable 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 direcionar a mudança de estado da animação. Usando 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

Quatro círculos com setas verdes animados entre cada um, animados um após o outro.
Figura 14. Diagrama indicando como uma animação sequencial progride, uma por uma.

Use as APIs de corrotina Animatable para executar 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

Três círculos com setas verdes animados para cada um, animados todos juntos ao mesmo tempo.
Figura 15. Diagrama indicando como as animações simultâneas progridem ao mesmo tempo.

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, 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 para gerar várias animações de propriedade 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 desempenho. Isso se deve à natureza de uma animação: mover ou mudar pixels na tela rapidamente, frame por frame, para criar a ilusão de movimento.

Considere as diferentes fases do Compose: composição, layout e renderização. Se a animação mudar a fase de layout, será necessário que todos os elementos combináveis afetados sejam reprojetados e redesenhados. Se a animação ocorrer na fase de renderização, ela terá mais desempenho do que se você executar a animação na fase de layout, porque terá menos trabalho.

Para garantir que o 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 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, consulte a seção adiar leituras na documentação de desempenho.

Mudar o tempo da animação

Por padrão, o Compose usa animações de mola para a maioria das animações. Molas ou animações 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 como uma animação é executada, seja para que ela seja executada por um determinado período ou seja mais elástica.

Confira um resumo das diferentes opções de animationSpec:

  • spring: animação baseada em física, o padrão para todas as animações. Você pode mudar a rigidez ou o dampingRatio para ter uma aparência diferente da animação.
  • tween (abreviação de between): animação baseada em duração, animada entre dois valores com uma função Easing.
  • keyframes: especificação para especificar valores em determinados pontos-chave de uma animação.
  • repeatable: especificação baseada na duração que é executada um determinado número de vezes, especificada por RepeatMode.
  • infiniteRepeatable: especificação baseada na duração que é executada para sempre.
  • snap: fixa instantaneamente o valor final sem nenhuma animação.
Escreva o texto alternativo aqui
Figura 16. Conjunto de especificações personalizado do Spring em comparação com nenhum conjunto de especificações

Leia a documentação completa para saber mais sobre animationSpecs.

Outros recursos

Para mais exemplos de animações divertidas no Compose, consulte estes recursos: