Modificatori di animazione e componibili

Compose è dotato di composable e modificatori integrati per la gestione dei casi d'uso comuni delle animazioni.

Componenti animati integrati

Animare la comparsa e la scomparsa con AnimatedVisibility

Componente verde che si mostra e si nasconde
Figura 1. Animazione della comparsa e della scomparsa di un elemento in una colonna

Il AnimatedVisibility composable anima la comparsa 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 dissolvenza in entrata e espansione e scompaiono con dissolvenza in uscita e riduzione. 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 + e ognuno accetta parametri facoltativi per personalizzarne il comportamento. Per ulteriori informazioni, consulta i riferimenti.

Esempi di EnterTransition e ExitTransition

EnterTransition ExitTransition
fadeIn
animazione di dissolvenza in entrata
fadeOut
animazione dissolvenza in uscita
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 verticale
scaleIn
Animazione di scale in
scaleOut
animazione di scale out
expandIn
espandi nell'animazione
shrinkOut
animazione di riduzione
expandHorizontally
animazione di espansione orizzontale
shrinkHorizontally
animazione di riduzione orizzontale
expandVertically
espandi verticalmente animazione
shrinkVertically
animazione di riduzione verticale

AnimatedVisibility offre anche una variante che accetta un MutableTransitionState. In questo modo, puoi attivare un'animazione non appena AnimatedVisibility viene aggiunto all'albero di 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. L'effetto visivo per ciascuno di questi elementi secondari è una combinazione delle animazioni specificate nel composable AnimatedVisibility e delle animazioni di entrata e 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 alcuna animazione in modo che gli elementi secondari possano avere animazioni distinte animateEnterExit. Per ottenere questo risultato, 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 uscita integrate, accedi all'istanza Transition sottostante tramite la proprietà transition all'interno della lambda di contenuti per AnimatedVisibility. Qualsiasi stato di animazione aggiunto all'istanza di transizione verrà eseguito contemporaneamente alle animazioni di entrata e uscita di AnimatedVisibility. AnimatedVisibility attende che tutte le animazioni in Transition siano terminate prima di rimuovere i relativi contenuti. Per le animazioni di uscita create indipendentemente da Transition (ad esempio utilizzando animate*AsState), AnimatedVisibility non sarebbe in grado di tenerne conto e pertanto potrebbe rimuovere il contenuto componibile prima che termini.

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 i dettagli su Transition, consulta updateTransition.

Animare in base allo stato di destinazione con AnimatedContent

Il componente AnimatedContent anima i suoi 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 visualizzati.

Per impostazione predefinita, i contenuti iniziali vengono dissolti e poi i contenuti di destinazione vengono visualizzati in dissolvenza in entrata (questo comportamento è chiamato dissolvenza incrociata). Puoi personalizzare questo comportamento dell'animazione specificando un oggetto ContentTransform per il parametro transitionSpec. Puoi creare ContentTransform combinando un EnterTransition con un ExitTransition utilizzando la funzione infissa with. Puoi applicare SizeTransform a ContentTransform allegandolo con la funzione infissa 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 l'aspetto dei contenuti di destinazione e ExitTransition definisce la modalità di scomparsa dei contenuti iniziali. Oltre a tutte le funzioni EnterTransition e ExitTransition disponibili per AnimatedVisibility, AnimatedContent offre slideIntoContainer e slideOutOfContainer. Queste sono alternative pratiche a slideInHorizontally/Vertically e slideOutHorizontally/Vertically che calcolano la distanza della slide in base alle dimensioni dei contenuti iniziali e dei contenuti di destinazione dei contenuti AnimatedContent.

SizeTransform definisce il modo in cui le dimensioni devono animarsi tra i contenuti iniziali e quelli di destinazione. Quando crei l'animazione, hai accesso sia alle dimensioni iniziali sia a quelle di destinazione. 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 AnimatedVisibility, il modificatore animateEnterExit è disponibile all'interno della lambda dei contenuti di AnimatedContent. Utilizza questo per applicare EnterAnimation e ExitAnimation a ciascuno dei figli diretti o indiretti separatamente.

Aggiungere un'animazione personalizzata

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

Animare la transizione tra due layout con Crossfade

Crossfade esegue l'animazione tra due layout con un'animazione di dissolvenza incrociata. Se attivi/disattivi il valore passato al parametro current, i contenuti vengono cambiati con un'animazione di dissolvenza incrociata.

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

Animare le modifiche delle dimensioni dei componenti componibili con animateContentSize

Il componente verde anima il cambio di dimensioni in modo fluido.
Figura 2. Componente componibile che anima in modo fluido il passaggio da una dimensione piccola a 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 di elenco

Se vuoi animare il riordino degli elementi all'interno di un elenco o di una griglia pigra, consulta la documentazione sull'animazione degli elementi del layout pigro.