本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
前言
前面用三篇文章分别介绍了 Compose 动画规格的 TweenSpec
(补间动画配置)、SnapSpec
(快闪动画配置)、KeyframesSpec
(关键帧动画配置)和 SpringSpec
(弹簧动画配置),本篇将继续介绍 Compose 动画规格的最后两个配置:RepeatableSpec
(重复动画配置)、InfiniteRepeatableSpec
(无限重复动画配置)
RepeatableSpec
RepeatableSpec
顾名思义是重复配置的意思,可以让动画进行重复执行,定义如下:
class RepeatableSpec<T>(
val iterations: Int,
val animation: DurationBasedAnimationSpec<T>,
val repeatMode: RepeatMode = RepeatMode.Restart,
val initialStartOffset: StartOffset = StartOffset(0)
) : FiniteAnimationSpec<T>
RepeatableSpec
构造方法有四个参数,对应参数解析如下:
- iterations:重复次数,即让动画重复执行多少次,必填参数
- animation:重复执行的目标动画配置,必填参数
- repeatMode:重复模式,有两种可选模式:重新播放、反向播放,默认为重新播放
- initialStartOffset:初始启动偏移,偏移的对象是动画时间,默认为
StartOffset(0)
即不偏移
除了直接使用 RepeatableSpec
构造函数创建 RepeatableSpec
外,Compose 同样为我们提供了简便函数 repeatable
让开发者使用,参数与构造函数参数一致,下面就详细介绍各个参数的使用和效果。
iterations
iterations
是RepeatableSpec
必填参数之一,是动画重复执行的次数,很好理解,就是我们想让动画重复执行多少次,那么这个参数就传多少就行了,注意类型为 Int 类型,即只能为整数,如我们想让动画重复执行 5 次,代码使用如下:
val repeatableSpec = repeatable<Dp>(iterations = 5, animation = ...)
animation
animation
也是 RepeatableSpec
必填参数之一,是重复执行的目标动画配置,即我们要对那个动画配置进行重复执行。
通过 RepeatableSpec
的定义发现 animation
是 DurationBasedAnimationSpec
类型,还记得在开始介绍动画配置时的那张继承关系图么?再来看一下:
DurationBasedAnimationSpec
只有三个实现子类,也就是我们最开始介绍的三个动画配置:TweenSpec
(补间动画配置)、SnapSpec
(快闪动画配置)、KeyframesSpec
(关键帧动画配置)。
也就是说 animation
参数我们只能传TweenSpec
、SnapSpec
、KeyframesSpec
这三种动画配置对象,而上一篇介绍的 SpringSpec
(弹簧动画配置)、 现在正在介绍的RepeatableSpec
本身以及后面介绍的 InfiniteRepeatableSpec
(无限重复动画配置)都不能用于 RepeatableSpec
进行重复执行。
我们还是以我们最熟悉的方块动画来体验一下 RepeatableSpec
的使用效果,代码如下:
var moveToRight by remember { mutableStateOf(false) }
val targetValue = if(moveToRight) 200.dp else 10.dp
// 补间动画
val animation1 = tween<Dp>(durationMillis = 500)
// 快闪动画
val animation2 = snap<Dp>()
// 关键帧动画
val animation3 = keyframes<Dp> {
50.dp at 150 with FastOutSlowInEasing
150.dp at 200
}
// 添加重复配置,重复次数三次
val startPadding1 by animateDpAsState(targetValue, animationSpec = repeatable(3, animation1))
val startPadding2 by animateDpAsState(targetValue, animationSpec = repeatable(3, animation2))
val startPadding3 by animateDpAsState(targetValue, animationSpec = repeatable(3, animation3))
Column {
// 应用重复补间动画
Text("RepeatableSpec--TweenSpec", modifier = Modifier.padding(top = 20.dp, start = 10.dp))
Box(
Modifier
.padding(start = startPadding1, top = 10.dp, bottom = 10.dp)
.size(100.dp)
.background(Color.Blue)
.clickable {
moveToRight = !moveToRight
}
)
// 应用重复快闪动画
Text("RepeatableSpec--SnapSpec", modifier = Modifier.padding(top = 20.dp, start = 10.dp))
Box(
Modifier
.padding(start = startPadding2, top = 10.dp, bottom = 10.dp)
.size(100.dp)
.background(Color.Blue)
.clickable {
moveToRight = !moveToRight
}
)
// 应用重复关键帧动画
Text("RepeatableSpec--KeyframesSpec", modifier = Modifier.padding(top = 20.dp, start = 10.dp))
Box(
Modifier
.padding(start = startPadding3, top = 10.dp)
.size(100.dp)
.background(Color.Blue)
.clickable {
moveToRight = !moveToRight
}
)
}
创建三个方块,使用RepeatableSpec
分别作用于TweenSpec
、SnapSpec
、KeyframesSpec
运行一下看看效果:
作用于TweenSpec
和KeyframesSpec
的动画效果很明显,肉眼可见的确实重复执行了 3 次,但是中间的SnapSpec
动画怎么好像只执行了一次呢?这是因为 SnapSpec
的特性,前面文章我们介绍了 SnapSpec
的动画时间是 0,所以无论重复执行多少次动画总时间还是 0,所以动画效果都是一瞬间执行完成了,看着像只执行了一次。
repeatMode
repeatMode
是重复模式,值是枚举类型 RepeatMode
,有两个枚举值:Restart
重新播放和 Reverse
反向播放,定义如下:
enum class RepeatMode {
// 重新播放
Restart,
// 反向播放
Reverse
}
以 TweenSpec
为例看一下使用效果:
val spec = repeatable(3, animation, repeatMode = RepeatMode.Restart)
val sepc2 = repeatable(3, animation, repeatMode = RepeatMode.Reverse)
对同一个动画重复 3 次,重复模式分别设置Restart
和 Reverse
,看一下运行效果:
可以发现区别还是很明显的,使用 Restart
模式时在每次执行完后会瞬间回到起点再重新执行,效果就会很突兀;而使用 Reverse
模式时,在每次执行完后会反向执行,视觉体验会好很多。当然开发中应根据实际需求设置对应的重复模式。
需要注意的是在使用 Reverse
反向模式且重复次数为双数时,动画运行效果可能不符合预期,还是上面的例子将重复次数从 3 修改为 2 看一下运行效果:
val sepc2 = repeatable(2, animation, repeatMode = RepeatMode.Reverse)
可以发现,因为重复次数设置的 2 ,所以动画会从起点运动到目标位置,然后再从目标位置反向回到起点,到这里是符合预期的,但是在动画最后一瞬间又直接回到的目标位置,看起来就比较突兀,按照正常逻辑重复次数设置为 2 那动画应该停在起点才对,这是为什么呢?
这是因为我们使用的是 animateXxxAsState
动画,他的作用就是将对象以动画形式运动到目标点,而我们之前介绍的包括 RepeatableSpec
在内的动画配置都是对动画进行修饰的,他们只能改变动画的过程而不能改变动画的结果,所以上面的动画最后会瞬间回到目标终点。
在使用 RepeatableSpec
且重复模式设置为 Reverse
时,需要注意重复次数设置为双数是否符合你的预期。
initialStartOffset
initialStartOffset
是初始启动偏移,可以设置动画偏移一定的动画时长,值为 StartOffset
类型。
StartOffset
构造方法有两个参数:
- offsetMillis:偏移的动画时间,单位毫秒
- offsetType:偏移类型,有两个可选值:
-
Delay
:延迟启动,即等待offsetMillis
时长后再启动动画,是offsetType
的默认值FastForward
:快进启动,即跳过动画的offsetMillis
时长,再执行后面的动画
需要注意的是 initialStartOffset
针对的是整个动画的时长做偏移而不是每次重复的动画做偏移。
比如单个动画时长为 300ms ,重复执行 3 次,那整个动画时长为 900ms,offsetMillis
设置 300ms,offsetType
设置不同的类型时动画表现不一样,如下:
offsetType
使用Delay
时,整个动画时长为offsetMillis
( 300ms )+ 900ms = 1200msoffsetType
使用FastForward
时,动画时长为 900ms -offsetMillis
( 300ms ) = 600ms
代码及效果如下:
val spec = repeatable(3, animation, repeatMode = RepeatMode.Reverse, initialStartOffset = StartOffset(300, StartOffsetType.Delay))
val spec2 = repeatable(3, animation, repeatMode = RepeatMode.Reverse, initialStartOffset = StartOffset(300, StartOffsetType.FastForward))
当为 Delay
时动画延迟了 300ms 后执行,且重复执行了 3 次;而当为 FastForward
时,动画是立即执行,但是因为设置的 offsetMillis
为 300ms ,且单次动画时长也为 300ms,所以动画跳过了第一次动画直接到达了终点,然后再重复执行了 2 次。
InfiniteRepeatableSpec
InfiniteRepeatableSpec
是无限重复动画,即无限循环动画,使用方式与参数基本与 RepeatableSpec
相同,唯一的区别是少一个 iterations
参数,因为是无限重复所以不需要设置重复次数。
除了构造方法以外 Compose 同样提供了简便函数 infiniteRepeatable
供开发者使用,使用如下:
val spec = infiniteRepeatable( animation, repeatMode = RepeatMode.Reverse, initialStartOffset = StartOffset(300, StartOffsetType.Delay))
val startPadding1 by animateDpAsState(targetValue, animationSpec = )
效果如下:
启动后动画会无限重复的播放。
那如果我们想停止无限重复播放怎么办呢?可以修改动画状态然后根据状态对动画重新设置一个 Spec,比如上面的动画当 moveToRight 为 false 时将 animationSpec 设置为 SnapSpec
就可以停止动画了,代码如下:
// 动画状态
var moveToRight by remember { mutableStateOf(false) }
val targetValue = if (moveToRight) 200.dp else 10.dp
// 无限重复动画配置
val infiniteRepeatableSpec = infiniteRepeatable<Dp>(
tween(),
repeatMode = RepeatMode.Reverse,
initialStartOffset = StartOffset(300, StartOffsetType.Delay)
)
// 根据状态设置动画配置,当 moveToRight 为 false 时设置 snap
val animationSpec = if (moveToRight) infiniteRepeatableSpec else snap()
val startPadding by animateDpAsState(
targetValue,
animationSpec = animationSpec
)
Column {
Box(
Modifier
.padding(start = startPadding, top = 20.dp, bottom = 10.dp)
.size(100.dp)
.background(Color.Blue)
)
// 添加按钮改变 moveToRight 值
Button(
onClick = { moveToRight = !moveToRight },
modifier = Modifier.padding(start = 10.dp)
) {
Text(if (moveToRight) "停止" else "开始")
}
}
运行效果如下:
这样就实现了手动停止无限循环动画的功能。
最后
本篇详细介绍了RepeatableSpec
(重复动画配置)、InfiniteRepeatableSpec
(无限重复动画配置)的详细配置使用并展示了不同配置呈现的效果, 至此关于 Compose 动画的六种配置我们就介绍完了,从下一篇开始我们继续探索 Compose 动画更深入的使用,请持续关注本专栏了解更多 Compose 动画相关内容。