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

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

Crea animación basada 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: Configuración de diferentes relaciones de amortiguamiento de resorte.

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

Figura 2: Establecer una rigidez de resorte diferente

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

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 de spring a la siguiente animación impulsada por el tacto del usuario, cuando se interrumpe la animación mientras avanza, puedes ver que usar tween no responde tan bien como usar spring.

Figura 3: Se configuraron las especificaciones de tween frente a spring para la animación, y se pudo interrumpir la función.

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 abreviatura de la palabra intermedia, 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
    )
)

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

Anima valores específicos en determinados 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 with LinearOutSlowInEasing // for 0-15 ms
        0.2f at 15 with FastOutLinearInEasing // for 15-75 ms
        0.4f at 75 // ms
        0.4f at 225 // ms
    }
)

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

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

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.

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

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
        )
    )
    // ……
}

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 tipos de datos personalizados con la conversión hacia y desde AnimationVector

La mayoría de las APIs de Animation 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, incluidos los personalizados. 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 conversores 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)
            }
        )
    )
}

En la siguiente lista, se incluyen algunos VectorConverter integrados: