Guia rápido sobre animações no Compose

O Compose tem muitos mecanismos de animação integrados, e pode ser complicado 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 leia a documentação completa sobre animações no Compose.

Animar propriedades comuns que podem ser compostas

O Compose fornece APIs convenientes que permitem solucionar muitos problemas comuns para casos de uso com animação. Esta seção demonstra como é possível animar de um elemento combinável.

Animações aparecendo / desaparecendo

Elemento combinável verde sendo mostrado e ocultado
Figura 1. Animar o aparecimento e desaparecimento de um item em uma coluna

Use AnimatedVisibility para ocultar ou mostrar um elemento combinável. Crianças dentro AnimatedVisibility pode usar Modifier.animateEnterExit() para a própria entrada ou sair da transição.

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 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 tem a ressalva de que o elemento combinável permanece na composição e continua ocupando o espaço em que foi disposta. Isso pode fazer com que leitores de tela e outros mecanismos de acessibilidade ainda considerem o item na tela. Por outro lado, o AnimatedVisibility remove o item da composição.

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

Animar cor do plano de fundo

Elemento combinável com a cor do plano de fundo mudando ao longo do tempo como uma animação, em que as cores vão esmaecendo umas às outras.
Figura 3. Como animar a cor do plano de fundo 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 usar Modifier.background(). Modifier.background() é aceitável para uma configuração de cor única, mas quando animar uma cor ao longo do tempo, isso poderia causar mais recomposições do que necessários.

Para animar infinitamente a cor do plano de fundo, consulte Como repetir uma animação .

Animar o tamanho de um elemento combinável

A animação do elemento combinável verde muda de forma suave.
Figura 4. Animação suave do elemento combinável entre um tamanho pequeno e um maior

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

Por exemplo, se você tem uma caixa que contém texto que pode se expandir de um para várias linhas, você pode usar Modifier.animateContentSize() para conseguir um resultado transição:

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 de um elemento combinável

Elemento combinável verde sendo animado para baixo e para a direita
Figura 5. Elemento combinável se movendo 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 combináveis ao animar a posição ou o tamanho, use Modifier.layout{ }. Isso propaga mudanças de tamanho e posição para o pai, o que afeta com outras crianças.

Por exemplo, se você estiver movendo um Box dentro de uma Column e os outros filhos precisam ser movidos quando a Box for movida, 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)
    )
}

2 caixas com a segunda caixa animando sua posição X,Y, e a terceira caixa respondendo se movendo pelo valor Y também.
Figura 6. Como animar com Modifier.layout{ }

Animar o padding de um elemento combinável

O elemento combinável verde fica cada vez menor ao clicar, com o padding sendo animado
Figura 7. elemento combinável com 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. Animação de elevação do elemento combinável ao clicar

Para animar a elevação de um elemento combinável, use animateDpAsState combinado com Modifier.graphicsLayer{ }. Para alterações de elevação únicas, use Modifier.shadow(): Se você estiver animando a sombra, usando 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 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

Texto combinável dizendo
Figura 9. Animações de texto suaves em dois tamanhos

Ao animar a escala, translação ou rotação do texto, defina a textMotion. em TextStyle como TextMotion.Animated. Isso garante um fluxo transições entre animações de texto. Use Modifier.graphicsLayer{ } para traduza, gire ou dimensione 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 de 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. Uso de AnimationContent para animar mudanças entre diferentes elementos combináveis (mais lento)
.

Use AnimatedContent para animar entre diferentes elementos combináveis. quiser apenas um esmaecimento padrão entre os 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 entrada e transições de saída. Para mais informações, leia a documentação sobre AnimatedContent ou leia esta postagem do blog sobre AnimatedContent

Anime durante a navegação para diferentes destinos

Dois elementos combináveis, um verde indicando "Página de destino" e um azul indicando "Detalhe". Para animar, deslize o elemento combinável de detalhes sobre o elemento de destino.
Figura 12. Como animar entre elementos combináveis usando Navigation-compose

Para animar as transições entre elementos combináveis usando o artefato navigation-compose, especifique os enterTransition e exitTransition em um elemento combinável. Você também pode definir a animação padrão usado 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 se aplicam os diferentes efeitos no conteúdo recebido e enviado, consulte a documentação para saber mais.

Repetir uma animação

Um plano de fundo verde que se transforma em azul, infinitamente animado entre as duas cores.
Figura 13. Cor de plano de fundo animada entre dois valores, infinitamente

Usar rememberInfiniteTransition com um infiniteRepeatable animationSpec para repetir a animação continuamente. Alterar RepeatModes para especificar como devem ir e voltar.

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. Começa uma animação na inicialização de um elemento combinável, você pode usar isso para direcionar a animação. mudança de estado. Use Animatable com o método animateTo para iniciar o 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 animadas entre cada um, um após o outro.
Figura 14. Diagrama indicando como uma animação sequencial progride, uma a uma.

Usar as APIs de corrotina Animatable para executar tarefas sequenciais ou simultâneas animações. Chamar animateTo no Animatable depois das outras causas cada animação para aguardar 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

Três círculos com setas verdes representando cada um deles, tudo isso ao mesmo tempo.
Figura 15. Diagrama indicando como as animações simultâneas progridem, tudo ao mesmo tempo.

Use as APIs de corrotina (Animatable#animateTo() ou animate) ou a API Transition para gerar animações simultâneas. Se você usa vários inicia funções em um contexto de corrotina, ele inicia as animações ao mesmo horário:

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 animações de propriedades diferentes ao mesmo tempo. O exemplo abaixo 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 alterar 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 muda a fase de layout. Todos os elementos combináveis afetados precisam ser redesenhar o layout e redesenhar. Se sua animação ocorrer na fase de desenho, é por padrão terá um desempenho melhor do que se você executasse a animação no layout porque teria menos trabalho a fazer no geral.

Para garantir que o app faça o mínimo possível durante a animação, escolha a 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 exibição fase de testes. Para mais informações, consulte a seção adiamento de leituras em a documentação sobre desempenho.

Alterar o tempo da animação

Por padrão, o Compose usa animações de mola para a maioria delas. Fontes baseadas em física, parecem mais naturais. Eles também podem ser interrompidos, eles consideram a velocidade atual do objeto, e não um tempo fixo. Se você quiser substituir o padrão, todas as APIs de animação demonstradas acima conseguir definir uma animationSpec para personalizar a execução da animação; se você gostaria que ela fosse executada por uma certa duração ou fosse 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. Você pode alterar a rigidez ou a taxa de amortecimento para obter uma animação diferente aparência.
  • tween (abreviação de entre): animação baseada na duração. entre dois valores com uma função Easing.
  • keyframes: especificação para especificar valores em pontos-chave de uma animação.
  • repeatable: especificação baseada na duração que é executada um determinado número de vezes. especificado por RepeatMode.
  • infiniteRepeatable: especificação baseada na duração que é executada indefinidamente.
  • snap: ajusta instantaneamente ao valor final sem nenhuma animação.
Escreva seu texto alternativo aqui
Figura 16. nenhuma especificação definida em comparação com a personalizada da primavera

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

Outros recursos

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