Personalizar animações

Geralmente, muitas das APIs de animação aceitam parâmetros para personalização de comportamentos.

Personalizar animações com o parâmetro AnimationSpec

A maioria das APIs de animação permite que os desenvolvedores personalizem as especificações de animação usando um 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)
)

Existem diferentes tipos de AnimationSpec para criação de diferentes tipos de animação.

Criar animação baseada em física com spring

A spring cria uma animação baseada em física entre os valores inicial e final. Ela precisa de dois parâmetros: dampingRatio e stiffness.

dampingRatio define o grau de mobilidade da spring. O valor padrão é Spring.DampingRatioNoBouncy.

Figura 1. Definir diferentes proporções de amortecimento da mola.

stiffness define a velocidade de movimento da spring em direção ao valor final. O valor padrão é Spring.StiffnessMedium.

Figura 2. Como configurar diferentes rigidez da mola

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

A spring pode processar interrupções de forma melhor que tipos de AnimationSpec baseados em duração, porque garante a continuidade da velocidade quando o valor de destino muda entre animações. spring é usada como o AnimationSpec padrão por muitas APIs de animação, como animate*AsState e updateTransition.

Por exemplo, se aplicarmos uma configuração spring à animação abaixo, que é acionada pelo toque do usuário, ao interromper a animação durante o progresso, você vai notar que o uso de tween não responde tão bem quanto usar spring.

Figura 3. Definir especificações tween vs. spring para animação e interrompê-la.

Animar entre os valores inicial e final com a curva de easing com tween

tween executa animações entre os valores inicial e final na durationMillis especificada, usando uma curva de easing. tween é a abreviação da palavra "entre" - entre dois valores.

Também é possível especificar delayMillis para adiar o início da animação.

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

Consulte Easing para ver mais informações.

Animar para valores específicos em determinados momentos com keyframes

keyframes executam animações com base nos valores resumidos especificados em carimbos de data/hora diferentes na duração da animação. O valor de animação será interpolado entre dois valores keyframe a qualquer momento. O easing pode ser especificado para qualquer um dos keyframes para determinar a curva de interpolação.

Especificar os valores como 0 ms e o tempo de duração é opcional. Caso esses valores não sejam especificados, o padrão será os valores inicial e final da animação, 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
    }
)

Repetir uma animação com repeatable

repeatable executa uma animação baseada em duração (como tween ou keyframes) repetidamente, até alcançar a contagem de iteração especificada. É possível transmitir o parâmetro repeatMode para especificar se a animação vai ser repetida começando do início (RepeatMode.Restart) ou do final (RepeatMode.Reverse).

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

Repetir uma animação infinitamente com infiniteRepeatable

infiniteRepeatable é semelhante a repeatable, mas essa função se repete por uma quantidade infinita de iterações.

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

Animações que usam infiniteRepeatable não são executadas em testes que usam ComposeTestRule. O componente será renderizado usando o valor inicial de cada valor de animação.

Ajustar imediatamente ao valor final com snap

snap é uma AnimationSpec especial que muda imediatamente o valor para o valor final. Você pode especificar delayMillis para atrasar o início da animação.

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

Definir uma função de easing personalizada

As operações AnimationSpec baseadas em duração (como tween ou keyframes) usam Easing para ajustar a fração da animação. Isso permite que o valor de animação acelere e desacelere, em vez de se mover a uma taxa constante. Uma fração é um valor entre 0 (início) e 1,0 (fim) indicando o ponto atual da animação.

O easing é, na verdade, uma função que usa um valor de fração entre 0 e 1,0 e retorna um ponto flutuante. O valor retornado pode estar fora do limite para representar uma ultrapassagem ou uma redução. Um easing personalizado pode ser criado, como no código abaixo.

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

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

O Compose oferece várias funções Easing integradas que abrangem a maioria dos casos de uso. Consulte Speed – Material Design (link em inglês) para mais informações sobre qual easing usar, dependendo do cenário.

  • FastOutSlowInEasing
  • LinearOutSlowInEasing
  • FastOutLinearEasing
  • LinearEasing
  • CubicBezierEasing
  • Veja mais

Animar tipos de dados personalizados convertendo de e para AnimationVector

A maioria das APIs de animação do Compose oferece suporte a Float, Color, Dp e outros tipos de dados básicos como valores de animação por padrão, mas às vezes é necessário animar outros tipos de dados, incluindo os personalizados. Durante a animação, qualquer valor de animação é representado como um AnimationVector. O valor é convertido em um AnimationVector, e vice-versa, por um TwoWayConverter correspondente para que o sistema de animação principal possa processá-lo de maneira uniforme. Por exemplo, uma Int é representada como um AnimationVector1D contendo um único valor flutuante. Para TwoWayConverter, o Int é assim:

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

A Color é essencialmente um conjunto de quatro valores: vermelho, verde, azul e alfa. A Color é convertida em um AnimationVector4D contendo quatro valores flutuantes. Dessa forma, cada tipo de dados usado nas animações é convertido em AnimationVector1D, AnimationVector2D, AnimationVector3D ou AnimationVector4D, dependendo da dimensionalidade. Isso permite que diferentes componentes do objeto sejam animados de forma independente, com rastreamentos de velocidade próprios. Os conversores integrados para tipos de dados básicos podem ser acessados usando conversores como Color.VectorConverter ou Dp.VectorConverter.

Caso queira adicionar compatibilidade com um novo tipo de dados como um valor de animação, crie seu próprio TwoWayConverter e forneça-o à API. Por exemplo, é possível usar animateValueAsState para animar o tipo de dados personalizado da seguinte forma:

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

A lista a seguir inclui alguns VectorConverters integrados: