Android Compose 动画使用详解(四)动画配置之TweenSpec

2,237 阅读7分钟

本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

前言

前面用两篇文章介绍了 animateXxxAsState 动画的使用以及如何自定义 animateXxxAsState的动画 Api,而 animateXxxAsState动画 Api 的一个非常重要参数 animationSpec 却一直没做过多介绍,我们知道在传统的属性动画中是可以对动画运行的时长、延迟时间、速率曲线等进行配置,但是在前面介绍的 animateXxxAsState的使用中并没有介绍到相关的配置,是 Compose 动画不支持相关配置吗?还是说只是animateXxxAsStateApi 不支持?当然不是,实际上animationSpec这一参数就是专门对动画进行相应配置的。

从本篇开始就来一步步了解 Compose 的动画能进行哪些相关配置。

AnimationSpec

通过查看 animateXxxAsState 源码可以发现 animationSpec 参数是 AnimationSpec类型的,而 AnimationSpec 是一个接口,源码如下:

interface AnimationSpec<T> {
    fun <V : AnimationVector> vectorize(
        converter: TwoWayConverter<T, V>
    ): VectorizedAnimationSpec<V>
}

那么是要我们自己去实现这个接口去对动画进行配置么?当然可以,但这样太麻烦了,Compose 提供了一系列预置的实现类帮助我们对动画的常用配置项进行设置。来看一下 AnimationSpec到底有哪些预置的实现类,如图:

其中 FloatAnimationSpecFiniteAnimationSpecDurationBasedAnimationSpec是接口,所以真正实现类有 8 个,而在这 8 个实现类中 FloatTweenSpecFloatSpringSpecTweenSpecSpringSpec作用基本是相同的,只是前者是只针对 Float 类型,而后者是针对所有类型,实际开发中使用后者即可,而前者一般是用于 Compose 动画底层实现辅助用的。

这样算下来 AnimationSpec的真正实现类就只有 6 个了,分别是:

  • KeyframesSpec:关键帧动画
  • SnapSpec:快闪动画
  • TweenSpec:补间动画
  • RepeatableSpec:循环动画
  • SpringSpec:弹簧动画
  • InfiniteRepeatableSpec:无限循环动画

从这一篇开始我们就针对这 6 个实现类进行一一探索,看看它们到底能配置那些内容,能实现什么样的效果,本篇首先从最简单的 TweenSpec开始。

TweenSpec

TweenSpec补间动画是用于配置动画的时长、延迟时间以及运行速率曲线,看一下 TweenSpec 类的定义:

class TweenSpec<T>(
    val durationMillis: Int = DefaultDurationMillis,
    val delay: Int = 0,
    val easing: Easing = FastOutSlowInEasing
) : DurationBasedAnimationSpec<T>

参数说明:

  • durationMillis:动画时长,单位毫秒,默认为 DefaultDurationMillis为 300ms
  • delay: 动画延迟,单位毫秒,默认为 0,即延迟指定时间后执行动画
  • easing:动画运行速率曲线,默认为 FastOutSlowInEasing

下面来分别看看这三个参数具体如何使用以及配置后的动画效果如何。

动画时长

看过前面几篇文章细心的同学会发现前面介绍的所有 Compose 动画好像都不能设置动画时长,那么这里我们就可以通过 TweenSpec来进行设置。

还是以之前的方块移动动画为例,通过给 animationSpec 参数设置 TweenSpec来设置动画时长:

val startPadding by animateDpAsState(targetValue, animationSpec = TweenSpec(1000))

运行效果:

除了直接使用构造函数创建 TweenSpec实例外,Compose 还提供了一个简便的函数 tween来进行创建,它的参数跟 TweenSpec 的构造参数完全一样,使用如下:

val startPadding by animateDpAsState(targetValue, animationSpec = tween(1000))

实际上 tween的实现就是调用的 TweenSpec 构造函数,源码如下:

fun <T> tween(
    durationMillis: Int = DefaultDurationMillis,
    delayMillis: Int = 0,
    easing: Easing = FastOutSlowInEasing
): TweenSpec<T> = TweenSpec(durationMillis, delayMillis, easing)

动画延迟

通过设置 TweenSpecdelay(使用 tween函数时参数名为 delayMillis)参数即可实现延迟启动动画 ,使用方式如下:

val startPadding by animateDpAsState(targetValue, animationSpec = tween(durationMillis = 1000, delayMillis = 1000))

运行效果:

动画速率曲线

前面两个参数都很好理解,这第三个参数 easing到底是什么意思呢?官方翻译为缓动,其实就是动画数值变化的速率曲线,类比属性动画就是插值器 Interpolator, Compose 也提供了四种预置的动画速率曲线:

  • FastOutSlowInEasing:先加速后减速,对应属性动画插值器的 FastOutSlowInInterpolator
  • LinearOutSlowInEasing:先匀速后减速,对应属性动画插值器的 LinearOutSlowInInterpolator
  • FastOutLinearInEasing:先加速后匀速,对应属性动画插值器的 FastOutLinearInInterpolator
  • LinearEasing:匀速运行

可能大家对这四个曲线的命名不太好理解记不住,除了最后一个匀速外前面三个都是分为两段,如第一个的 FastOut、SlowIn,即动画开始的速率表现和动画结束的速率表现,即加速开始然后减速结束,为了帮助大家理解下面我画了一个简单的示意图:

通过这个示例图相信大家对这四个曲线会有更好的理解,下面就来分别看一下这四个动画速率曲线的使用和效果。

FastOutSlowInEasing

动画加速开始,然后减速停止,使用代码如下:

val offsetY by animateDpAsState(targetValue, animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing))

效果:

这里使用了一个辅助曲线帮助大家更好的理解配置 easing 后的动画效果与速率曲线的关系。

LinearOutSlowInEasing

动画匀速开始,然后减速停止,使用代码如下:

val offsetY by animateDpAsState(targetValue, animationSpec = tween(durationMillis = 1000, easing = LinearOutSlowInEasing))

效果:

FastOutLinearInEasing

动画加速开始,然后匀速停止,使用代码如下:

val offsetY by animateDpAsState(targetValue, animationSpec = tween(durationMillis = 1000, easing = FastOutLinearInEasing))

效果:

LinearEasing

动画从开始到结束匀速运动,使用代码如下:

val offsetY by animateDpAsState(targetValue, animationSpec = tween(durationMillis = 1000, easing = LinearEasing))

效果:

自定义 Easing

除了上面介绍的 4 种 Compose 预置的动画速率曲线外我们能否自定义其他曲线呢?答案是肯定的。Easing 本身是一个接口类型,我们只需自定义实现这个 Easing 接口即可自定义动画的速率曲线,Easing 定义如下:

fun interface Easing {
    fun transform(fraction: Float): Float
}

其中 fraction是动画的进度值,值为 0.0 ~ 1.0之间,其中 0.0 为动画开始, 1.0 为动画结束,返回的是转换后的 fraction 值,我们可以通过修改 fraction 的转换规则来改变动画的速率曲线。

前面介绍的预置的 LinearEasing就是最简单的自定义 Easing 实现,直接将 fraction 未做任何转换直接进行返回,源码如下:

val LinearEasing: Easing = Easing { fraction -> fraction }

假设我们要自定义一个 Easing 类,实现当动画进度小于 0.3 或大于 0.7 时进行匀速运动,在 0.3 ~ 0.7 之间时停在动画的中间位置,代码应该这样写:

class CustomEasing : Easing {
    override fun transform(fraction: Float): Float {
        if (fraction < 0.3 || fraction > 0.7) {
            return fraction
        } else {
            return 0.5f
        }
    }
}

使用:

val offsetY by animateDpAsState(targetValue, animationSpec = tween(durationMillis = 1000, easing = CustomEasing()))

看一下运行效果:

虽然效果怪怪的,但是确实是实现了自定义的效果

自定义贝塞尔曲线 Easing

Compose 提供了一个预置的 Easing 实现类 CubicBezierEasing贝塞尔曲线的 Easing,而前面介绍的预置的 4 种曲线除了 LinearEasing 外其他三个都是通过 CubicBezierEasing 创建的,源码如下:

val FastOutSlowInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f)

val LinearOutSlowInEasing: Easing = CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f)

val FastOutLinearInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 1.0f, 1.0f)

CubicBezierEasing是三阶贝塞尔曲线,三阶贝塞尔曲线有 4 个点,起点为 (0,0) 结束点位 (1,1) ,中间两个点则为 CubicBezierEasing 构造方法传入的数据,即 (x1,y1)、(x2,y2) 四个参数。

我们可以通过 CubicBezierEasing 来创建自己想要的动画速率曲线,这里推荐一个专门用来预览贝塞尔曲线速率动画的网站 cubic-bezier ,通过这个网站我们可以设置自己想要的曲线并预览动画运行效果:

将上面的曲线通过 CubicBezierEasing实现:

val CustomCubicBezierEasing: Easing = CubicBezierEasing(0.79f, -0.04f, 0.64f, 1.34f)

使用:

val offsetY by animateDpAsState(targetValue, animationSpec = tween(durationMillis = 1000, easing = CustomCubicBezierEasing))

运行效果:

在实际开发中 Compose 预置的四种 Easing 基本能满足大部分需求,即使需要自定义一般也是多用 CubicBezierEasing 来自定义动画速率曲线,而实现 Easing 接口进行自定义的情况则相对较少。

最后

本文对 Compose 动画的配置 AnimationSpec 进行了简单的介绍,并详细介绍了 TweenSpec 的使用,通过 TweenSpec我们可以对动画的时长、延迟时间以及动画速率曲线进行配置,并且可基于需求对动画的速率曲线进行自定义。下一篇将继续介绍 AnimationSpec 的其他配置实例,欢迎在下方关注专栏持续了解 Compose 动画相关内容。