Cómo personalizar animaciones

Muchas de las API de Animation suelen aceptar parámetros para personalizar su comportamiento.

Cómo personalizar animaciones con el parámetro AnimationSpec

La mayoría de las API de Animation permiten a los desarrolladores personalizar las especificaciones de animaciones mediante un parámetro AnimationSpec opcional.

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"
)

Hay diferentes tipos de AnimationSpec para crear diferentes tipos de animaciones.

Crea animaciones basadas en la física con spring

spring crea una animación basada en la física entre valores iniciales y finales. Toma 2 parámetros: dampingRatio y stiffness.

dampingRatio define el nivel de efectividad que debería tener el resorte. El valor predeterminado es Spring.DampingRatioNoBouncy.

Figura 1: Configura diferentes relaciones de amortiguamiento del resorte.

stiffness define la velocidad con la que debe moverse el resorte hacia el valor final. El valor predeterminado es Spring.StiffnessMedium.

Figura 2: Cómo establecer diferentes rigidez del resorte

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

spring puede controlar las interrupciones de manera más fluida que los tipos AnimationSpec basados en la duración, ya que garantiza la continuidad de la velocidad cuando cambia el valor objetivo entre las animaciones. spring se usa como el valor predeterminado de AnimationSpec para muchas API de Animation, como animate*AsState y updateTransition.

Por ejemplo, si aplicamos una configuración spring a la siguiente animación que se controla con el toque del usuario, cuando interrumpimos la animación a medida que avanza, puedes ver que usar tween no responde tan bien como usar spring.

Figura 3: Configurar las especificaciones de tween en comparación con las de spring para la animación y, luego, interrumpirla

Anima entre los valores inicial y final con una curva de aceleración con tween

tween anima entre los valores inicial y final sobre el durationMillis especificado mediante una curva de aceleración. tween es la forma abreviada de la palabra between, ya que se encuentra entre dos valores.

También puedes especificar delayMillis para posponer el inicio de la animación.

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

Consulta Aceleración para obtener más información.

Anima a valores específicos en ciertos momentos con keyframes

keyframes anima en función de los valores de instantánea especificados en diferentes marcas de tiempo en la duración de la animación. El valor de la animación se interpolará entre dos valores de fotogramas clave. Para cada uno de esos fotogramas clave, se puede especificar la aceleración a fin de determinar la curva de interpolación.

Es opcional especificar los valores en 0 ms y en el tiempo de duración. Si no especificas esos valores, se establecerán de manera predeterminada en los valores de inicio y finalización de la animación, respectivamente.

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"
)

Cómo repetir una animación con repeatable

repeatable ejecuta una animación basada en la duración (como tween o keyframes) varias veces hasta que alcanza el recuento de iteración especificado. Puedes pasar el parámetro repeatMode para especificar si la animación se debe repetir comenzando desde el principio (RepeatMode.Restart) o desde el final (RepeatMode.Reverse).

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

Cómo repetir una animación de forma infinita con infiniteRepeatable

infiniteRepeatable es como repeatable, pero se repite durante una cantidad infinita de iteraciones.

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

En las pruebas que usan ComposeTestRule, no se ejecutan las animaciones que usan infiniteRepeatable. El componente se renderizará con el valor inicial de cada valor animado.

Cómo ajustar de inmediato al valor final con snap

snap es un AnimationSpec especial que cambia inmediatamente el valor al valor final. Puedes especificar delayMillis para retrasar el inicio de la animación.

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

Configurar una función de aceleración personalizada

Las operaciones de AnimationSpec basadas en la duración (como tween o keyframes) usan Easing para ajustar la fracción de una animación. Eso permite que el valor de la animación se acelere y se ralentice, en lugar de moverse a una velocidad constante. La fracción es un valor entre 0 (inicio) y 1.0 (final) que indica el punto actual en la animación.

La aceleración es una función que toma un valor de fracción entre 0 y 1.0, y muestra un número de punto flotante. El valor que se muestra puede estar fuera de los límites para representar una suboscilación o una sobreoscilación. Se puede crear una aceleración personalizada como el siguiente código.

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 proporciona varias funciones Easing integradas que abarcan la mayoría de los casos de uso. Consulta Velocidad: Material Design para obtener más información sobre qué tipo de aceleración debes usar según tu caso.

  • FastOutSlowInEasing
  • LinearOutSlowInEasing
  • FastOutLinearEasing
  • LinearEasing
  • CubicBezierEasing
  • Ver más

Anima los tipos de datos personalizados convirtiendo de AnimationVector a AnimationVector y viceversa

La mayoría de las APIs de animación de Compose admiten Float, Color, Dp y otros tipos de datos básicos como valores de animación de forma predeterminada, pero a veces necesitas animar otros tipos de datos, como los que personalizas. Durante la animación, cualquier valor de animación se representa como un AnimationVector. El valor se convierte en un AnimationVector y viceversa por un TwoWayConverter correspondiente para que el sistema de animación principal pueda controlarlos de manera uniforme. Por ejemplo, un Int se representa como un AnimationVector1D que tiene un solo valor de número de punto flotante. TwoWayConverter de Int tiene este aspecto:

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

Color es, básicamente, un conjunto de 4 valores (rojo, verde, azul y alfa), por lo que Color se convierte en un AnimationVector4D que tiene 4 valores de número de punto flotante. De esta manera, cada tipo de datos que se usa en las animaciones se convierte en AnimationVector1D, AnimationVector2D, AnimationVector3D o AnimationVector4D, según su dimensionalidad. Esto permite que diferentes componentes del objeto se animen de forma independiente, cada uno con su propio seguimiento de velocidad. Se puede acceder a los convertidores integrados para tipos de datos básicos mediante convertidores como Color.VectorConverter o Dp.VectorConverter.

Si deseas agregar compatibilidad con un nuevo tipo de datos como un valor de animación, puedes crear tu propio TwoWayConverter y proporcionarlo a la API. Por ejemplo, puedes usar animateValueAsState para animar tu tipo de datos personalizados de la siguiente manera:

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"
    )
}

La siguiente lista incluye algunos VectorConverter integrados: