前言
前面讲了Animatable
的根底以及衰减动画的用法,本篇则首要解说 Animatable
动画的中止,动画的中止状况首要分为四种,如下:
- 动画正常运转完结后中止
- 动画被打断中止
- 自动中止动画
- 动画触达鸿沟中止
第一种很好了解,便是动画按照咱们规划的参数正常运转完结的状况,这种状况归于正常中止,此刻动画回来成果的 endReason
为 AnimationEndReason.Finished
(在《Android Compose 动画运用详解(八)Animatable的运用》一文中有详细介绍),本文首要介绍剩余三种中止状况。
打断中止
望文生义即动画在运转进程中被打断,那么是被什么打断呢?被同一个 Animatable
的另一个动画打断,简单的说便是当 Animatable
在履行某一个动画的进程中,此刻再运用同一个 Animatable
去敞开另一个动画,此刻就会打断正在运转中的动画,即中止正在运转中的话履行新的动画。
那么为什么要打断正在运转中的动画呢?不能两个动画一起履行吗?假设一个动画是将方块移动到 200dp 的方位,另一个动画是将方块移动到 0dp 的方位,假如不会被打断两个动画能够一起履行,那就会呈现方块一瞬间往 200dp 方位一瞬间往 0dp 移动的闪耀状况,显然动画作用不符合预期,所以被规划成了后一个动画会打断前一个动画。
下面就用一个实例演示动画的打断,代码如下:
// 方块色彩,默以为蓝色
var backgroundColor by remember { mutableStateOf(Color.Blue) }
// 动画实例
val animatable = remember { Animatable(10.dp, Dp.VectorConverter) }
// 获取协程作用域
val scope = rememberCoroutineScope()
Box {
// 动画方块
Box(
Modifier
// 运用动画值
.padding(start = animatable.value, top = 30.dp)
.size(100.dp, 100.dp)
.background(backgroundColor)
.clickable { // 点击事情
// 发动动画
scope.launch {
animatable.animateTo(200.dp,
// 为了便利看到作用,动画时间设置为 1000ms
animationSpec = tween(durationMillis = 1000)
)
}
}
)
// 按钮,用于敞开新动画
Button(onClick = {
// 修正方块色彩,便利观察区别两个动画
backgroundColor = Color.Cyan
// 发动新动画
scope.launch {
animatable.animateTo(50.dp, animationSpec = tween(durationMillis = 1000))
}
}, Modifier.padding(top = 170.dp, start = 70.dp)) {
Text(text = "Next", style = TextStyle(fontSize = 10.sp))
}
}
界面上添加了一个方块和一个按钮,点击方块履行动画移动到 200dp 方位,点击按钮履行动画移动到 50dp 方位,为了区别两个动画动画履行时为方块设置了不同的色彩,运转作用如下:
从上面的作用能够看出,对同一个 Animatable
敞开新的动画的确会打断正在运转的动画。
除了上面演示的 animateTo
能够打断动画以外,Animatable
的snapTo
、animateDecay
相同能够打断动画。
自动中止
前面介绍的是新动画打断正在运转的动画,那么假如咱们想自动中止一个Animatable
动画该怎么办呢?很简单,Animatable
供给了stop
办法用于中止动画。
示例代码如下:
var backgroundColor by remember { mutableStateOf(Color.Blue) }
// 动画实例
val animatable = remember { Animatable(10.dp, Dp.VectorConverter) }
val scope = rememberCoroutineScope()
Box {
// 动画方块
Box(
Modifier
// 运用动画值
.padding(start = animatable.value, top = 30.dp)
.size(100.dp, 100.dp)
.background(backgroundColor)
.clickable { // 点击事情,敞开动画
scope.launch {
animatable.animateTo(200.dp,
// 为了便利观察动画作用,动画时长设置为 1000ms
animationSpec = tween(durationMillis = 1000)
)
}
}
)
// 中止按钮
Button(onClick = {
// 修正方块色彩
backgroundColor = Color.Cyan
// 中止动画
scope.launch {
animatable.stop()
}
}, Modifier.padding(top = 170.dp, start = 70.dp)) {
Text(text = "Stop", style = TextStyle(fontSize = 10.sp))
}
}
需要注意的是 stop
办法也是一个挂起函数,需要在协程中履行,作用如下:
触达鸿沟中止
Animatable
能够经过 updateBounds
函数为动画设置鸿沟值,当动画运动到鸿沟时会当即中止动画,updateBounds
定义如下:
fun updateBounds(lowerBound: T? = this.lowerBound, upperBound: T? = this.upperBound)
updateBounds
办法有两个参数 lowerBound
、upperBound
别离为动画的鸿沟下限值和上限值,默以为 null
即不做限制,能够单独设置上限和下限的值,当设置对应值后,动画运转进程中动画值抵达鸿沟值时就会当即中止动画。
示例代码如下:
// 创立状况 经过状况驱动动画
var moveToRight by remember { mutableStateOf(false) }
// 动画实例
val animatable = remember { Animatable(10.dp, Dp.VectorConverter) }
// 设置动画鸿沟
animatable.updateBounds(lowerBound = 10.dp, upperBound = 200.dp)
val scope = rememberCoroutineScope()
Box(
Modifier
// 运用动画值
.padding(start = animatable.value, top = 30.dp)
.size(100.dp, 100.dp)
.background(Color.Blue)
.clickable { // 点击事情
// 修正状况
moveToRight = !moveToRight
scope.launch {
// 履行动画
animatable.animateTo(
// 根据状况设置动画的目标值,别离是向右到 400dp 和 向左到 -100dp 方位
if (moveToRight) 400.dp else -100.dp,
animationSpec = tween(durationMillis = 1000)
)
}
}
)
上面代码别离设置了下限值为 10dp、上限值为 200dp,一起动画目标值别离设置为 -100dp 和 400dp,看一下运转作用:
能够看出来,尽管动画目标设置别离设置了 -100dp 和 400dp,可是因为咱们设置了鸿沟值为 10dp 和 200dp,所以动画向右运动时抵达鸿沟值即 200dp 方位时就中止了,向左相同的抵达鸿沟值 10dp 也中止了动画,这便是动画鸿沟的作用。
多维鸿沟
之前介绍了 Compose 动画是能够作用于多维数值的,比方作用于 Size、Offset、React 等数据时便是多维的动画,此刻对动画设置鸿沟后,动画目标值只要有其间一维的数值抵达鸿沟就会当即中止,并不会等到所有维的数值都抵达鸿沟才会中止。
下面用一个示例来举例说明,还是上面的方块动画,上面只进行了横向的动画,假如咱们要一起进行横向和竖向的动画,能够运用 Offset 来进行动画,然后对其进行鸿沟设置来观察作用,代码如下:
// 创立状况 经过状况驱动动画
var moveToRight by remember { mutableStateOf(false) }
// 动画实例
val animatable = remember { Animatable(Offset(10f, 30f), Offset.VectorConverter) }
// 设置鸿沟值,下限:Offset(10f, 30f) 上限:Offset(400f, 200f)
animatable.updateBounds(lowerBound = Offset(10f, 30f), upperBound = Offset(400f, 200f))
val scope = rememberCoroutineScope()
Box {
Box(
Modifier
// 运用动画值
.padding(start = animatable.value.x.dp, top = animatable.value.y.dp)
.size(100.dp, 100.dp)
.background(Color.Blue)
.clickable {
// 修正状况
moveToRight = !moveToRight
scope.launch {
animatable.animateTo(
// 根据状况设置动画的目标值
// 别离为向右和向下的 Offset(400f,400f)
// 向上和向左的 Offset(-100f,0f)
if (moveToRight) Offset(400f,400f) else Offset(-100f,0f),
animationSpec = tween(durationMillis = 1000)
)
}
}
)
}
运转作用:
能够发现,上限设置为 Offset(400f, 200f)
即 x 轴最大为 400dp、y 轴最大为 200dp,动画目标值为Offset(400f,400f)
,当方块移动到 y 坐标为 200dp 时 y 坐标的值抵达鸿沟值,动画就中止了,此刻 x 坐标值并未抵达鸿沟值。相同的往回履行时 x 坐标先触发抵达鸿沟值 10dp 时中止了动画。
假如便是想让动画都抵达鸿沟才中止,此刻不应该采用多维动画的方法,而是应该运用多个单维动画,对其别离设置鸿沟即可。
动画中止监听
动画中止可分为反常中止和正常中止,其间打断和自动中止动画归于反常中止,动画运转完结或抵达鸿沟后中止归于正常中止。
反常中止
一个动画打断另一个动画,或调用 stop
自动中止动画都归于反常中止,此刻动画会抛出 CancellationException
的反常,在代码中可经过捕获该反常来监听动画的反常中止,代码如下:
scope.launch {
try {
// 反常中止时动画会抛出反常,不会正常回来动画成果
val animationResult = animatable.animateTo(200.dp,
animationSpec = tween(durationMillis = 1000)
)
// 动画反常中止时会抛出反常,下面的代码不会被履行
// do something
} catch (e: CancellationException) {
Log.e("ANIMATOIN", "动画反常中止")
}
}
正常中止
动画触达鸿沟中止归于正常中止,此刻动画会正常回来成果,从成果中的 endReason
可判别动画是否为触达鸿沟中止,代码如下:
scope.launch {
val animationResult = animatable.animateTo(200.dp,
animationSpec = tween(durationMillis = 1000)
)
// 判别动画是否触达鸿沟中止
if(animationResult.endReason == AnimationEndReason.BoundReached){
// do something
}
}
一起动画成果还能拿到动画中止时的速度等数据,这样就能经过监听动画鸿沟中止进行自定义的处理,比方结合上一篇介绍的衰减动画让动画抵达鸿沟后反向运动,代码如下:
@Preview
@Composable
fun AnimationBound3() {
// 动画实例
val animatable = remember { Animatable(10.dp, Dp.VectorConverter) }
// 设置鸿沟值
animatable.updateBounds(upperBound = 200.dp, lowerBound = 10.dp)
val scope = rememberCoroutineScope()
val splineBasedDecay = rememberSplineBasedDecay<Dp>()
Box(
Modifier
// 运用动画值
.padding(start = animatable.value, top = 30.dp)
.size(100.dp, 100.dp)
.background(Color.Blue)
.clickable {
scope.launch {
// 发动动画,设置初始速度为 3000dp
val animationResult = animatable.animateDecay(3000.dp, splineBasedDecay)
// 判别是否为抵达鸿沟中止
if(animationResult.endReason == AnimationEndReason.BoundReached){
// 履行反向动画,初始速度取动画结束时速度的负数
reverseAnimation(animatable, -animationResult.endState.velocity, splineBasedDecay)
}
}
}
)
}
/// 反向履行动画
private suspend fun reverseAnimation(
animatable: Animatable<Dp, AnimationVector1D>,
initialVelocity: Dp,
splineBasedDecay: DecayAnimationSpec<Dp>
) {
val result = animatable.animateDecay(initialVelocity, splineBasedDecay)
// 判别是鸿沟中止时递归履行反向动画直到动画非鸿沟中止
if(result.endReason == AnimationEndReason.BoundReached){
reverseAnimation(animatable, -result.endState.velocity, splineBasedDecay)
}
}
运转作用如下:
这样就经过监听动画的中止完成了动画抵达鸿沟后反向运动的作用。
最后
本篇介绍了 Animatable
动画的中止,包括打断中止、自动中止和抵达鸿沟中止,介绍了不同中止的完成方法以及对动画中止的监听处理。下一篇咱们持续探究 Compose 动画的其他运用,请持续重视本专栏了解更多 Compose 动画内容。
本文正在参加「金石计划」