Guida rapida alle animazioni in Compose

Compose ha molti meccanismi di animazione integrati e può essere complicato sapere quale scegliere. Di seguito è riportato un elenco di casi d'uso comuni per le animazioni. Per informazioni più dettagliate sul set completo delle diverse opzioni API disponibili, leggi la documentazione completa di Compose Animation.

Animazione delle proprietà componibili comuni

Compose fornisce utili API che consentono di risolvere molti casi d'uso comuni dell'animazione. Questa sezione mostra come animare le proprietà comuni di un componibile.

Animazione che appare / scompare

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

Utilizza AnimatedVisibility per nascondere o mostrare un componibile. I bambini all'interno di AnimatedVisibility possono utilizzare Modifier.animateEnterExit() per la propria transizione di entrata o uscita.

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

I parametri di entrata e uscita di AnimatedVisibility consentono di configurare il comportamento di un componibile quando appare e scompare. Per ulteriori informazioni, consulta la documentazione completa.

Un'altra opzione per animare la visibilità di un componibile è quella di animare l'alfa nel tempo utilizzando animateFloatAsState:

var visible by remember {
    mutableStateOf(true)
}
val animatedAlpha by animateFloatAsState(
    targetValue = if (visible) 1.0f else 0f,
    label = "alpha"
)
Box(
    modifier = Modifier
        .size(200.dp)
        .graphicsLayer {
            alpha = animatedAlpha
        }
        .clip(RoundedCornerShape(8.dp))
        .background(colorGreen)
        .align(Alignment.TopCenter)
) {
}

Tuttavia, la modifica dell'alpha comporta l'avvertenza che il componibile rimane nella composizione e continua a occupare lo spazio in cui è disposto. Ciò potrebbe far sì che gli screen reader e altri meccanismi di accessibilità continuino a prendere in considerazione l'elemento sullo schermo. AnimatedVisibility, d'altra parte, rimuove l'elemento dalla composizione.

Animazione dell'alpha di un componibile
Figura 2. Animazione dell'alpha di un componibile

Anima colore sfondo

Componibile con il colore di sfondo che cambia nel tempo come un'animazione, in cui i colori si scoloriscono l'uno nell'altro.
Figura 3. Animazione del colore di sfondo del componibile

val animatedColor by animateColorAsState(
    if (animateBackgroundColor) colorGreen else colorBlue,
    label = "color"
)
Column(
    modifier = Modifier.drawBehind {
        drawRect(animatedColor)
    }
) {
    // your composable here
}

Questa opzione offre un rendimento migliore rispetto all'uso di Modifier.background(). Modifier.background() è accettabile per un'impostazione colore one-shot, ma l'animazione di un colore nel tempo potrebbe causare più ricomposizioni del necessario.

Per animare all'infinito il colore di sfondo, consulta la sezione relativa alla ripetizione di una sezione di animazione.

Animare le dimensioni di un componibile

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

Compose ti consente di animare le dimensioni dei componibili in diversi modi. Utilizza animateContentSize() per le animazioni tra le modifiche alle dimensioni del componibile.

Ad esempio, se hai una casella che contiene testo che può espandersi da una a più righe, puoi utilizzare Modifier.animateContentSize() per ottenere una transizione più fluida:

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
        }

) {
}

Puoi anche utilizzare AnimatedContent, con un SizeTransform per descrivere come devono essere apportate le modifiche alle dimensioni.

Animazione della posizione del componibile

Componibile verde che si anima in modo fluido verso il basso e a destra
Figura 5. Spostamento componibile di un offset

Per animare la posizione di un componibile, utilizza Modifier.offset{ } combinato con animateIntOffsetAsState().

var moved by remember { mutableStateOf(false) }
val pxToMove = with(LocalDensity.current) {
    100.dp.toPx().roundToInt()
}
val offset by animateIntOffsetAsState(
    targetValue = if (moved) {
        IntOffset(pxToMove, pxToMove)
    } else {
        IntOffset.Zero
    },
    label = "offset"
)

Box(
    modifier = Modifier
        .offset {
            offset
        }
        .background(colorBlue)
        .size(100.dp)
        .clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null
        ) {
            moved = !moved
        }
)

Se vuoi assicurarti che i componibili non vengano disegnati sopra o sotto altri elementi componibili durante l'animazione di posizioni o dimensioni, utilizza Modifier.layout{ }. Questo modificatore propaga le modifiche di dimensioni e posizione all'elemento padre, il che a sua volta influenza gli altri elementi secondari.

Ad esempio, se stai spostando un Box all'interno di un Column e gli altri elementi secondari devono muoversi quando Box si sposta, includi le informazioni di offset con Modifier.layout{ } come segue:

var toggled by remember {
    mutableStateOf(false)
}
val interactionSource = remember {
    MutableInteractionSource()
}
Column(
    modifier = Modifier
        .padding(16.dp)
        .fillMaxSize()
        .clickable(indication = null, interactionSource = interactionSource) {
            toggled = !toggled
        }
) {
    val offsetTarget = if (toggled) {
        IntOffset(150, 150)
    } else {
        IntOffset.Zero
    }
    val offset = animateIntOffsetAsState(
        targetValue = offsetTarget, label = "offset"
    )
    Box(
        modifier = Modifier
            .size(100.dp)
            .background(colorBlue)
    )
    Box(
        modifier = Modifier
            .layout { measurable, constraints ->
                val offsetValue = if (isLookingAhead) offsetTarget else offset.value
                val placeable = measurable.measure(constraints)
                layout(placeable.width + offsetValue.x, placeable.height + offsetValue.y) {
                    placeable.placeRelative(offsetValue)
                }
            }
            .size(100.dp)
            .background(colorGreen)
    )
    Box(
        modifier = Modifier
            .size(100.dp)
            .background(colorBlue)
    )
}

2 riquadri con il secondo riquadro che anima la sua posizione X e Y, il terzo riquadro che risponde spostandosi di una quantità Y.
Figura 6. Animazione in corso con Modifier.layout{ }

Animare la spaziatura interna di un componibile

Un elemento componibile verde che diventa sempre più piccolo al clic, con la spaziatura interna animata
Figura 7. Componibile con spaziatura interna animata

Per animare la spaziatura interna di un componibile, utilizza animateDpAsState in combinazione con Modifier.padding():

var toggled by remember {
    mutableStateOf(false)
}
val animatedPadding by animateDpAsState(
    if (toggled) {
        0.dp
    } else {
        20.dp
    },
    label = "padding"
)
Box(
    modifier = Modifier
        .aspectRatio(1f)
        .fillMaxSize()
        .padding(animatedPadding)
        .background(Color(0xff53D9A1))
        .clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null
        ) {
            toggled = !toggled
        }
)

Animare l'elevazione di un componibile

Figura 8. Animazione dell'elevazione del componente componibile al clic

Per animare l'elevazione di un componibile, utilizza animateDpAsState combinato con Modifier.graphicsLayer{ }. Per cambiamenti di altitudine una tantum, utilizza Modifier.shadow(). Se stai animando l'ombra, l'uso del modificatore Modifier.graphicsLayer{ } è l'opzione più efficiente.

val mutableInteractionSource = remember {
    MutableInteractionSource()
}
val pressed = mutableInteractionSource.collectIsPressedAsState()
val elevation = animateDpAsState(
    targetValue = if (pressed.value) {
        32.dp
    } else {
        8.dp
    },
    label = "elevation"
)
Box(
    modifier = Modifier
        .size(100.dp)
        .align(Alignment.Center)
        .graphicsLayer {
            this.shadowElevation = elevation.value.toPx()
        }
        .clickable(interactionSource = mutableInteractionSource, indication = null) {
        }
        .background(colorGreen)
) {
}

In alternativa, utilizza il componibile Card e imposta la proprietà elevazione su valori diversi per stato.

Applica animazioni a scala, traduzione o rotazione del testo

Testo componibile che dice
Figura 9. Testo che si anima in modo fluido tra due dimensioni

Quando attivi l'animazione di scala, traslazione o rotazione del testo, imposta il parametro textMotion su TextStyle su TextMotion.Animated. Ciò garantisce transizioni più fluide tra le animazioni del testo. Utilizza Modifier.graphicsLayer{ } per tradurre, ruotare o ridimensionare il testo.

val infiniteTransition = rememberInfiniteTransition(label = "infinite transition")
val scale by infiniteTransition.animateFloat(
    initialValue = 1f,
    targetValue = 8f,
    animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse),
    label = "scale"
)
Box(modifier = Modifier.fillMaxSize()) {
    Text(
        text = "Hello",
        modifier = Modifier
            .graphicsLayer {
                scaleX = scale
                scaleY = scale
                transformOrigin = TransformOrigin.Center
            }
            .align(Alignment.Center),
        // Text composable does not take TextMotion as a parameter.
        // Provide it via style argument but make sure that we are copying from current theme
        style = LocalTextStyle.current.copy(textMotion = TextMotion.Animated)
    )
}

Colore testo animazione

Le parole
Figura 10. Esempio di colore dell'animazione del testo

Per animare il colore del testo, utilizza la funzione lambda color nell'elemento componibile BasicText:

val infiniteTransition = rememberInfiniteTransition(label = "infinite transition")
val animatedColor by infiniteTransition.animateColor(
    initialValue = Color(0xFF60DDAD),
    targetValue = Color(0xFF4285F4),
    animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse),
    label = "color"
)

BasicText(
    text = "Hello Compose",
    color = {
        animatedColor
    },
    // ...
)

Passare da un tipo di contenuto all'altro

Schermo verde che dice
Figura 11. Utilizzo di AnimatedContent per animare le modifiche tra diversi componibili (rallentato)

Usa AnimatedContent per animare tra diversi componibili. Se vuoi solo una dissolvenza standard tra i componibili, usa Crossfade.

var state by remember {
    mutableStateOf(UiState.Loading)
}
AnimatedContent(
    state,
    transitionSpec = {
        fadeIn(
            animationSpec = tween(3000)
        ) togetherWith fadeOut(animationSpec = tween(3000))
    },
    modifier = Modifier.clickable(
        interactionSource = remember { MutableInteractionSource() },
        indication = null
    ) {
        state = when (state) {
            UiState.Loading -> UiState.Loaded
            UiState.Loaded -> UiState.Error
            UiState.Error -> UiState.Loading
        }
    },
    label = "Animated Content"
) { targetState ->
    when (targetState) {
        UiState.Loading -> {
            LoadingScreen()
        }
        UiState.Loaded -> {
            LoadedScreen()
        }
        UiState.Error -> {
            ErrorScreen()
        }
    }
}

AnimatedContent può essere personalizzato in modo da mostrare molti tipi diversi di transizioni di entrata e di uscita. Per saperne di più, leggi la documentazione su AnimatedContent o leggi questo post del blog su AnimatedContent.

Crea animazioni mentre navighi verso destinazioni diverse

Due componibili, uno verde con la scritta Landing e l'altro blu con la scritta Dettagli, che si anima facendo scorrere il dettaglio componibile sul pianerottolo.
Figura 12. Animazione tra elementi componibili utilizzando navigatore-compose

Per animare le transizioni tra le componibili quando utilizzi l'artefatto navigation-compose, specifica enterTransition e exitTransition su un componibile. Puoi anche impostare l'animazione predefinita da utilizzare per tutte le destinazioni del livello NavHost di primo livello:

val navController = rememberNavController()
NavHost(
    navController = navController, startDestination = "landing",
    enterTransition = { EnterTransition.None },
    exitTransition = { ExitTransition.None }
) {
    composable("landing") {
        ScreenLanding(
            // ...
        )
    }
    composable(
        "detail/{photoUrl}",
        arguments = listOf(navArgument("photoUrl") { type = NavType.StringType }),
        enterTransition = {
            fadeIn(
                animationSpec = tween(
                    300, easing = LinearEasing
                )
            ) + slideIntoContainer(
                animationSpec = tween(300, easing = EaseIn),
                towards = AnimatedContentTransitionScope.SlideDirection.Start
            )
        },
        exitTransition = {
            fadeOut(
                animationSpec = tween(
                    300, easing = LinearEasing
                )
            ) + slideOutOfContainer(
                animationSpec = tween(300, easing = EaseOut),
                towards = AnimatedContentTransitionScope.SlideDirection.End
            )
        }
    ) { backStackEntry ->
        ScreenDetails(
            // ...
        )
    }
}

Esistono molti tipi diversi di transizioni di entrata e di uscita che applicano effetti diversi ai contenuti in entrata e in uscita. Per saperne di più, consulta la documentazione.

Ripetere un'animazione

Uno sfondo verde che si trasforma in uno sfondo blu all'infinito, animandosi tra i due colori.
Figura 13. Colore di sfondo che si anima tra due valori all'infinito

Usa rememberInfiniteTransition con un infiniteRepeatable animationSpec per ripetere continuamente l'animazione. Modifica RepeatModes per specificare come deve andare avanti e indietro.

Usa finiteRepeatable per ripetere un determinato numero di volte.

val infiniteTransition = rememberInfiniteTransition(label = "infinite")
val color by infiniteTransition.animateColor(
    initialValue = Color.Green,
    targetValue = Color.Blue,
    animationSpec = infiniteRepeatable(
        animation = tween(1000, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    ),
    label = "color"
)
Column(
    modifier = Modifier.drawBehind {
        drawRect(color)
    }
) {
    // your composable here
}

Avvia un'animazione all'avvio di un componibile

LaunchedEffect viene eseguito quando un componibile entra nella composizione. All'avvio di un componibile, l'animazione viene avviata. Puoi utilizzarla per generare il cambiamento dello stato dell'animazione. Utilizzo di Animatable con il metodo animateTo per avviare l'animazione all'avvio:

val alphaAnimation = remember {
    Animatable(0f)
}
LaunchedEffect(Unit) {
    alphaAnimation.animateTo(1f)
}
Box(
    modifier = Modifier.graphicsLayer {
        alpha = alphaAnimation.value
    }
)

Creare animazioni sequenziali

Quattro cerchi con frecce verdi che si animano l'uno dopo l'altro, uno dopo l'altro.
Figura 14. Diagramma che indica l'avanzamento di un'animazione sequenziale, una per una.

Utilizza le API coroutine Animatable per eseguire animazioni sequenziali o simultanee. Se si chiama animateTo su Animatable uno dopo l'altro, ogni animazione attende il termine delle animazioni precedenti prima di procedere . Il motivo è che si tratta di una funzione di sospensione.

val alphaAnimation = remember { Animatable(0f) }
val yAnimation = remember { Animatable(0f) }

LaunchedEffect("animationKey") {
    alphaAnimation.animateTo(1f)
    yAnimation.animateTo(100f)
    yAnimation.animateTo(500f, animationSpec = tween(100))
}

Creare animazioni simultanee

Tre cerchi con frecce verdi che si animano su ciascuno di essi e che si animano tutti contemporaneamente.
Figura 15. Diagramma che indica l'avanzamento delle animazioni simultanee, tutte contemporaneamente.

Utilizza le API coroutine (Animatable#animateTo() o animate) o l'API Transition per ottenere animazioni contemporanee. Se utilizzi più funzioni di avvio in un contesto di coroutine, le animazioni vengono avviate contemporaneamente:

val alphaAnimation = remember { Animatable(0f) }
val yAnimation = remember { Animatable(0f) }

LaunchedEffect("animationKey") {
    launch {
        alphaAnimation.animateTo(1f)
    }
    launch {
        yAnimation.animateTo(100f)
    }
}

Puoi utilizzare l'API updateTransition per utilizzare lo stesso stato per generare contemporaneamente molte animazioni diverse delle proprietà. L'esempio seguente anima due proprietà controllate da una modifica di stato, rect e borderWidth:

var currentState by remember { mutableStateOf(BoxState.Collapsed) }
val transition = updateTransition(currentState, label = "transition")

val rect by transition.animateRect(label = "rect") { state ->
    when (state) {
        BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
        BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
    }
}
val borderWidth by transition.animateDp(label = "borderWidth") { state ->
    when (state) {
        BoxState.Collapsed -> 1.dp
        BoxState.Expanded -> 0.dp
    }
}

Ottimizzare il rendimento delle animazioni

Le animazioni in Compose possono causare problemi di prestazioni. Ciò è dovuto alla natura di un'animazione: spostamento o cambiamento rapido dei pixel sullo schermo, foto per fotogramma per creare l'illusione del movimento.

Considera le diverse fasi di Compose: composizione, layout e disegno. Se l'animazione cambia la fase del layout, richiede il relayout e riprogettazione di tutti i componibili interessati. Se l'animazione si trova nella fase di disegno, per impostazione predefinita avrà un rendimento migliore rispetto a quando eseguissi l'animazione nella fase di layout, poiché il complesso richiede meno lavoro.

Per assicurarti che l'app operi il meno possibile durante l'animazione, scegli la versione lambda di Modifier se possibile. In questo modo viene saltata la ricomposizione ed è eseguita l'animazione al di fuori della fase di composizione. In caso contrario, utilizza Modifier.graphicsLayer{ }, poiché questo modificatore viene sempre eseguito in fase di disegno. Per ulteriori informazioni, consulta la sezione Differenziare le letture nella documentazione sulle prestazioni.

Modificare la durata dell'animazione

Per impostazione predefinita, la composizione utilizza le animazioni primavera per la maggior parte delle animazioni. Le molle, o le animazioni basate sulla fisica, sembrano più naturali. Sono interrompibili perché tengono conto della velocità attuale dell'oggetto, invece di un tempo fisso. Se vuoi eseguire l'override dell'impostazione predefinita, tutte le API di animazione illustrate sopra hanno la possibilità di impostare un animationSpec per personalizzare la modalità di esecuzione di un'animazione, indipendentemente dal fatto che tu voglia eseguirla per un determinato periodo di tempo o avere una maggiore elasticità.

Di seguito è riportato un riepilogo delle diverse opzioni per animationSpec:

  • spring: animazione basata sulla fisica, l'impostazione predefinita per tutte le animazioni. Puoi modificare la rigidità o il rapporto di smorzamento per ottenere un aspetto diverso per le animazioni.
  • tween (abbreviazione di tra): un'animazione basata sulla durata, che si anima tra due valori con una funzione Easing.
  • keyframes: specifiche per la specifica di valori in determinati punti chiave in un'animazione.
  • repeatable: specifica basata sulla durata che viene eseguita un determinato numero di volte, specificato da RepeatMode.
  • infiniteRepeatable: specifica basata sulla durata eseguita all'infinito.
  • snap: aggancia istantaneamente al valore finale senza alcuna animazione.
Scrivi qui il testo alternativo
Figura 16. Nessun set di specifiche o set di specifiche Custom Spring

Leggi la documentazione completa per ulteriori informazioni su animationSpecs.

Risorse aggiuntive

Per altri esempi di animazioni divertenti in Compose, dai un'occhiata a quanto segue: