前言

前面讲了Animatable的根底以及衰减动画的用法,本篇则首要解说 Animatable 动画的中止,动画的中止状况首要分为四种,如下:

  • 动画正常运转完结后中止
  • 动画被打断中止
  • 自动中止动画
  • 动画触达鸿沟中止

第一种很好了解,便是动画按照咱们规划的参数正常运转完结的状况,这种状况归于正常中止,此刻动画回来成果的 endReasonAnimationEndReason.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 方位,为了区别两个动画动画履行时为方块设置了不同的色彩,运转作用如下:

Android Compose 动画使用详解(十)Animatable之动画停止

从上面的作用能够看出,对同一个 Animatable敞开新的动画的确会打断正在运转的动画。

除了上面演示的 animateTo能够打断动画以外,AnimatablesnapToanimateDecay相同能够打断动画。

自动中止

前面介绍的是新动画打断正在运转的动画,那么假如咱们想自动中止一个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办法也是一个挂起函数,需要在协程中履行,作用如下:

Android Compose 动画使用详解(十)Animatable之动画停止

触达鸿沟中止

Animatable能够经过 updateBounds函数为动画设置鸿沟值,当动画运动到鸿沟时会当即中止动画,updateBounds定义如下:

fun updateBounds(lowerBound: T? = this.lowerBound, upperBound: T? = this.upperBound)

updateBounds办法有两个参数 lowerBoundupperBound别离为动画的鸿沟下限值和上限值,默以为 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,看一下运转作用:

Android Compose 动画使用详解(十)Animatable之动画停止

能够看出来,尽管动画目标设置别离设置了 -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)
                    )
                }
            }
    )
}

运转作用:

Android Compose 动画使用详解(十)Animatable之动画停止

能够发现,上限设置为 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)
    }
}

运转作用如下:

Android Compose 动画使用详解(十)Animatable之动画停止

这样就经过监听动画的中止完成了动画抵达鸿沟后反向运动的作用。

最后

本篇介绍了 Animatable 动画的中止,包括打断中止、自动中止和抵达鸿沟中止,介绍了不同中止的完成方法以及对动画中止的监听处理。下一篇咱们持续探究 Compose 动画的其他运用,请持续重视本专栏了解更多 Compose 动画内容。

本文正在参加「金石计划」