Animare un singolo valore con animate*AsState
Le funzioni animate*AsState
sono le API di animazione più semplici in Compose per animare un singolo valore. Fornisci solo il valore target (o finale) e
l'API avvia l'animazione dal valore corrente a quello specificato.
Di seguito è riportato un esempio di animazione dell'alpha utilizzando questa API. Raggruppando semplicemente il
valore target in animateFloatAsState
, il valore alpha corrisponde ora a un valore di animazione
tra i valori forniti (1f
o 0.5f
in questo caso).
var enabled by remember { mutableStateOf(true) } val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer(alpha = alpha) .background(Color.Red) )
Tieni presente che non è necessario creare un'istanza di una classe di animazione o gestire l'interruzione. Di base, nel sito di chiamata verrà creato e memorizzato un oggetto di animazione (ovvero un'istanza Animatable
), con il primo valore target come valore iniziale. Da questo momento in poi, ogni volta che fornisci un valore target diverso per questo componibile, viene avviata automaticamente un'animazione per quel valore. Se è già in corso un'animazione, questa parte dal valore (e dalla velocità) corrente e si anima verso il valore target. Durante l'animazione, questo composable viene ricomposto e restituisce un valore di animazione aggiornato in ogni frame.
Per impostazione predefinita, Compose fornisce funzioni animate*AsState
per Float
,
Color
, Dp
, Size
, Offset
, Rect
, Int
, IntOffset
e
IntSize
. Puoi aggiungere facilmente il supporto di altri tipi di dati fornendo un valore da TwoWayConverter
a animateValueAsState
che accetta un tipo generico.
Puoi personalizzare le specifiche dell'animazione fornendo un AnimationSpec
.
Per ulteriori informazioni, 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 del tipo, come in questo esempio:
enum class BoxState { Collapsed, Expanded }
updateTransition
crea e memorizza un'istanza di Transition
e aggiorna il relativo 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 che viene aggiornato 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 valore AnimationSpec
diverso per ciascuna delle combinazioni di modifiche dello stato di transizione. Per ulteriori informazioni, 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 è arrivata allo stato target, Transition.currentState
è uguale a Transition.targetState
. Questo può essere utilizzato come indicatore per verificare se la transizione è stata completata.
A volte vogliamo avere uno stato iniziale diverso dal primo stato
target. Per raggiungere questo obiettivo,
puoi utilizzare updateTransition
con MutableTransitionState
. 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 i problemi tra più componenti secondari in un composable complesso. La transizione principale terrà conto di tutti i valori dell'animazione nelle transizioni figlio.
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
. targetState
per
Transition.AnimatedVisibility
e Transition.AnimatedContent
deriva da Transition
e attivano le transizioni di tipo entra/uscita secondo le necessità quando il valore targetState
di
Transition
viene modificato. Queste funzioni di estensione consentono di sollevare in Transition
tutte le animazioni enter/exit/sizeTransform che altrimenti sarebbero interne a AnimatedVisibility
/AnimatedContent
.
Con queste funzioni di estensione, la variazione di stato di AnimatedVisibility
/AnimatedContent
può essere osservata dall'esterno. Anziché un parametro booleano visible
,
questa versione di AnimatedVisibility
accetta una funzione lambda che converte lo stato di destinazione della transizione principale in un booleano.
Per maggiori 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 casi d'uso semplici, la definizione di animazioni di transizione nella stessa interfaccia utente è un'opzione perfettamente valida. Tuttavia, quando lavori su un componente complesso con una serie di valori animati, ti consigliamo di separare l'implementazione dell'animazione dall'interfaccia utente componibile.
Puoi farlo creando una classe che contenga tutti i valori di animazione e una funzione "update" che restituisce 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 le 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) } }
Creare un'animazione ripetuta 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
interrompono 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 valore 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 visualizzano una variazione immediata del valore come valore di animazione. È supportato da Animatable
, un'API basata su coroutine per l'animazione di 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 rimangono in esecuzione
per sempre. Tutte queste API sono componibili, ad eccezione di 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 con valore singolo basata su coroutine
Animatable
è un contenitore di valori che può animare il valore man mano che viene modificato tramite
animateTo
. Questa è l'API di supporto dell'implementazione di animate*AsState
.
Garantisce continuità e esclusione reciproca, il che significa che la variazione del valore è sempre continua e qualsiasi animazione in corso verrà annullata.
Molte funzionalità di Animatable
, tra cui animateTo
, sono fornite come funzioni di sospensione. Ciò significa che devono essere racchiuse in un ambito appropriato della coroutine. Ad esempio, puoi utilizzare l'elemento componibile LaunchedEffect
per creare un ambito solo per la durata della coppia chiave-valore specificata.
// 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 riportato sopra, creiamo e ricordiamo un'istanza di Animatable
con il valore iniziale 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 al momento della modifica del valore è in corso un'animazione, questa viene annullata e la nuova animazione inizia dal valore dell'istantanea corrente con la velocità corrente.
Questa è l'implementazione dell'animazione che esegue il backup dell'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 a animarsi in verde o rosso. In secondo luogo, Animatable
offre più operazioni sul valore dei contenuti, ovvero snapTo
e animateDecay
. snapTo
imposta immediatamente il valore attuale sul valore target. Ciò è utile quando l'animazione stessa non è l'unica fonte attendibile e deve essere sincronizzata con altri stati, come gli eventi touch. animateDecay
avvia un'animazione che rallenta
dalla velocità specificata. Questo è utile per implementare il comportamento di scorrimento. Per ulteriori informazioni, consulta la sezione Gesti e animazioni.
Animatable
supporta Float
e Color
, ma qualsiasi tipo di dato può essere utilizzato fornendo un TwoWayConverter
. Per ulteriori informazioni, consulta
AnimationVector.
Puoi personalizzare le specifiche dell'animazione fornendo un AnimationSpec
.
Per ulteriori informazioni, consulta la sezione 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 tipi di Animation
:
TargetBasedAnimation
e DecayAnimation
.
Animation
deve essere utilizzato solo per controllare manualmente il tempo dell'animazione.
Animation
è stateless e non ha alcun concetto di ciclo di vita. Funge da motore di calcolo delle animazioni utilizzato dalle API di livello superiore.
TargetBasedAnimation
Altre API sono adatte alla maggior parte dei casi d'uso, ma l'utilizzo diretto di TargetBasedAnimation
ti consente
di controllare personalmente la riproduzione dell'animazione. Nell'esempio seguente,
la durata di riproduzione di TargetAnimation
è 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 l'indicazione di un targetValue
. ma calcola il suo
targetValue
in base alle condizioni iniziali, impostate da initialVelocity
e
initialValue
e dal valore DecayAnimationSpec
fornito.
Le animazioni di decadimento vengono spesso utilizzate dopo un gesto di lancio per rallentare gli elementi fino a fermarli. 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
- Composabili e modificatori di animazione