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 del layout dei contenuti.

Animare l'aspetto e la scomparsa

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

Il AnimatedVisibility composable anima l'aspetto e la scomparsa dei relativi 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 una dissolvenza in entrata e un'espansione e scompaiono con una dissolvenza in uscita e una riduzione. Personalizza questa transizione specificando EnterTransition e ExitTransition oggetti.

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 personalizzarne il 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 UI scorre orizzontalmente fuori dalla visualizzazione.
slideInVertically
Un elemento UI scorre verticalmente fino a essere visibile.
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 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 fino a diventare visibile.
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 degli elementi secondari

I contenuti all'interno di AnimatedVisibility (elementi secondari diretti o indiretti) possono utilizzare il animateEnterExit modificatore per specificare un comportamento di animazione diverso per ognuno di essi. 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 ogni elemento secondario possa avere le proprie animazioni distinte tramite 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 uscita integrate, accedi all'istanza Transition sottostante utilizzando la proprietà transition all'interno della lambda dei contenuti per AnimatedVisibility. Tutti gli stati di animazione aggiunti all'istanza Transition verranno eseguiti contemporaneamente alle animazioni di entrata e uscita di AnimatedVisibility. AnimatedVisibility attende il completamento di tutte le animazioni in Transition prima di rimuovere 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, pertanto, potrebbe rimuovere il composable dei contenuti prima che vengano completate.

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 AnimatedContent composable anima i contenuti 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 visualizzati con una dissolvenza in uscita e poi i contenuti di destinazione vengono visualizzati con una dissolvenza in entrata (questo comportamento è chiamato dissolvenza). Puoi personalizzare questo comportamento di animazione specificando un ContentTransform oggetto nel parametro transitionSpec. Puoi creare un istanza di ContentTransform combinando un EnterTransition oggetto con un ExitTransition oggetto utilizzando la funzione infissa with. Puoi applicare SizeTransform all'oggetto ContentTransform collegandolo con la using funzione infissa.

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 la modalità di visualizzazione dei contenuti di destinazione, mentre 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 di scorrimento in base alle dimensioni dei contenuti iniziali e dei contenuti di destinazione dei contenuti AnimatedContent.

SizeTransform definisce la modalità di animazione delle dimensioni 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 degli elementi secondari

Come per AnimatedVisibility, il animateEnterExit modificatore è disponibile all'interno della lambda dei contenuti di AnimatedContent. Utilizzalo per applicare EnterAnimation e ExitAnimation a ciascuno degli elementi secondari diretti o indiretti separatamente.

Aggiungere un'animazione personalizzata

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

Animare tra due layout

Crossfade anima due layout con un'animazione di dissolvenza incrociata. Attivando e disattivando il valore passato al parametro current, i contenuti vengono scambiati 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 composable.

Animare le modifiche delle dimensioni dei composable

Il componente verde anima il cambio di dimensioni in modo fluido.
Figura 2. Composable che anima senza problemi 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 degli elementi dell'elenco

Se vuoi animare il riordinamento degli elementi all'interno di un elenco o di una griglia Lazy, consulta la documentazione relativa all'animazione degli elementi del layout Lazy.