Modificatori di animazione e componibili

Compose è dotato di composabili e modificatori integrati per gestire i casi d'uso comuni delle animazioni.

Composabili animati integrati

Anima l'apparizione e la scomparsa con AnimatedVisibility

Componente componibile verde che si mostra e si nasconde
Figura 1. Animazione dell'apparizione e della scomparsa di un elemento in una colonna

Il composable AnimatedVisibility anima l'apparizione e la scomparsa dei suoi contenuti.

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

Per impostazione predefinita, i contenuti vengono visualizzati con un effetto di dissolvenza in entrata ed espansione e scompaiono con un effetto di dissolvenza in uscita e contrazione. La transizione può essere personalizzata specificando 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)
    )
}

Come puoi vedere nell'esempio precedente, puoi combinare più oggetti EnterTransition o ExitTransition con un operatore +, ognuno dei quali accetta parametri facoltativi per personalizzare il proprio comportamento. Per ulteriori informazioni, consulta i riferimenti.

EnterTransition ed esempi di ExitTransition

EnterTransition ExitTransition
fadeIn
animazione di dissolvenza in entrata
fadeOut
animazione di dissolvenza
slideIn
animazione di scorrimento
slideOut
animazione di scorrimento verso l'esterno
slideInHorizontally
animazione di scorrimento orizzontale
slideOutHorizontally
animazione di scorrimento orizzontale
slideInVertically
animazione di scorrimento verticale
slideOutVertically
animazione di scorrimento verso l'esterno in verticale
scaleIn
animare lo scale in
scaleOut
animazione di scala fuori
expandIn
espandersi nell'animazione
shrinkOut
animazione di rimpicciolimento
expandHorizontally
animazione di espansione orizzontale
shrinkHorizontally
animazione di riduzione orizzontale
expandVertically
animazione di espansione verticale
shrinkVertically
animazioni di riduzione verticale

AnimatedVisibility offre anche una variante che accetta un MutableTransitionState. In questo modo puoi attivare un'animazione non appena il AnimatedVisibility viene aggiunto all'albero della composizione. È utile anche per osservare lo stato dell'animazione.

// 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"
        }
    )
}

Animare l'entrata e l'uscita per i bambini

I contenuti all'interno di AnimatedVisibility (elementi secondari diretti o indiretti) possono utilizzare il modificatore animateEnterExit per specificare un comportamento di animazione diverso per ciascuno di essi. L'effetto visivo per ciascuno di questi elementi secondari è una combinazione delle animazioni specificate nel composable AnimatedVisibility e delle animazioni di ingresso e di uscita dell'elemento secondario.

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…
        }
    }
}

In alcuni casi, potresti voler fare in modo che AnimatedVisibility non applichi animazioni al fine che ogni bambino possa avere le proprie animazioni distinte per animateEnterExit. Per farlo, specifica EnterTransition.None e ExitTransition.None nel composable AnimatedVisibility.

Aggiungere un'animazione personalizzata

Se vuoi aggiungere effetti di animazione personalizzati oltre alle animazioni di entrata e di uscita predefinite, accedi all'istanza Transition sottostante tramite la proprietà transition all'interno della lambda dei contenuti per AnimatedVisibility. Eventuali stati di animazione aggiunti all'istanza Transition verranno eseguiti contemporaneamente alle animazioni di entrata e di uscita di AnimatedVisibility. AnimatedVisibility attende che tutte le animazioni in Transition siano terminate prima di rimuoverne i contenuti. Per le animazioni di uscita create indipendentemente da Transition (ad esempio utilizzando animate*AsState), AnimatedVisibility non sarebbe in grado di tenerne conto, e di conseguenza potrebbe rimuovere i contenuti composibili prima del termine.

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)
    )
}

Per informazioni dettagliate su Transition, consulta updateTransition.

Creare animazioni in base allo stato target con AnimatedContent

Il composable AnimatedContent anima i contenuti man mano che cambiano in base a uno stato di destinazione.

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")
    }
}

Tieni presente che devi sempre utilizzare il parametro lambda e rifletterlo nei contenuti. L'API utilizza questo valore come chiave per identificare i contenuti attualmente mostrati.

Per impostazione predefinita, i contenuti iniziali svaniscono e poi i contenuti di destinazione vengono visualizzati gradualmente (questo comportamento è chiamato dissolvenza). Puoi personalizzare questo comportamento di animazione specificando un oggetto ContentTransform per il parametro transitionSpec. Puoi creare ContentTransform combinando un EnterTransition con un ExitTransition utilizzando la funzione infix with. Puoi applicare SizeTransform a ContentTransform collegandolo con la funzione infix 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")
}

EnterTransition definisce il modo in cui devono essere visualizzati i contenuti target e ExitTransition definisce il modo in cui devono scomparire i contenuti iniziali. Oltre a tutte le funzioni EnterTransition e ExitTransition disponibili per AnimatedVisibility, AnimatedContent offre slideIntoContainer e slideOutOfContainer. Si tratta di alternative pratiche a slideInHorizontally/Vertically e slideOutHorizontally/Vertically che calcolano la distanza tra le diapositive in base alle dimensioni dei contenuti iniziali e ai contenuti di destinazione dei contenutiAnimatedContent.

SizeTransform definisce la modalità di animazione delle dimensioni tra i contenuti iniziali e quelli di destinazione. Hai accesso sia alle dimensioni iniziali sia a quelle target durante la creazione dell'animazione. SizeTransform controlla anche se i contenuti devono essere ritagliati in base alle dimensioni del componente durante le animazioni.

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()
        }
    }
}

Animare le transizioni di entrata e uscita dei figli

Come per AnimatedVisibility, il modificatore animateEnterExit è disponibile all'interno della lambda dei contenuti di AnimatedContent. Utilizza questa opzione per applicare EnterAnimation e ExitAnimation separatamente a ogni elemento figlio diretto o indiretto.

Aggiungere un'animazione personalizzata

Come AnimatedVisibility, il campo transition è disponibile all'interno della funzione lambda dei contenuti di AnimatedContent. Utilizzalo per creare un effetto di animazione personalizzato che venga eseguito contemporaneamente alla transizione AnimatedContent. Per i dettagli, consulta updateTransition.

Passare da un layout all'altro con Crossfade

Crossfade passa da un layout all'altro con un'animazione di transizione graduale. Se attivi/disattivi il valore passato al parametro current, i contenuti vengono scambiati con un'animazione di transizione graduale.

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

Modificatori di animazione integrati

Anima le modifiche delle dimensioni dei componenti con animateContentSize

Composable verde che anima la modifica delle dimensioni in modo fluido.
Figura 2. Composable che anima senza interruzioni tra una dimensione piccola e una più grande

Il modificatore animateContentSize anima una modifica delle dimensioni.

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
        }

) {
}

Animazioni delle voci dell'elenco

Se vuoi animare il riordinamento degli elementi all'interno di un elenco o di una griglia lazy, consulta la documentazione sull'animazione degli elementi con layout lazy.