自定义动画

很多动画 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),
    label = "alpha"
)

开发者可以使用不同类型的 AnimationSpec 来创建不同类型的动画。

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

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

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

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

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

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

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

相比基于时长的 AnimationSpec 类型,spring 可以更流畅地处理中断,因为它可以在目标值在动画中变化时保证速度的连续性。spring 用作很多动画 API(如 animate*AsStateupdateTransition)的默认 AnimationSpec。

例如,如果我们将 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
    ),
    label = "tween delay"
)

如需了解详情,请参阅缓动

使用 keyframes 在特定时间点以动画方式呈现特定值

keyframes 会根据在动画时长内的不同时间戳中指定的快照值添加动画效果。在任何给定时间,动画值都将插值到两个关键帧值之间。对于其中每个关键帧,您都可以指定 Easing 来确定插值曲线。

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

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

使用 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
    ),
    label = "repeatable spec"
)

使用 infiniteRepeatable 无限重复动画

infiniteRepeatablerepeatable 类似,但它会重复无限次的迭代。

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

在使用 ComposeTestRule 的测试中,使用 infiniteRepeatable 的动画不会运行。系统将使用每个动画值的初始值来呈现组件。

使用 snap 立即跳转到结束值

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

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

设置自定义缓动函数

基于时长的 AnimationSpec 操作(如 tweenkeyframes)使用 Easing 来调整动画的小数值。这样可让动画值加速和减速,而不是以恒定的速率移动。小数是介于 0(起始值)和 1.0(结束值)之间的值,表示动画中的当前点。

Easing 实际上是一个函数,它取一个介于 0 和 1.0 之间的小数值并返回一个浮点数。返回的值可能位于边界之外,表示过冲或下冲。您可以使用如下所示的代码创建一个自定义 Easing。

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 提供多种内置 Easing 函数,可满足大多数用例的需要。如需详细了解根据您的情况应使用哪种 Easing,请参阅速度 - Material Design

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

通过相互转换为 AnimationVector 来为自定义数据类型添加动画效果

大多数 Compose 动画 API 默认支持将 FloatColorDp 以及其他基本数据类型作为动画值,但有时您需要为其他数据类型(包括您的自定义类型)添加动画效果。在动画播放期间,任何动画值都表示为 AnimationVector。使用相应的 TwoWayConverter 即可将值转换为 AnimationVector,反之亦然,这样一来,核心动画系统就可以统一对其进行处理。例如,Int 表示为包含单个浮点值的 AnimationVector1D。用于 IntTwoWayConverter 如下所示:

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

Color 实际上是 red、green、blue 和 alpha 这 4 个值的集合,因此,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)
            }
        ),
        label = "size"
    )
}

以下列表包含一些内置 VectorConverter