传统View系统的属性动画
在了解Compose的动画之前,我们先来看看传统View系统中的属性动画是什么。
一个属性动画的简单示例:
java
// 获取按钮组件
View view = findViewById(R.id.my_button);
// 创建动画,让按钮从0移动到300像素的位置(X轴)
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 300f);
animator.setDuration(1000); // 动画持续1秒
// 启动动画
animator.start();
在这个示例中,它可以让一个按钮:从左边移动到右边。

以上只是基本的使用,我们还可以添加:
-
动画监听器(AnimatorListener) 来响应动画的不同阶段,比如在动画开始/结束/取消/重复 阶段,做一些操作。
-
插值器(Interpolator) :控制动画的变化速度,它决定了动画在不同时间点的完成程度,我们可以让动画先加速后减速、先加速后加速、匀速,或者以弹跳曲线来变化速度。
-
估值器(TypeEvaluator) :计算属性在起始值和结束值之间的实际数值,就是此次动画的变化量。
说了这么多,好像懂了?
不懂没关系,总结一下,属性动画过程:在指定的时间内,以帧为单位平滑地修改对象的属性值,从而实现流畅的动画效果。
如果用一张图来形象地这个过程就是:

而Compose是声明式UI框架,我们是描述各状态下的界面外观,所以不是去直接操作UI元素。
那该怎么办才能实现动画呢?你可能已经想到了,答案就在上一行,不断修改界面的描述状态就可以了。
是不是觉得很难,别担心,Compose官方已经为我们实现了一整套的动画API。
AnimateXxxAsState()
我们先来自己实现一个变化效果,点击绿色矩形,矩形会变大:
kotlin
var size by remember { mutableStateOf(48.dp) }
Box(Modifier
.size(size)
.background(Color.Green)
.clickable {
size = 96.dp
})

很明显,这种突变效果并不是真正意义上的动画。所以我们使用Compose为我们提供的animateDpAsState()
函数来改造它:
kotlin
var size by animateDpAsState(48.dp)
Box(Modifier
.size(size)
.background(Color.Green)
.clickable {
size = 96.dp
})
animateDpAsState()
函数具有重组时防止重复初始化 和创建一个可观察的状态对象的功能,所以只需一个函数就可以完成之前两个函数的工作。
可是修改后,发现报错了:
perl
Type 'State<Dp>' has no method 'setValue(Nothing?, KProperty<*>, Dp)' and thus it cannot serve as a delegate for var (read-write property)
不对啊,你刚刚不是说animateDpAsState()
函数可以替换掉前面的两个函数吗?怎么替换后,还错了。
这是因为之前的mutableStateOf()
函数返回的是一个MutableState<Dp>
类型的对象,可以对它的value
属性进行读写;而animateDpAsState()
函数返回的是一个State<Dp>
类型的对象,它的value
属性是只读的,所以我们不能进行修改。
那不能修改,动画还怎么进行下去啊,你不是就是要去不断地修改描述状态,才可以实现动画吗?
别急只是我们开发者不能修改,具体的修改过程由框架内部去完成。我们只需使用下面这种方式,就能完成动画了:
kotlin
var big by remember { mutableStateOf(false) }
val size by animateDpAsState(targetValue = if (big) 96.dp else 48.dp)
Box(Modifier
.size(size)
.background(Color.Green)
.clickable {
big = !big
})

这种方式来写的动画,代码非常简洁,我们只需声明目标状态应该是什么,不必关心状态的过渡细节。
注意: AnimateXxxAsState()是一个系列函数,其中还有animateIntAsState、animateOffsetAsState等函数,可以用于不同类型的动画过渡。

animateXxxAsState()的工作流程
- 创建AnimationState内部对象,并设置初始值和目标值。
kotlin
val size by animateDpAsState(targetValue = if (big) 96.dp else 48.dp)
- 检测到targetValue目标值发生改变时,就开启一个动画。
kotlin
// big值改变
var big by remember { mutableStateOf(false) }
- 动画运行期间,会在每一帧计算值,更新到AnimationState内部对象的value上,并且这个value值的更新会触发重组,导致使用到这个值的组件重组。
- 在达到目标值后,动画结束。