Animare un singolo valore con animate*AsState
Le funzioni animate*AsState
sono le API di animazione più semplici di Compose per
animare un singolo valore. Fornisci solo il valore target (o valore finale) e
l'API avvia l'animazione dal valore corrente al valore specificato.
Di seguito è riportato un esempio di animazione dell'alpha utilizzando questa API. Se racchiudi semplicemente il valore di destinazione in animateFloatAsState
, il valore alfa diventa un valore di animazione tra i valori forniti (1f
o 0.5f
in questo caso).
var enabled by remember { mutableStateOf(true) } val animatedAlpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer { alpha = animatedAlpha } .background(Color.Red) )
Tieni presente che non è necessario creare un'istanza di alcuna classe di animazione o gestire
interruzioni. Sotto il cofano, verrà creato e memorizzato un oggetto animazione (ovvero un'istanza Animatable
) nel sito di chiamata, con il primo valore di destinazione come valore iniziale. Da quel momento in poi, ogni volta che fornisci a questo elemento componibile un valore di destinazione diverso, viene avviata automaticamente un'animazione verso quel valore. Se è già in corso un'animazione, questa inizia dal suo
valore (e velocità) attuale e si anima verso il valore target. Durante l'animazione, questo componibile viene ricomposto e restituisce un valore di animazione aggiornato ogni fotogramma.
Compose offre animate*AsState
funzioni predefinite per Float
,
Color
, Dp
, Size
, Offset
, Rect
, Int
, IntOffset
e
IntSize
. Puoi aggiungere facilmente il supporto per altri tipi di dati fornendo un
TwoWayConverter
a animateValueAsState
che accetta un tipo generico.
Puoi personalizzare le specifiche dell'animazione fornendo un AnimationSpec
.
Per saperne di più, consulta AnimationSpec.
Animare più proprietà contemporaneamente con una transizione
Transition
gestisce una o più animazioni come elementi secondari e le esegue
contemporaneamente tra più stati.
Gli stati possono essere di qualsiasi tipo di dati. In molti casi, puoi utilizzare un tipo enum
personalizzato per garantire la sicurezza dei tipi, come in questo esempio:
enum class BoxState { Collapsed, Expanded }
updateTransition
crea e memorizza un'istanza di Transition
e ne aggiorna
lo stato.
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "box state")
Puoi quindi utilizzare una delle funzioni di estensione animate*
per definire un'animazione
secondaria in questa transizione. Specifica i valori target per ciascuno degli stati.
Queste funzioni animate*
restituiscono un valore di animazione aggiornato a ogni fotogramma
durante l'animazione quando lo stato di transizione viene aggiornato con
updateTransition
.
val rect by transition.animateRect(label = "rectangle") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "border width") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
Se vuoi, puoi passare un parametro transitionSpec
per specificare un AnimationSpec
diverso per ciascuna delle combinazioni di modifiche dello stato di transizione. Per saperne di più, consulta
AnimationSpec.
val color by transition.animateColor( transitionSpec = { when { BoxState.Expanded isTransitioningTo BoxState.Collapsed -> spring(stiffness = 50f) else -> tween(durationMillis = 500) } }, label = "color" ) { state -> when (state) { BoxState.Collapsed -> MaterialTheme.colorScheme.primary BoxState.Expanded -> MaterialTheme.colorScheme.background } }
Una volta che una transizione ha raggiunto lo stato di destinazione, Transition.currentState
sarà uguale a Transition.targetState
. Può essere utilizzato come indicatore
per capire se la transizione è terminata.
A volte vogliamo che lo stato iniziale sia diverso dal primo stato
target. Possiamo utilizzare updateTransition
con MutableTransitionState
per ottenere
questo risultato. Ad esempio, ci consente di avviare l'animazione non appena il codice entra nella composizione.
// Start in collapsed state and immediately animate to expanded var currentState = remember { MutableTransitionState(BoxState.Collapsed) } currentState.targetState = BoxState.Expanded val transition = rememberTransition(currentState, label = "box state") // ……
Per una transizione più complessa che coinvolge più funzioni componibili, puoi
utilizzare createChildTransition
per creare una transizione secondaria. Questa tecnica è utile per separare le preoccupazioni
tra più sottocomponenti in un composable complesso. La transizione principale
conoscerà tutti i valori di animazione nelle transizioni secondarie.
enum class DialerState { DialerMinimized, NumberPad } @Composable fun DialerButton(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun NumberPad(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun Dialer(dialerState: DialerState) { val transition = updateTransition(dialerState, label = "dialer state") Box { // Creates separate child transitions of Boolean type for NumberPad // and DialerButton for any content animation between visible and // not visible NumberPad( transition.createChildTransition { it == DialerState.NumberPad } ) DialerButton( transition.createChildTransition { it == DialerState.DialerMinimized } ) } }
Utilizzare la transizione con AnimatedVisibility
e AnimatedContent
AnimatedVisibility
e AnimatedContent
sono disponibili come funzioni di estensione di Transition
. Il targetState
per
Transition.AnimatedVisibility
e Transition.AnimatedContent
è derivato
da Transition
e attiva le transizioni di entrata/uscita in base alle necessità quando il
targetState
di Transition
è cambiato. Queste funzioni di estensione consentono di spostare tutte le animazioni di ingresso/uscita/trasformazione delle dimensioni che altrimenti sarebbero interne a AnimatedVisibility
/AnimatedContent
in Transition
.
Con queste funzioni di estensione, lo stato di AnimatedVisibility
/AnimatedContent
può essere osservato dall'esterno. Anziché un parametro booleano visible
,
questa versione di AnimatedVisibility
accetta una lambda che converte lo stato di destinazione
della transizione principale in un valore booleano.
Per i dettagli, consulta AnimatedVisibility e AnimatedContent.
var selected by remember { mutableStateOf(false) } // Animates changes when `selected` is changed. val transition = updateTransition(selected, label = "selected state") val borderColor by transition.animateColor(label = "border color") { isSelected -> if (isSelected) Color.Magenta else Color.White } val elevation by transition.animateDp(label = "elevation") { isSelected -> if (isSelected) 10.dp else 2.dp } Surface( onClick = { selected = !selected }, shape = RoundedCornerShape(8.dp), border = BorderStroke(2.dp, borderColor), shadowElevation = elevation ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text(text = "Hello, world!") // AnimatedVisibility as a part of the transition. transition.AnimatedVisibility( visible = { targetSelected -> targetSelected }, enter = expandVertically(), exit = shrinkVertically() ) { Text(text = "It is fine today.") } // AnimatedContent as a part of the transition. transition.AnimatedContent { targetState -> if (targetState) { Text(text = "Selected") } else { Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone") } } } }
Incapsulare una transizione e renderla riutilizzabile
Per i casi d'uso semplici, definire le animazioni di transizione nello stesso elemento componibile dell'interfaccia utente è un'opzione perfettamente valida. Quando lavori su un componente complesso con un numero di valori animati, tuttavia, potresti voler separare l'implementazione dell'animazione dall'UI componibile.
Puoi farlo creando una classe che contenga tutti i valori di animazione e una funzione "update" che restituisca un'istanza di quella classe. L'implementazione della transizione può essere estratta nella nuova funzione separata. Questo pattern è utile quando è necessario centralizzare la logica di animazione o rendere riutilizzabili animazioni complesse.
enum class BoxState { Collapsed, Expanded } @Composable fun AnimatingBox(boxState: BoxState) { val transitionData = updateTransitionData(boxState) // UI tree Box( modifier = Modifier .background(transitionData.color) .size(transitionData.size) ) } // Holds the animation values. private class TransitionData( color: State<Color>, size: State<Dp> ) { val color by color val size by size } // Create a Transition and return its animation values. @Composable private fun updateTransitionData(boxState: BoxState): TransitionData { val transition = updateTransition(boxState, label = "box state") val color = transition.animateColor(label = "color") { state -> when (state) { BoxState.Collapsed -> Color.Gray BoxState.Expanded -> Color.Red } } val size = transition.animateDp(label = "size") { state -> when (state) { BoxState.Collapsed -> 64.dp BoxState.Expanded -> 128.dp } } return remember(transition) { TransitionData(color, size) } }
Crea un'animazione che si ripete all'infinito con rememberInfiniteTransition
InfiniteTransition
contiene una o più animazioni secondarie come Transition
, ma
le animazioni iniziano a essere eseguite non appena entrano nella composizione e non
si fermano a meno che non vengano rimosse. Puoi creare un'istanza di InfiniteTransition
con rememberInfiniteTransition
. Le animazioni secondarie possono essere aggiunte con
animateColor
, animatedFloat
o animatedValue
. Devi anche specificare un
infiniteRepeatable per specificare le specifiche
dell'animazione.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Red, targetValue = Color.Green, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Box( Modifier .fillMaxSize() .background(color) )
API di animazione di basso livello
Tutte le API di animazione di alto livello menzionate nella sezione precedente si basano sulle API di animazione di basso livello.
Le funzioni animate*AsState
sono le API più semplici, che eseguono il rendering di una variazione
istantanea del valore come valore di animazione. È supportato da Animatable
, che è un'API basata su coroutine per animare un singolo valore. updateTransition
crea un
oggetto di transizione che può gestire più valori di animazione ed eseguirli in base
a una modifica dello stato. rememberInfiniteTransition
è simile, ma crea una transizione infinita che può gestire più animazioni che continuano a essere eseguite all'infinito. Tutte queste API sono componibili, tranne Animatable
, il che
significa che queste animazioni possono essere create al di fuori della composizione.
Tutte queste API si basano sull'API Animation
più fondamentale. Sebbene la maggior parte
delle app non interagisca direttamente con Animation
, alcune delle funzionalità
di personalizzazione per Animation
sono disponibili tramite API di livello superiore. Per ulteriori informazioni su
AnimationVector
e AnimationSpec
, consulta
Personalizzare le animazioni.
Animatable
: Animazione a valore singolo basata su coroutine
Animatable
è un segnaposto che può animare il valore quando viene modificato tramite
animateTo
. Questa è l'API che supporta l'implementazione di animate*AsState
.
Garantisce la continuità e l'esclusività reciproca, il che significa che la
modifica del valore è sempre continua e qualsiasi animazione in corso verrà annullata.
Molte funzionalità di Animatable
, tra cui animateTo
, vengono fornite come funzioni di sospensione. Ciò significa che devono essere racchiuse in un ambito di coroutine appropriato. Ad esempio, puoi utilizzare il composable LaunchedEffect
per creare
un ambito solo per la durata del valore chiave specificato.
// Start out gray and animate to green/red based on `ok` val color = remember { Animatable(Color.Gray) } LaunchedEffect(ok) { color.animateTo(if (ok) Color.Green else Color.Red) } Box( Modifier .fillMaxSize() .background(color.value) )
Nell'esempio precedente, creiamo e memorizziamo un'istanza di Animatable
con
il valore iniziale di Color.Gray
. A seconda del valore del flag booleano
ok
, il colore viene animato in Color.Green
o Color.Red
. Qualsiasi modifica successiva
al valore booleano avvia l'animazione verso l'altro colore. Se è in corso
un'animazione quando il valore viene modificato, l'animazione viene annullata e la
nuova animazione inizia dal valore dello snapshot corrente con la velocità attuale.
Questa è l'implementazione dell'animazione che supporta l'API animate*AsState
menzionata nella sezione precedente. Rispetto a animate*AsState
, l'utilizzo
diretto di Animatable
ci offre un controllo più granulare su diversi aspetti. Innanzitutto,
Animatable
può avere un valore iniziale diverso dal primo valore target.
Ad esempio, l'esempio di codice riportato sopra mostra inizialmente una casella grigia, che inizia immediatamente
l'animazione in verde o rosso. In secondo luogo, Animatable
fornisce più operazioni sul valore dei contenuti, ovvero snapTo
e animateDecay
. snapTo
imposta immediatamente il valore corrente sul valore target. Questa funzionalità è utile quando l'animazione stessa non è l'unica origine di riferimento e deve essere sincronizzata con altri stati, come gli eventi tocco. animateDecay
avvia un'animazione che rallenta
dalla velocità specificata. Ciò è utile per implementare il comportamento di scorrimento rapido. Per saperne di più, consulta la sezione
Gesti e animazioni.
Animatable
supporta Float
e Color
, ma è possibile utilizzare qualsiasi tipo di dati fornendo un TwoWayConverter
. Per ulteriori informazioni, consulta
AnimationVector.
Puoi personalizzare le specifiche dell'animazione fornendo un AnimationSpec
.
Per saperne di più, consulta AnimationSpec.
Animation
: animazione controllata manualmente
Animation
è l'API Animation di livello più basso disponibile. Molte delle animazioni
che abbiamo visto finora si basano su Animation. Esistono due sottotipi di Animation
:
TargetBasedAnimation
e DecayAnimation
.
Animation
deve essere utilizzato solo per controllare manualmente la durata dell'animazione.
Animation
è senza stato e non ha alcun concetto di ciclo di vita. Funge da motore di calcolo dell'animazione utilizzato dalle API di livello superiore.
TargetBasedAnimation
Altre API coprono la maggior parte dei casi d'uso, ma l'utilizzo diretto di TargetBasedAnimation
ti consente di controllare personalmente la durata della riproduzione dell'animazione. Nell'esempio riportato di seguito,
la durata di riproduzione di TargetAnimation
viene controllata manualmente in base al tempo
del frame fornito da withFrameNanos
.
val anim = remember { TargetBasedAnimation( animationSpec = tween(200), typeConverter = Float.VectorConverter, initialValue = 200f, targetValue = 1000f ) } var playTime by remember { mutableLongStateOf(0L) } LaunchedEffect(anim) { val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime val animationValue = anim.getValueFromNanos(playTime) } while (someCustomCondition()) }
DecayAnimation
A differenza di TargetBasedAnimation
,
DecayAnimation
non richiede la fornitura di un targetValue
. Calcola invece il proprio
targetValue
in base alle condizioni iniziali, impostate da initialVelocity
e
initialValue
, e al DecayAnimationSpec
fornito.
Le animazioni di decadimento vengono spesso utilizzate dopo un gesto di scorrimento rapido per rallentare gli elementi fino a
farli fermare. La velocità dell'animazione inizia con il valore impostato da initialVelocityVector
e rallenta nel tempo.
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Personalizzare le animazioni {:#customize-animations}
- Animazioni in Compose
- Modificatori e componenti combinabili di animazione