Personalizza le animazioni

Molte API di animazione accettano comunemente parametri per personalizzare il loro comportamento.

Personalizzare le animazioni con il parametro AnimationSpec

La maggior parte delle API di animazione consente agli sviluppatori di personalizzare le specifiche dell'animazione tramite un parametro AnimationSpec facoltativo.

val alpha: Float by animateFloatAsState(
    targetValue = if (enabled) 1f else 0.5f,
    // Configure the animation duration and easing.
    animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing),
    label = "alpha"
)

Esistono diversi tipi di AnimationSpec per creare diversi tipi di animazione.

Creare animazioni basate sulla fisica con spring

spring crea un'animazione basata sulla fisica tra i valori iniziale e finale. Richiede due parametri: dampingRatio e stiffness.

dampingRatio definisce l'elasticità della molla. Il valore predefinito è Spring.DampingRatioNoBouncy.

Figura 1. Impostazione di diversi rapporti di smorzamento delle molle.

stiffness definisce la velocità con cui la molla deve spostarsi verso il valore finale. Il valore predefinito è Spring.StiffnessMedium.

Figura 2. Impostazione di diverse rigidità delle molle.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioHighBouncy,
        stiffness = Spring.StiffnessMedium
    ),
    label = "spring spec"
)

spring può gestire le interruzioni in modo più fluido rispetto ai tipi AnimationSpec basati sulla durata perché garantisce la continuità della velocità quando il valore target cambia durante le animazioni. spring viene utilizzato come AnimationSpec predefinita da molte API di animazione, come animate*AsState e updateTransition.

Ad esempio, se applichiamo una configurazione spring alla seguente animazione basata sul tocco dell'utente, quando interrompiamo l'animazione in corso, puoi notare che l'utilizzo di tween non risponde in modo fluido come l'utilizzo di spring.

Figura 3. Impostazione delle specifiche tween e spring per l'animazione e interruzione.

Anima tra i valori iniziale e finale con la curva di accelerazione con tween

tween anima i valori iniziale e finale nel periodo di tempo durationMillis specificato utilizzando una curva di accelerazione. tween è l'abbreviazione della parola "tra", in quanto si trova tra due valori.

Puoi anche specificare delayMillis per posticipare l'inizio dell'animazione.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = tween(
        durationMillis = 300,
        delayMillis = 50,
        easing = LinearOutSlowInEasing
    ),
    label = "tween delay"
)

Per saperne di più, consulta Accelerazione.

Animare valori specifici in determinati momenti con keyframes

keyframes viene animato in base ai valori dello snapshot specificati in diversi timestamp durante la durata dell'animazione. In un determinato momento, il valore dell'animazione verrà interpolato tra due valori dei fotogrammi chiave. Per ciascuno di questi fotogrammi chiave, è possibile specificare l'accelerazione per determinare la curva di interpolazione.

È facoltativo specificare i valori a 0 ms e al momento della durata. Se non specifichi questi valori, vengono impostati come predefiniti i valori iniziale e finale dell'animazione, rispettivamente.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = keyframes {
        durationMillis = 375
        0.0f at 0 using LinearOutSlowInEasing // for 0-15 ms
        0.2f at 15 using FastOutLinearInEasing // for 15-75 ms
        0.4f at 75 // ms
        0.4f at 225 // ms
    },
    label = "keyframe"
)

Animare in modo fluido tra i fotogrammi chiave con keyframesWithSplines

Per creare un'animazione che segue una curva uniforme durante la transizione tra i valori, puoi utilizzare keyframesWithSplines anziché le specifiche di animazione keyframes.

val offset by animateOffsetAsState(
    targetValue = Offset(300f, 300f),
    animationSpec = keyframesWithSpline {
        durationMillis = 6000
        Offset(0f, 0f) at 0
        Offset(150f, 200f) atFraction 0.5f
        Offset(0f, 100f) atFraction 0.7f
    }
)

I fotogrammi chiave basati su spline sono particolarmente utili per il movimento 2D degli elementi sullo schermo.

I seguenti video mostrano le differenze tra keyframes e keyframesWithSpline dato lo stesso insieme di coordinate x e y che un cerchio deve seguire.

keyframes keyframesWithSplines

Come puoi vedere, i fotogrammi chiave basati su spline offrono transizioni più fluide tra i punti, in quanto utilizzano curve di Bézier per animare in modo fluido gli elementi. Questa specifica è utile per un'animazione preimpostata. Tuttavia, se lavori con punti guidati dall'utente, è preferibile utilizzare le molle per ottenere una fluidità simile tra i punti, perché sono interrompibili.

Ripetere un'animazione con repeatable

repeatable esegue un'animazione basata sulla durata (ad esempio tween o keyframes) ripetutamente fino a raggiungere il numero di iterazioni specificato. Puoi trasmettere il parametro repeatMode per specificare se l'animazione deve ripetersi iniziando dall'inizio (RepeatMode.Restart) o dalla fine (RepeatMode.Reverse).

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = repeatable(
        iterations = 3,
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ),
    label = "repeatable spec"
)

Ripeti un'animazione all'infinito con infiniteRepeatable

infiniteRepeatable è simile a repeatable, ma si ripete per un numero infinito di iterazioni.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = infiniteRepeatable(
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ),
    label = "infinite repeatable"
)

Nei test che utilizzano ComposeTestRule, le animazioni che utilizzano infiniteRepeatable non vengono eseguite. Il componente verrà visualizzato utilizzando il valore iniziale di ogni valore animato.

Passa immediatamente al valore finale con snap

snap è un AnimationSpec speciale che imposta immediatamente il valore sul valore finale. Puoi specificare delayMillis per ritardare l'inizio dell'animazione.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = snap(delayMillis = 50),
    label = "snap spec"
)

Impostare una funzione di accelerazione personalizzata

Le operazioni basate sulla durata AnimationSpec (come tween o keyframes) utilizzano Easing per regolare la frazione di un'animazione. Ciò consente al valore di animazione di accelerare e rallentare, anziché muoversi a una velocità costante. Frazione è un valore compreso tra 0 (inizio) e 1,0 (fine) che indica il punto corrente dell' animazione.

L'accelerazione è in realtà una funzione che accetta un valore frazionario compreso tra 0 e 1,0 e restituisce un float. Il valore restituito può essere al di fuori del limite per rappresentare un superamento o un mancato raggiungimento. È possibile creare un easing personalizzato come nel codice riportato di seguito.

val CustomEasing = Easing { fraction -> fraction * fraction }

@Composable
fun EasingUsage() {
    val value by animateFloatAsState(
        targetValue = 1f,
        animationSpec = tween(
            durationMillis = 300,
            easing = CustomEasing
        ),
        label = "custom easing"
    )
    // ……
}

Compose fornisce diverse funzioni Easing integrate che coprono la maggior parte dei casi d'uso. Per ulteriori informazioni su quale tipo di accelerazione utilizzare a seconda dello scenario, consulta Velocità - Material Design.

  • FastOutSlowInEasing
  • LinearOutSlowInEasing
  • FastOutLinearEasing
  • LinearEasing
  • CubicBezierEasing
  • Scopri di più

Animare i tipi di dati personalizzati convertendoli in e da AnimationVector

La maggior parte delle API di animazione Compose supporta Float, Color, Dp e altri tipi di dati di base come valori di animazione per impostazione predefinita, ma a volte è necessario animare altri tipi di dati, inclusi quelli personalizzati. Durante l'animazione, qualsiasi valore di animazione è rappresentato come AnimationVector. Il valore viene convertito in un AnimationVector e viceversa da un TwoWayConverter corrispondente, in modo che il sistema di animazione principale possa gestirli in modo uniforme. Ad esempio, un Int è rappresentato come un AnimationVector1D che contiene un singolo valore float. TwoWayConverter per Int ha questo aspetto:

val IntToVector: TwoWayConverter<Int, AnimationVector1D> =
    TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() })

Color è essenzialmente un insieme di 4 valori: rosso, verde, blu e alfa, quindi Color viene convertito in un AnimationVector4D che contiene 4 valori float. In questo modo, ogni tipo di dati utilizzato nelle animazioni viene convertito in AnimationVector1D, AnimationVector2D, AnimationVector3D o AnimationVector4D a seconda della sua dimensionalità. In questo modo, i diversi componenti dell'oggetto possono essere animati in modo indipendente, ognuno con il proprio tracciamento della velocità. È possibile accedere ai convertitori integrati per i tipi di dati di base utilizzando convertitori come Color.VectorConverter o Dp.VectorConverter.

Quando vuoi aggiungere il supporto per un nuovo tipo di dati come valore di animazione, puoi creare il tuo TwoWayConverter e fornirlo all'API. Ad esempio, puoi utilizzare animateValueAsState per animare il tuo tipo di dati personalizzato nel seguente modo:

data class MySize(val width: Dp, val height: Dp)

@Composable
fun MyAnimation(targetSize: MySize) {
    val animSize: MySize by animateValueAsState(
        targetSize,
        TwoWayConverter(
            convertToVector = { size: MySize ->
                // Extract a float value from each of the `Dp` fields.
                AnimationVector2D(size.width.value, size.height.value)
            },
            convertFromVector = { vector: AnimationVector2D ->
                MySize(vector.v1.dp, vector.v2.dp)
            }
        ),
        label = "size"
    )
}

Il seguente elenco include alcuni VectorConverter incorporati: