Guida rapida alle animazioni in Compose

Compose ha molti meccanismi di animazione integrati e può essere difficile scegliere quale. Di seguito è riportato un elenco di casi d'uso comuni delle animazioni. Per informazioni più dettagliate sull'insieme completo di diverse opzioni API a tua disposizione, consulta la documentazione completa di Compose Animation.

Animare le proprietà composable comuni

Compose fornisce API pratiche che ti consentono di risolvere molti casi d'uso comuni per le animazioni. Questa sezione mostra come animare le proprietà comuni di un composable.

Animazione di comparsa / sparizione

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

Utilizza AnimatedVisibility per nascondere o mostrare un composable. I bambini all'interno di AnimatedVisibility possono utilizzare Modifier.animateEnterExit() per la propria transizione di entrata o di 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 enter e exit di AnimatedVisibility consentono di configurare il comportamento di un componibile quando viene visualizzato e scompare. Per saperne di più, consulta la documentazione completa.

Un'altra opzione per animare la visibilità di un composable è animare il valore alpha 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 il fatto che il composable rimane nella composizione e continua a occupare lo spazio in cui è disposto. Ciò potrebbe causare lo screen reader e altri meccanismi di accessibilità a considerare ancora l'elemento sullo schermo. D'altra parte, AnimatedVisibility rimuove l'elemento dalla composizione.

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

Anima il colore di sfondo

Componibile con colore di sfondo che cambia nel tempo come un'animazione, dove i colori si dissolvono l'uno nell'altro.
Figura 3. Animazione del colore di sfondo del composable

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

Questa opzione ha un rendimento migliore rispetto all'utilizzo di Modifier.background(). Modifier.background() è accettabile per un'impostazione di colore una tantum, ma quando si anima un colore nel tempo, questo potrebbe causare più ricostruzioni del necessario.

Per animare all'infinito il colore di sfondo, consulta la sezione sulla ripetizione di un'animazione.

Animare le dimensioni di un composable

Composable verde che anima la modifica delle dimensioni in modo fluido.
Figura 4. Componibile con animazione fluida tra una dimensione piccola e una più grande

Compose ti consente di animare le dimensioni dei composabili in diversi modi. Utilizza animateContentSize() per le animazioni tra le modifiche delle dimensioni composable.

Ad esempio, se hai una casella contenente 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 avvenire le modifiche delle dimensioni.

Animazione posizione del componibile

Componente componibile verde che si anima dolcemente verso il basso e verso destra
Figura 5. Spostamento di un composable con un offset

Per animare la posizione di un composable, 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
        }
)

Per assicurarti che i componibili non vengano disegnati sopra o sotto altri componibili quando anima la posizione o la dimensione, utilizza Modifier.layout{ }. Questo modificatore propaga le modifiche di dimensioni e posizione al contenitore principale, che poi influisce su gli altri elementi secondari.

Ad esempio, se stai spostando un Box all'interno di un Column e gli altri elementi secondari devono spostarsi 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 caselle con la seconda che anima la posizione X,Y, la terza casella risponde spostandosi anche per l'importo Y.
Figura 6. Animazione con Modifier.layout{ }

Animare il padding di un composable

Il componibile verde diventa sempre più grande al clic, con la spaziatura interna animata
Figura 7. Composable con animazioni del padding

Per animare la spaziatura interna di un componibile, utilizza animateDpAsState combinato 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 componente componibile

Figura 8. Animazione dell'elevazione del composable al clic

Per animare l'elevazione di un composable, utilizza animateDpAsState combinato con Modifier.graphicsLayer{ }. Per i dislivelli occasionali, utilizza Modifier.shadow(). Se stai animando l'ombra, l'opzione più performante è l'utilizzo del modificatore Modifier.graphicsLayer{ }.

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 composable Card e imposta la proprietà altezza su valori diversi per stato.

Animare la scala, la traslazione o la rotazione del testo

Frase componibile di testo
Figura 9. Testo che passa senza interruzioni da una dimensione all'altra

Quando animi la scala, la traslazione o la rotazione del testo, imposta il parametro textMotion su TextStyle su TextMotion.Animated. In questo modo, le transizioni tra le animazioni di testo saranno più fluide. Usa 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 del testo animato

Le parole
Figura 10. Esempio che mostra l'animazione del colore del testo

Per animare il colore del testo, utilizza la lambda color nel composable 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

Frase sullo schermo verde
Figura 11. Utilizzo di AnimatedContent per animare le modifiche tra diversi composabili (rallentato)

Utilizza AnimatedContent per animare il passaggio da un composable all'altro. Se vuoi semplicemente una dissolvenza standard tra i composable, utilizza 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 per mostrare molti tipi diversi di transizioni di entrata e di uscita. Per ulteriori informazioni, consulta la documentazione su AnimatedContent o questo post del blog su AnimatedContent.

Animare durante la navigazione verso destinazioni diverse

Due composabili, uno verde con la dicitura Landing e uno blu con la dicitura Dettaglio, che si animano facendo scorrere il composable dei dettagli sopra quello della pagina di destinazione.
Figura 12. Animazione tra composabili utilizzando navigation-compose

Per animare le transizioni tra composabili quando utilizzi l'elemento navigation-compose, specifica enterTransition e exitTransition in un composable. Puoi anche impostare l'animazione predefinita da utilizzare per tutte le destinazioni a livello superiore NavHost:

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.

Ripeti un'animazione

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

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

Utilizza finiteRepeatable per ripetere un numero predefinito 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. Avvia un'animazione all'avvio di un composable, che puoi utilizzare per gestire la modifica dello stato dell'animazione. Utilizza 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 tra un cerchio e l'altro, uno dopo l'altro.
Figura 14. Diagramma che indica l'avanzamento di un'animazione sequenziale, uno alla volta.

Utilizza le API Animatable coroutine per eseguire animazioni sequenziali o simultanee. Se chiami animateTo su Animatable una dopo l'altra, 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 tutti contemporaneamente.
Figura 15. Diagramma che indica l'avanzamento delle animazioni simultanee.

Utilizza le API Coroutine (Animatable#animateTo() o animate) o l'API Transition per ottenere animazioni in parallelo. Se utilizzi più funzioni di lancio 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 gestire contemporaneamente molte animazioni di proprietà diverse. L'esempio seguente anima due proprietà controllate da una modifica dello 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 le prestazioni delle animazioni

Le animazioni in Compose possono causare problemi di prestazioni. Ciò è dovuto alla natura di un'animazione: spostare o modificare rapidamente pixel sullo schermo, fotogramma per fotogramma per creare l'illusione di movimento.

Considera le diverse fasi di Componi: composizione, layout e disegno. Se la tua animazione modifica la fase di layout, richiede il relayout e il ridisegni di tutti i composabili interessati. Se l'animazione avviene nella fase di disegno, per impostazione predefinita avrà prestazioni migliori rispetto a quando eseguirai l'animazione nella fase di layout, in quanto richiederebbe meno lavoro da eseguire nel complesso.

Per assicurarti che la tua app faccia il meno possibile durante l'animazione, scegli la versione lambda di un Modifier, se possibile. In questo modo viene saltata la ricompozione ed eseguita l'animazione al di fuori della fase di composizione, altrimenti utilizza Modifier.graphicsLayer{ }, poiché questo modificatore viene eseguito sempre nella fase di disegno. Per ulteriori informazioni, consulta la sezione Rimandare le letture nella documentazione sul rendimento.

Modificare la durata dell'animazione

Per impostazione predefinita, Compose utilizza le animazioni elastiche per la maggior parte delle animazioni. Le molle, o animazioni basate sulla fisica, sembrano più naturali. Sono inoltre interrompibili poiché tengono conto della velocità corrente dell'oggetto anziché di un tempo fisso. Se vuoi eseguire l'override del valore predefinito, tutte le API di animazione mostrate sopra hanno la possibilità di impostare un animationSpec per personalizzare il funzionamento di un'animazione, ad esempio per eseguirla per una determinata durata o renderla più elastica.

Di seguito è riportato un riepilogo delle diverse opzioni 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 dell'animazione.
  • tween (abbreviazione di tra): animazione basata sulla durata, anima tra due valori con una funzione Easing.
  • keyframes: specifica per specificare i valori in determinati punti chiave di un'animazione.
  • repeatable: specifica basata sulla durata eseguita un certo numero di volte, specificata da RepeatMode.
  • infiniteRepeatable: specifica basata sulla durata che viene eseguita all'infinito.
  • snap: passa immediatamente al valore finale senza alcuna animazione.
Scrivi il testo alternativo qui
Figura 16. Nessun set di specifiche e set di specifiche di primavera personalizzato

Leggi la documentazione completa per saperne di più su animationSpecs.

Risorse aggiuntive

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