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