Modificatori di animazione e componibili

Compose include elementi componibili e modificatori integrati per gestire i casi d'uso più comuni delle animazioni.

Componibili animati integrati

Animazione dell'aspetto e della scomparsa con AnimatedVisibility

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

L'elemento componibile AnimatedVisibility anima l'aspetto 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 compaiono per dissolvenza in entrata ed espansione e scompaiono sbiadindo e riducendosi. 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 vedi nell'esempio precedente, puoi combinare più oggetti EnterTransition o ExitTransition con un operatore +, ognuno dei quali accetta parametri facoltativi per personalizzarne il comportamento. Per ulteriori informazioni, consulta i riferimenti.

Esempi di EnterTransition e ExitTransition

Entrata Esci dalla transizione
fadeIn
animazione con dissolvenza in entrata
fadeOut
animazione con dissolvenza in uscita
slideIn
animazione della slide
slideOut
animazione slide in uscita
slideInHorizontally
animazione scorri in orizzontale
slideOutHorizontally
animazione scorri in orizzontale
slideInVertically
animazione slide verticale
slideOutVertically
animazione a scorrimento verticale
scaleIn
Ridimensiona nell'animazione
scaleOut
animazione scale out
expandIn
espandi nell'animazione
shrinkOut
riduci animazione
expandHorizontally
animazione di espansione orizzontalmente
shrinkHorizontally
animazione riduci orizzontalmente
expandVertically
animazione espandi verticalmente
shrinkVertically
animazione di riduzione verticalmente

AnimatedVisibility offre anche una variante che richiede 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"
        }
    )
}

Animazione di entrata e uscita per i bambini

I contenuti all'interno di AnimatedVisibility (elementi secondari diretti o indiretti) possono utilizzare il modificatore di animateEnterExit per specificare diversi comportamenti dell'animazione per ciascuno di essi. L'effetto visivo per ognuno di questi elementi secondari è una combinazione delle animazioni specificate nell'elemento componibile AnimatedVisibility e delle animazioni di entrata e uscita del riquadro 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 ognuno possa avere le proprie animazioni distinte di animateEnterExit. Per ottenere questo risultato, specifica EnterTransition.None e ExitTransition.None nel componibile AnimatedVisibility.

Aggiungi 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 funzione lambda dei contenuti per AnimatedVisibility. Qualsiasi stato dell'animazione aggiunto all'istanza di transizione verrà eseguito contemporaneamente alle animazioni di entrata e uscita di AnimatedVisibility. AnimatedVisibility attende la fine di tutte le animazioni in Transition prima di rimuovere i relativi contenuti. Per le animazioni di uscita create in modo indipendente da Transition (ad esempio, l'utilizzo di animate*AsState), AnimatedVisibility non potrebbe tenerne conto e pertanto potrebbe rimuovere i contenuti componibili prima che vengano completati.

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, vedi updateTransizione.

Applica l'animazione in base allo stato target con AnimatedContent

L'elemento componibile AnimatedContent anima i suoi contenuti quando cambiano in base a uno stato target.

Row {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Add")
    }
    AnimatedContent(targetState = count) { 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, il contenuto iniziale scompare e poi i contenuti di destinazione svaniscono (questo comportamento è chiamato dissolvenza attraverso). Puoi personalizzare il comportamento di questo animazione specificando un oggetto ContentTransform nel parametro transitionSpec. Puoi creare ContentTransform combinando un EnterTransition con una ExitTransition utilizzando la funzione infisso di with. Puoi applicare SizeTransform a ContentTransform collegandolo con la funzione di infisso 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() with
                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() with
                slideOutVertically { height -> height } + fadeOut()
        }.using(
            // Disable clipping since the faded slide-in/out should
            // be displayed out of bounds.
            SizeTransform(clip = false)
        )
    }
) { targetCount ->
    Text(text = "$targetCount")
}

EnterTransition definisce in che modo devono apparire i contenuti di destinazione, mentre ExitTransition definisce in che modo devono scomparire i contenuti iniziali. Oltre a tutte le funzioni EnterTransition e ExitTransition disponibili per AnimatedVisibility, AnimatedContent offre slideIntoContainer e slideOutOfContainer. Queste sono alternative utili a slideInHorizontally/Vertically e slideOutHorizontally/Vertically che calcolano la distanza delle slide in base alle dimensioni dei contenuti iniziali e ai contenuti target dei contenuti AnimatedContent.

SizeTransform definisce il modo in cui la dimensione deve animarsi tra il contenuto iniziale e quello di destinazione. Hai accesso sia alla dimensione iniziale che a quella di destinazione quando crei l'animazione. SizeTransform controlla anche se i contenuti devono essere ritagliati 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)) with
                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
                        }
                    }
                }
        }
    ) { targetExpanded ->
        if (targetExpanded) {
            Expanded()
        } else {
            ContentIcon()
        }
    }
}

Animazione delle transizioni di entrata e uscita figlio

Proprio come AnimatedVisibility, il modificatore di animateEnterExit è disponibile all'interno del lambda dei contenuti di AnimatedContent. Utilizza questo attributo per applicare EnterAnimation e ExitAnimation separatamente a ciascuno dei publisher secondari diretti o indiretti.

Aggiungi animazione personalizzata

Proprio come AnimatedVisibility, il campo transition è disponibile all'interno della funzione lambda dei contenuti di AnimatedContent. Utilizzalo per creare un effetto di animazione personalizzato che viene eseguito in contemporanea alla transizione AnimatedContent. Per i dettagli, consulta updateTransizione.

Crea animazioni tra due layout con Crossfade

Crossfade si anima tra due layout con un'animazione a dissolvenza incrociata. Se attivi/disattivi il valore passato al parametro current, i contenuti vengono scambiati con un'animazione a dissolvenza incrociata.

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

Modificatori di animazione integrati

Animazione delle modifiche alle dimensioni componibili con animateContentSize

Il componibile verde che anima le sue dimensioni cambia facilmente.
Figura 2. Componibile che si anima in modo fluido tra una dimensione piccola e una più grande

Il modificatore animateContentSize anima una modifica di dimensione.

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
        }

) {
}

Elenco animazioni degli elementi

Se stai cercando di animare il riordinamento di elementi in un elenco o in una griglia Lazy, consulta la documentazione relativa all'animazione degli elementi di layout Lazy.