Modificatori di animazione e componibili

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

Composable animati integrati

Compose fornisce diversi composable che animano l'aspetto, la scomparsa e le modifiche al layout dei contenuti.

Animare la comparsa e la scomparsa

Il composable 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 ed espansione e scompaiono con dissolvenza in uscita e riduzione. Personalizza questa transizione specificando gli oggetti 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 mostrato nell'esempio precedente, puoi combinare più oggetti EnterTransition o ExitTransition con un operatore + e ognuno accetta parametri facoltativi per personalizzare il proprio comportamento. Per ulteriori informazioni, consulta le pagine di riferimento.

Esempi di transizioni di entrata e uscita

EnterTransition ExitTransition
fadeIn
Un elemento UI appare gradualmente.
fadeOut
Un elemento dell'interfaccia utente scompare gradualmente dalla visualizzazione.
slideIn
Un elemento UI scorre sullo schermo.
slideOut
Un elemento UI scompare dallo schermo.
slideInHorizontally
Un elemento dell'interfaccia utente scorre orizzontalmente nella visualizzazione.
slideOutHorizontally
Un elemento dell'interfaccia utente scorre orizzontalmente fuori dalla visualizzazione.
slideInVertically
Un elemento dell'interfaccia utente scorre verticalmente fino a essere visualizzato.
slideOutVertically
Un elemento UI scorre verticalmente fuori dalla visualizzazione.
scaleIn
Un elemento dell'interfaccia utente viene visualizzato in primo piano.
scaleOut
Un elemento UI viene ridimensionato e scompare dalla visualizzazione.
expandIn
Un elemento della UI si espande in vista da un punto centrale.
shrinkOut
Un elemento UI si restringe fuori dalla visualizzazione fino a un punto centrale.
expandHorizontally
Un elemento dell'interfaccia utente si espande orizzontalmente fino a diventare visibile.
shrinkHorizontally
Un elemento UI si restringe orizzontalmente fuori dalla visualizzazione.
expandVertically
Un elemento dell'interfaccia utente si espande verticalmente.
shrinkVertically
Un elemento UI si restringe verticalmente fino a scomparire dalla visualizzazione.

AnimatedVisibility offre anche una variante che accetta un argomento MutableTransitionState. In questo modo puoi attivare un'animazione non appena il composable 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 che AnimatedVisibility non applichi alcuna animazione in modo che gli elementi secondari possano avere animazioni distinte animateEnterExit. Per farlo, specifica EnterTransition.None e ExitTransition.None nel componibile 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 utilizzando la proprietà transition all'interno della lambda di contenuti per AnimatedVisibility. Gli stati di animazione aggiunti all'istanza di transizione verranno eseguiti 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 composable dei contenuti 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 scoprire di più sull'utilizzo di Transition per gestire le animazioni, consulta Animare più proprietà contemporaneamente con una transizione.

Animare in base allo stato di destinazione

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

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 un'istanza di ContentTransform combinando un oggetto EnterTransition con un oggetto ExitTransition utilizzando la funzione infissa with. Puoi applicare SizeTransform all'oggetto ContentTransform collegandolo 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. Si tratta di alternative pratiche a slideInHorizontally/Vertically e slideOutHorizontally/Vertically che calcolano la distanza di scorrimento in base alle dimensioni dei contenuti iniziali e di quelli 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

Proprio 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 il passaggio tra due layout

Crossfade esegue la transizione 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

Compose fornisce modificatori per animare modifiche specifiche direttamente sui composables.

Animare le modifiche delle dimensioni dei componenti componibili

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 di layout pigro.