自定义动画

许多动画 API 通常会接受用于自定义其行为的参数。

使用AnimationSpec参数自定义动画

大多数动画 API 允许开发者通过一个可选的 AnimationSpec 参数来自定义动画规范。

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

有不同种类的 AnimationSpec 用于创建不同类型的动画。

使用 spring 创建基于物理的动画

spring 在起始值和结束值之间创建基于物理的动画。它接受两个参数:dampingRatiostiffness

dampingRatio 定义弹簧的弹性程度。默认值为 Spring.DampingRatioNoBouncy

图 1. 设置不同的弹簧阻尼比。

stiffness 定义弹簧向结束值移动的速度。默认值为 Spring.StiffnessMedium

图 2. 设置不同的弹簧刚度

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

spring 比基于持续时间的 AnimationSpec 类型更平滑地处理中断,因为它保证了在动画过程中目标值发生变化时速度的连续性。spring 被许多动画 API 用作默认的 AnimationSpec,例如 animate*AsStateupdateTransition

例如,如果我们将 spring 配置应用于以下由用户触摸驱动的动画,当在动画进行过程中中断它时,您可以看到使用 tween 的响应不如使用 spring 平滑。

图 3. 为动画设置 tweenspring 规范,并中断它。

使用缓动曲线在起始值和结束值之间进行动画 tween

tween 使用缓动曲线在指定的 durationMillis 内在起始值和结束值之间进行动画。tween 是“between”(之间)的缩写 - 因为它在两个值之间变化。

您还可以指定 delayMillis 来推迟动画的开始。

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

有关更多信息,请参阅 缓动

使用 keyframes 在特定时间点动画到特定值

keyframes 根据动画持续时间内不同时间戳上指定的快照值进行动画。在任何给定时间,动画值将在两个关键帧值之间进行插值。对于每个关键帧,可以指定缓动来确定插值曲线。

可以选择在 0 毫秒和持续时间时指定值。如果未指定这些值,则它们分别默认为动画的起始值和结束值。

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

使用 repeatable 重复动画

repeatable 重复运行基于持续时间的动画(例如 tweenkeyframes),直到达到指定的迭代次数。您可以传递 repeatMode 参数来指定动画是否应从头开始重复 (RepeatMode.Restart) 或从末尾重复 (RepeatMode.Reverse)。

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

使用 infiniteRepeatable 无限重复动画

infiniteRepeatable 类似于 repeatable,但它会无限次重复。

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

在使用 ComposeTestRule 进行的测试中,不会运行使用 infiniteRepeatable 的动画。组件将使用每个动画值的初始值进行渲染。

使用 snap 立即捕捉到结束值

snap 是一种特殊的 AnimationSpec,它会立即将值切换到结束值。您可以指定 delayMillis 以延迟动画的开始。

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

设置自定义缓动函数

基于持续时间的 AnimationSpec 操作(例如 tweenkeyframes)使用 Easing 来调整动画的分数。这允许动画值加速和减速,而不是以恒定速率移动。分数是在 0(开始)和 1.0(结束)之间的值,表示动画中的当前点。

缓动实际上是一个函数,它接受 0 到 1.0 之间的分数值并返回一个浮点数。返回值可以超出边界以表示过冲或欠冲。可以像下面的代码一样创建自定义缓动。

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

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

Compose 提供了一些内置的 Easing 函数,可以满足大多数用例。有关根据您的场景使用哪个缓动的更多信息,请参阅 速度 - Material Design

  • FastOutSlowInEasing
  • LinearOutSlowInEasing
  • FastOutLinearEasing
  • LinearEasing
  • CubicBezierEasing
  • 查看更多

通过转换为和从 AnimationVector 中转换来动画自定义数据类型

大多数 Compose 动画 API 默认支持 FloatColorDp 和其他基本数据类型作为动画值,但有时您需要动画其他数据类型,包括自定义数据类型。在动画过程中,任何动画值都表示为 AnimationVector。该值通过相应的 TwoWayConverter 转换为 AnimationVector,反之亦然,以便核心动画系统能够统一处理它们。例如,Int 表示为一个包含单个浮点值的 AnimationVector1DIntTwoWayConverter 如下所示

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

Color 本质上是一组 4 个值:红色、绿色、蓝色和 alpha,因此 Color 转换为一个包含 4 个浮点值的 AnimationVector4D。通过这种方式,动画中使用的每种数据类型都会根据其维度转换为 AnimationVector1DAnimationVector2DAnimationVector3DAnimationVector4D。这允许对象的各个组件独立进行动画,每个组件都有自己的速度跟踪。可以使用诸如 Color.VectorConverterDp.VectorConverter 之类的转换器访问基本数据类型的内置转换器。

当您想为新的数据类型添加作为动画值的支持时,您可以创建自己的 TwoWayConverter 并将其提供给 API。例如,您可以使用 animateValueAsState 来动画您的自定义数据类型,如下所示

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

以下列表包含一些内置的 VectorConverter