Jetpack Compose(6)——动画

本文介绍 Jetpack Compose 动画。
官方文档
关于动画这块,第一次看官网,觉得内容很杂,很难把握住整个框架结构,很难去对动画进行分类。参考了很多文献资料,大多数都是从高级别 API 开始讲解,包括官网也是如此。我发现这样不太容易理解,因为高级别 API 中可能会涉及到低级别 API 中的一些方法,术语等。所以本文从低级别 API 讲起。

一、低级别动画 API

1.1 animate*AsState

animate*AsState 函数是 Compose 动画中最常用的低级别 API 之一,它类似于传统 View 中的属性动画,你只需要提供结束值(或者目标值),API 就会从当前值到目标值开始动画。
看一个改变 Composable 组件大小的例子:

@Composable
fun Demo() {
 var bigBox by remember {
 mutableStateOf(false)
 }
 // I'm here
 val boxSize by animateDpAsState(targetValue = if (bigBox) 200.dp else 50.dp, label = "")
 Box(modifier = Modifier
 .background(Color.LightGray)
 .size(boxSize) // I'm here
 .clickable {
 bigBox = !bigBox
 })
}

运行一下看看效果:

上述示例中我们使用了 animateDpAsState 这个函数,定义了一个 “Dp” 相关的动画。
其实 animate*AsState 并不是只某一个具体方法,而是只形如 animate*AsState 的一系列方法,具体如下:

是的,你没有看错,甚至可以使用 animateColorAsState 方法对颜色做动画。
聪明的你,肯定会有一个疑问,这个方法是从当前值到设定的目标值启动动画,但是动画具体执行过程是怎样的,比如持续时间等等,这个有办法控制吗?还是以 AnimateDpAsState 为例,看看这个参数的完整签名:

@Composable
fun animateDpAsState(
 targetValue: Dp,
 animationSpec: AnimationSpec<Dp> = dpDefaultSpring,
 label: String = "DpAnimation",
 finishedListener: ((Dp) -> Unit)? = null
): State<Dp> {
 return animateValueAsState(
 targetValue,
 Dp.VectorConverter,
 animationSpec,
 label = label,
 finishedListener = finishedListener
 )
}

实际上这个方法有4个参数。

  • targetValue 是没有默认值的,表示动画的目标值。
  • animationSpec 动画规格,这里有一个默认值,实际上就是这个参数决定了动画的执行逻辑。
  • lable 这个参数是为了区别在 Android Studio 中进行动画预览时,区别其它动画的。
  • finishedListener 可以用来监听动画的结束。

关于动画规格 AnimationSpec, 此处不展开,后面会详细讲解。

再延伸一点,看看该方法的实现,实际上是调用了 animateValueAsState 方法。事实上前面展示的 animate*AsState 的系列方法都是调用的 animateValueAsState

看看源码:

@Composable
fun <T, V : AnimationVector> animateValueAsState(
 targetValue: T,
 typeConverter: TwoWayConverter<T, V>,
 animationSpec: AnimationSpec<T> = remember { spring() },
 visibilityThreshold: T? = null,
 label: String = "ValueAnimation",
 finishedListener: ((T) -> Unit)? = null
): State<T> {
 val toolingOverride = remember { mutableStateOf<State<T>?>(null) }
 val animatable = remember { Animatable(targetValue, typeConverter, visibilityThreshold, label) }
 val listener by rememberUpdatedState(finishedListener)
 val animSpec: AnimationSpec<T> by rememberUpdatedState(
 animationSpec.run {
 if (visibilityThreshold != null && this is SpringSpec &&
 this.visibilityThreshold != visibilityThreshold
 ) {
 spring(dampingRatio, stiffness, visibilityThreshold)
 } else {
 this
 }
 }
 )
 val channel = remember { C