Compose ha molti meccanismi di animazione integrati e può essere difficile scegliere quello giusto. Di seguito è riportato un elenco dei casi d'uso comuni delle animazioni. Per informazioni più dettagliate sull'insieme completo delle diverse opzioni API a tua disposizione, leggi la documentazione completa di Compose Animation.
Animare le proprietà dei componibili comuni
Compose fornisce API pratiche che ti consentono di risolvere molti casi d'uso comuni delle animazioni. Questa sezione mostra come animare le proprietà comuni di un componibile.
Animare la visualizzazione / la scomparsa
Utilizza AnimatedVisibility per nascondere o mostrare un componibile. I figli 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 viene visualizzato e scompare. Per ulteriori informazioni, leggi la completa
documentazione.
Un'altra opzione per animare la visibilità di un componibile è 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'alfa comporta l'avvertenza che il componibile rimane nella composizione e continua a occupare lo spazio in cui è disposto. Ciò potrebbe fare in modo che gli screen reader e altri meccanismi di accessibilità continuino a considerare l'elemento sullo schermo. D'altra parte, AnimatedVisibility alla fine rimuove l'elemento dalla composizione.
Animare il colore di sfondo
val animatedColor by animateColorAsState( if (animateBackgroundColor) colorGreen else colorBlue, label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(animatedColor) } ) { // your composable here }
Questa opzione è più efficiente dell'utilizzo di Modifier.background().
Modifier.background() è accettabile per un'impostazione del colore una tantum, ma quando si anima un colore nel tempo, potrebbe causare più ricomposizioni del necessario.
Per animare all'infinito il colore di sfondo, consulta la sezione Ripetere un'animazione sezione.
Animare le dimensioni di un componibile
Compose ti consente di animare le dimensioni dei componibili in diversi modi. Utilizza
animateContentSize() per le animazioni tra le modifiche delle dimensioni dei componibili.
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 avvenire le modifiche delle dimensioni.
Animare la posizione del componibile
Per animare la posizione di un componibile, utilizza Modifier.offset{ } in combinazione 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 componibili durante l'animazione della posizione o delle dimensioni, utilizza Modifier.layout{ }. Questo modificatore propaga le modifiche delle dimensioni e della posizione al parent, che a sua volta influisce sugli altri figli.
Ad esempio, se stai spostando una Box all'interno di una Column e gli altri figli devono spostarsi quando si sposta la Box, includi le informazioni sull'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) ) }
Modifier.layout{ }Animare il padding di un componibile
Per animare il padding 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
Per animare l'elevazione di un componibile, utilizza animateDpAsState in combinazione con Modifier.graphicsLayer{ }. Per le modifiche dell'elevazione una tantum, utilizza Modifier.shadow(). Se stai animando l'ombra, l'utilizzo 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 Card componibile e imposta la proprietà elevation su
valori diversi per stato.
Animare la scala, la traslazione o la rotazione del testo
Quando animi la scala, la traslazione o la rotazione del testo, imposta il textMotion
parametro su TextStyle su TextMotion.Animated. In questo modo, le transizioni tra le animazioni di testo sono più fluide. Utilizza Modifier.graphicsLayer{ } per
traslare, ruotare o scalare 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) ) }
Animare il colore del testo
Per animare il colore del testo, utilizza l'espressione lambda color sul 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
Utilizza AnimatedContent per animare i diversi componibili. Se vuoi
solo una dissolvenza standard tra i componibili, 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 uscita. Per ulteriori informazioni, leggi la documentazione su
AnimatedContent o questo post del blog su
AnimatedContent.
Animare durante la navigazione verso destinazioni diverse
Per animare le transizioni tra i 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 nel 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 uscita che applicano effetti diversi ai contenuti in entrata e in uscita. Per saperne di più, consulta la documentazione.
Ripetere un'animazione
Utilizza rememberInfiniteTransition con un infiniteRepeatable
animationSpec per ripetere continuamente l'animazione. Modifica RepeatModes per specificare come deve andare avanti e indietro.
Utilizza repeatable per ripetere un numero di volte impostato.
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 }
Avviare un'animazione all'avvio di un componibile
LaunchedEffect viene eseguito quando un componibile entra nella composizione. Avvia un'animazione all'avvio di un componibile, che puoi utilizzare per determinare la modifica 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
Utilizza le API di coroutine Animatable per eseguire animazioni sequenziali o simultanee. La chiamata di animateTo su Animatable una dopo l'altra fa sì che ogni animazione attenda il completamento delle animazioni precedenti prima di procedere .
Questo perché è 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
Utilizza le API di coroutine (Animatable#animateTo() o animate) o
l'API Transition per ottenere animazioni simultanee. 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 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 il rendimento delle animazioni
Le animazioni in Compose possono causare problemi di prestazioni. Ciò è dovuto alla natura di un'animazione: spostare o modificare rapidamente i pixel sullo schermo, fotogramma per fotogramma, per creare l'illusione del movimento.
Considera le diverse fasi di Compose: composizione, layout e disegno. Se l'animazione modifica la fase di layout, è necessario che tutti i componibili interessati vengano ridisegnati e ridisegnati. Se l'animazione si verifica nella fase di disegno, per impostazione predefinita è più efficiente rispetto all'esecuzione dell'animazione nella fase di layout, in quanto il lavoro complessivo è inferiore.
Per assicurarti che la tua app esegua il minor numero possibile di operazioni durante l'animazione, scegli la versione lambda di un Modifier ove possibile. In questo modo, la ricomposizione viene ignorata e l'animazione viene eseguita al di fuori della fase di composizione. In caso contrario, utilizza
Modifier.graphicsLayer{ }, poiché questo modificatore viene sempre eseguito nella fase di disegno. Per ulteriori informazioni, consulta la sezione Posticipare le letture in
ella documentazione sul rendimento.
Modificare la durata dell'animazione
Per impostazione predefinita, Compose utilizza le animazioni spring per la maggior parte delle animazioni. Le animazioni basate su molle o sulla fisica sono più naturali. Sono anche interrompibili perché tengono conto della velocità attuale dell'oggetto, anziché di un tempo fisso.
Se vuoi eseguire l'override del valore predefinito, tutte le API di animazione illustrate in precedenza hanno la possibilità di impostare un animationSpec per personalizzare l'esecuzione di un'animazione, sia che tu voglia che venga eseguita per una determinata durata sia che sia 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 between): animazione basata sulla durata, anima tra due valori con una funzioneEasing.keyframes: specifica i valori in determinati punti chiave di un'animazione.repeatable: specifica basata sulla durata che viene eseguita un certo numero di volte, specificato daRepeatMode.infiniteRepeatable: specifica basata sulla durata che viene eseguita all'infinito.snap: passa immediatamente al valore finale senza alcuna animazione.
Per ulteriori informazioni su animationSpec, leggi la documentazione completa.
Risorse aggiuntive
Per altri esempi di animazioni divertenti in Compose, dai un'occhiata a quanto segue:
- 5 animazioni rapide in Compose
- Far muovere le meduse in Compose
- Personalizzare
AnimatedContentin Compose - Easing nelle funzioni Easing in Compose