Jetpack Compose - 动画的几种结束机制 (九)
动画的打断机制
所谓动画的打断机制 其实就是当某一个anim 在执行的时候,如果这个anim又跑去执行了其他的动画 那么之前的动画就会被打断, 注意是打断,而不是等前面一个动画执行完了再执行下一个
可以参考下面的这段代码, 本来我这个box 是要惯性滑动到某个位置的, 但是因为我在1100的时候 又开始了 另外一个anim的操作 所以在滑动到那个位置之前 我就往回惯性滑动了
也就是说在1000ms 开始执行的动画 被打断了,我们可以在try catch中 捕获到这个被打断的异常
setContent {
val anim = remember {
Animatable(0.dp, Dp.VectorConverter)
}
val decay = rememberSplineBasedDecay<Dp>()
LaunchedEffect(Unit) {
delay(1000)
// 动画的监听
try {
anim.animateDecay(3000.dp, decay)
} catch (e: CancellationException) {
Log.e("wuyue","cancel my anim")
}
}
LaunchedEffect(Unit) {
delay(1100)
// 动画的监听
anim.animateDecay((-1000).dp, decay)
}
Box(
Modifier
.padding(0.dp, anim.value, 0.dp, 0.dp)
.size(100.dp)
.background(Color.Green)
) {
}
}
下面这3个函数 会出现互相打断的情况
anim.animateDecay()
anim.animateTo()
anim.snapTo()
动画的停止
在某些时候,我们需要对某个进行中的动画发出信号让他停止执行
.clickable {
lifecycleScope.launch{
anim.stop()
}
}
在compose中 我们可以方便的使用stop函数 即可,注意了 这个stop和anim函数一样 都是需要协程的执行环境的, 所以我们需要给他指定一个协程环境的scope
有人要问了 为啥这里不用launedEffect ,le 也是一个scope啊,
因为通常而言 le的环境是和compose的生命周期绑定在一起的,当你不想在compose重组的时候 重复执行某些操作 那就可以用 le,其他时候 其实不用le 更好
动画的边界条件
看下面这个代码
setContent {
val anim = remember {
Animatable(0.dp, Dp.VectorConverter)
}
val decay = rememberSplineBasedDecay<Dp>()
LaunchedEffect(Unit) {
delay(1000)
// 动画的监听
try {
anim.animateDecay(3000.dp, decay)
} catch (e: CancellationException) {
Log.e("wuyue","cancel my anim")
}
}
Box(
Modifier
.padding(anim.value, 0.dp, 0.dp, 0.dp)
.size(100.dp)
.background(Color.Green)
.clickable {
lifecycleScope.launch{
anim.stop()
}
}
) {
}
}
同样是动画执行,但是这次 我们改变的是 paddingleft, 执行以后你会发现这个方块 出了屏幕了看不到了?
但是往往我们需要的是 想让这个方块在滑动的时候 到屏幕边缘就停止
所以我们需要给 动画设置一个边界条件,让他到了边界条件的时候就自动停止
那么显然 在这里 我们的边界条件就是让这个方块 滑动到屏幕边缘就立即结束 不要继续
进行如下修改即可
setContent {
BoxWithConstraints {
val anim = remember {
Animatable(0.dp, Dp.VectorConverter)
}
val decay = rememberSplineBasedDecay<Dp>()
LaunchedEffect(Unit) {
delay(1000)
// 动画的监听
try {
anim.animateDecay(3000.dp, decay)
} catch (e: CancellationException) {
Log.e("wuyue","cancel my anim")
}
}
anim.updateBounds(upperBound = maxWidth - 100.dp)
Box(
Modifier
.padding(anim.value, 0.dp, 0.dp, 0.dp)
.size(100.dp)
.background(Color.Green)
.clickable {
lifecycleScope.launch {
anim.stop()
}
}
) {
}
}
}
这里要注意的是 anim.updateBounds(upperBound = maxWidth - 100.dp)
这行代码madxWidth是BoxWithConstraints他的一个默认属性,我们可以直接使用, 为啥要-100.dp 这里要想明白
因为你想要的其实是 这个方块的右边距 顶着右边屏幕, 而我们一个view的坐标 是这个view的左顶点,所以 你的动画边距。最后算出来的距离 就必须得是 屏幕宽度-box的宽度
动画的返回值
还是上面的代码 ,我们稍微做一下修改, 给动画的执行加一个返回值
LaunchedEffect(Unit) {
delay(1000)
// 动画的监听
try {
val result = anim.animateDecay(3000.dp, decay)
if (result.endReason == AnimationEndReason.BoundReached) {
Log.e("wuyue","finish because endReason is boundReached")
}
} catch (e: CancellationException) {
Log.e("wuyue","cancel my anim")
}
}
再看一下 动画执行到屏幕边缘以后的日志
这里能明确的看出来
到边缘停止的情况,是不会有异常的,反而我们还能在动画执行的返回值中 判断到这种情况
我们甚至可以利用这个方便的返回值,来很方便的实现 反弹效果
LaunchedEffect(Unit) {
delay(1000)
// 动画的监听
try {
var result = anim.animateDecay(3000.dp, decay)
while (result.endReason == AnimationEndReason.BoundReached) {
result = anim.animateDecay(-result.endState.velocity, decay)
}
} catch (e: CancellationException) {
}
}
anim.updateBounds(upperBound = maxWidth - 100.dp, lowerBound = 0.dp)
转载自:https://juejin.cn/post/7160878990347993101