Modificadores de animações e elementos combináveis

O Compose vem com elementos combináveis e modificadores integrados para lidar com casos de uso de animação comuns.

Elementos combináveis animados integrados

Animar a aparição e o desaparecimento com AnimatedVisibility

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

A função AnimatedVisibility que pode ser composta anima o aparecimento e desaparecimento de conteúdo.

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
    // ...
}

Por padrão, o aparecimento do conteúdo ocorre com esmaecimento e expansão e o desaparecimento com esmaecimento e encolhimento. A transição pode ser personalizada especificando EnterTransition e ExitTransition.

var visible by remember { mutableStateOf(true) }
val density = LocalDensity.current
AnimatedVisibility(
    visible = visible,
    enter = slideInVertically {
        // Slide in from 40 dp from the top.
        with(density) { -40.dp.roundToPx() }
    } + expandVertically(
        // Expand from the top.
        expandFrom = Alignment.Top
    ) + fadeIn(
        // Fade in with the initial alpha of 0.3f.
        initialAlpha = 0.3f
    ),
    exit = slideOutVertically() + shrinkVertically() + fadeOut()
) {
    Text(
        "Hello",
        Modifier
            .fillMaxWidth()
            .height(200.dp)
    )
}

Como mostrado no exemplo acima, é possível combinar vários objetos EnterTransition ou ExitTransition com um operador +, sendo que cada um aceita parâmetros opcionais para personalizar o comportamento. Consulte as referências para mais informações.

Exemplos da EnterTransition e ExitTransition

A função de composição AnimatedVisibility também oferece uma variante que recebe uma classe MutableTransitionState. Isso permite que você acione uma animação assim que a AnimatedVisibility for adicionada à árvore de composição. Também é útil para observar o estado da animação.

// Create a MutableTransitionState<Boolean> for the AnimatedVisibility.
val state = remember {
    MutableTransitionState(false).apply {
        // Start the animation immediately.
        targetState = true
    }
}
Column {
    AnimatedVisibility(visibleState = state) {
        Text(text = "Hello, world!")
    }

    // Use the MutableTransitionState to know the current animation state
    // of the AnimatedVisibility.
    Text(
        text = when {
            state.isIdle && state.currentState -> "Visible"
            !state.isIdle && state.currentState -> "Disappearing"
            state.isIdle && !state.currentState -> "Invisible"
            else -> "Appearing"
        }
    )
}

Animar a entrada e a saída de filhas

O conteúdo em AnimatedVisibility (animações filhas diretas ou indiretas) pode usar o modificador animateEnterExit para especificar um comportamento de animação diferente para cada uma delas. O efeito visual de cada uma dessas filhas é uma combinação das animações especificadas na função AnimatedVisibility e nas animações de entrada e saída das próprias filhas.

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) {
    // Fade in/out the background and the foreground.
    Box(
        Modifier
            .fillMaxSize()
            .background(Color.DarkGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .animateEnterExit(
                    // Slide in/out the inner box.
                    enter = slideInVertically(),
                    exit = slideOutVertically()
                )
                .sizeIn(minWidth = 256.dp, minHeight = 64.dp)
                .background(Color.Red)
        ) {
            // Content of the notification…
        }
    }
}

Em alguns casos, pode ser necessário fazer com que AnimatedVisibility não aplique animações para que cada filha possa ter animações diferentes usando animateEnterExit. Para fazer isso, especifique EnterTransition.None e ExitTransition.None na função AnimatedVisibility que pode ser composta.

Adicionar uma animação personalizada

Se você quiser adicionar efeitos de animação personalizados, além das animações de entrada e saída integradas, acesse a instância Transition usando a propriedade transition dentro da lambda de conteúdo da AnimatedVisibility. Todos os estados de animação adicionados à instância de transição serão executados simultaneamente com as animações de entrada e saída de AnimatedVisibility. A AnimatedVisibility aguarda até que todas as animações em Transition tenham terminado antes de remover o conteúdo. A AnimatedVisibility não consegue considerar animações de saída criadas de forma independente da Transition, como o uso de animate*AsState. Por isso, é possível que o elemento do conteúdo seja removido antes do fim da transição.

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) { // this: AnimatedVisibilityScope
    // Use AnimatedVisibilityScope#transition to add a custom animation
    // to the AnimatedVisibility.
    val background by transition.animateColor(label = "color") { state ->
        if (state == EnterExitState.Visible) Color.Blue else Color.Gray
    }
    Box(
        modifier = Modifier
            .size(128.dp)
            .background(background)
    )
}

Consulte updateTransition para saber mais sobre a Transition.

Animar com base no estado de destino com AnimatedContent

O elemento AnimatedContent combinável anima o conteúdo de acordo com um estado de destino.

Row {
    var count by remember { mutableIntStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Add")
    }
    AnimatedContent(
        targetState = count,
        label = "animated content"
    ) { targetCount ->
        // Make sure to use `targetCount`, not `count`.
        Text(text = "Count: $targetCount")
    }
}

Use sempre o parâmetro lambda e o reflita no conteúdo. A API usa esse valor como a chave para identificar o conteúdo que está sendo mostrado.

Por padrão, o conteúdo inicial esmaece e o conteúdo de destino aparece gradualmente. Esse comportamento é chamado de esmaecimento gradual (link em inglês). É possível personalizar esse comportamento de animação especificando um objeto ContentTransform para o parâmetro transitionSpec. Você pode criar um ContentTransform combinando um EnterTransition com um ExitTransition, usando a função de infixo with. Aplique uma SizeTransform ao ContentTransform anexando a função de infixo using.

AnimatedContent(
    targetState = count,
    transitionSpec = {
        // Compare the incoming number with the previous number.
        if (targetState > initialState) {
            // If the target number is larger, it slides up and fades in
            // while the initial (smaller) number slides up and fades out.
            slideInVertically { height -> height } + fadeIn() togetherWith
                slideOutVertically { height -> -height } + fadeOut()
        } else {
            // If the target number is smaller, it slides down and fades in
            // while the initial number slides down and fades out.
            slideInVertically { height -> -height } + fadeIn() togetherWith
                slideOutVertically { height -> height } + fadeOut()
        }.using(
            // Disable clipping since the faded slide-in/out should
            // be displayed out of bounds.
            SizeTransform(clip = false)
        )
    }, label = "animated content"
) { targetCount ->
    Text(text = "$targetCount")
}

O objeto EnterTransition define como o conteúdo de destino vai aparecer, e ExitTransition define como o conteúdo inicial vai desaparecer. Além de todas as funções de EnterTransition e ExitTransition disponíveis para AnimatedVisibility, AnimatedContent oferece slideIntoContainer e slideOutOfContainer. Essas são alternativas convenientes a slideInHorizontally/Vertically e slideOutHorizontally/Vertically, que calculam a distância do deslizamento com base nos tamanhos do conteúdo inicial e do conteúdo de destino do AnimatedContent.

SizeTransform define como o tamanho será animado entre o conteúdo inicial e de destino. Ao criar a animação, você tem acesso ao tamanho inicial e ao tamanho de destino. SizeTransform também controla se o conteúdo precisa ser cortado para o tamanho do componente durante as animações.

var expanded by remember { mutableStateOf(false) }
Surface(
    color = MaterialTheme.colorScheme.primary,
    onClick = { expanded = !expanded }
) {
    AnimatedContent(
        targetState = expanded,
        transitionSpec = {
            fadeIn(animationSpec = tween(150, 150)) togetherWith
                fadeOut(animationSpec = tween(150)) using
                SizeTransform { initialSize, targetSize ->
                    if (targetState) {
                        keyframes {
                            // Expand horizontally first.
                            IntSize(targetSize.width, initialSize.height) at 150
                            durationMillis = 300
                        }
                    } else {
                        keyframes {
                            // Shrink vertically first.
                            IntSize(initialSize.width, targetSize.height) at 150
                            durationMillis = 300
                        }
                    }
                }
        }, label = "size transform"
    ) { targetExpanded ->
        if (targetExpanded) {
            Expanded()
        } else {
            ContentIcon()
        }
    }
}

Animar transições de entrada e saída de filhas

Assim como AnimatedVisibility, o modificador animateEnterExit está disponível dentro da lambda de conteúdo de AnimatedContent. Use esse modificador para aplicar o EnterAnimation e o ExitAnimation a cada um dos filhos diretos ou indiretos separadamente.

Adicionar uma animação personalizada

Assim como AnimatedVisibility, o campo transition está disponível dentro da lambda de conteúdo de AnimatedContent. Use esse campo para criar um efeito de animação personalizado que será executado simultaneamente com a transição AnimatedContent. Consulte updateTransition para saber mais.

Animar entre dois layouts com Crossfade

O Crossfade é animado entre dois layouts com uma animação de fading cruzado. Ao alternar o valor transmitido para o parâmetro current, o conteúdo muda com uma animação de fading cruzado.

var currentPage by remember { mutableStateOf("A") }
Crossfade(targetState = currentPage, label = "cross fade") { screen ->
    when (screen) {
        "A" -> Text("Page A")
        "B" -> Text("Page B")
    }
}

Modificadores de animação integrados

Animar mudanças de tamanho combinável com animateContentSize

A animação do elemento combinável verde muda de forma suave.
Figura 2. Elemento combinável animado suavemente entre um tamanho pequeno e um maior

O modificador animateContentSize anima uma mudança de tamanho.

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
        }

) {
}

Animações de itens de lista

Se você quiser animar a reordenação dos itens em uma lista ou grade lenta, consulte a documentação de animação de itens de layout lento.

Nenhuma recomendação no momento.

Tente na sua Conta do Google.