Google官方为Compose提供的动画API与原生View体系的动画API相差甚远,目前总共分为四类:高级别动画API、低级别动画API、自定义动画、手势和动画(其他),下图是官方提供的流程指示图,以供根据不同使用场景来选择相应动画:
animate*AsState
此函数是Compose中的简单动画API,也是低级别动画API,主要用于实现单一属性的动画效果,其能处理的相关属性如下:Float、Color、Dp、Size、Offset、Rect、Int、IntOffset 和 IntSize。使用方式只需将目标值封装在 animate*AsState()中即可。例如以下示例:
@Composable
fun showAnim(){
val enabled = remember {
mutableStateOf(true)
}
val alpha: Float by animateFloatAsState(if (enabled.value) 1f else 0.2f)
Box(
Modifier.fillMaxSize()
.graphicsLayer(alpha = alpha)
.background(Color.Red)
.clickable { enabled.value = !enabled.value }
)
}
代码理解起来并不难,通过animateFloatAsState()函数来改变控件的alpha值(alpha值为Float类型,所以是函数是animateFloatAsState),在点击事件里监听到点击则改变对应变量,通过嵌套了remember的mutableStateOf函数对界面进行重组,于是页面的alpha在0.2f和1.f之间来回改变。对应效果如下:
以此类推,改变颜色的函数API则是animateColorAsState(),改变尺寸的函数则是animateSizeAsState(),等等。例如下述的颜色改变动画:
@Composable
fun showAnim(){
val enabled = remember {
mutableStateOf(true)
}
val color = animateColorAsState(if (enabled.value) Color.Blue else Color.Yellow)
Box(
Modifier.fillMaxSize()
.background(color.value)
.clickable { enabled.value = !enabled.value }
)
}
其效果为:
此函数不需要创建任何动画类的实例,也不需要处理中断操作。系统会在后台创建并记录一个动画对象(Animatable实例),并将你设置的第一个目标值设为初始值。此后,只要目标值改变了,系统就会自动播放此目标值的动画。如果这时已有动画在播放,系统将从其当前值(和速度)开始向目标值播放动画(不会从初始值重新开始)。在播放动画期间,这个可组合项会重组,并返回已更新的每帧动画值。
Animatable
一个动画值的包装器,主要用于实现帧动画。可以在通过调用animateTo(),监听animateTo()值改变时进行相应的动画效果。在某些情况下可以指达到同animate*AsState一样的效果,但如果此动画在执行过程中如果再次执行则会中断当前动画。示例如下:
fun showAnim(){
val enabled = remember {
mutableStateOf(true)
}
val color = remember { Animatable(Color.Green) }
LaunchedEffect(enabled.value) {
color.animateTo(if (enabled.value) Color.Yellow else Color.Red)
}
Box(Modifier.fillMaxSize().background(color.value)
.clickable {
enabled.value = !enabled.value
})
}
根据官方提示,Animatable 的许多功能(包括 animateTo)以挂起函数的形式提供。这意味着,它们需要封装在适当的协程作用域内。所以这里使用 LaunchedEffect 可组合项针对指定键值的时长创建一个作用域。其效果如下:
与 animate*AsState 相比,Animatable 的初始值可以与第一个目标值不同,上例就是三个颜色的改变。此外,Animatable 还提供更多操作(即 snapTo 和 animateDecay)。snapTo 可立即将当前值设为目标值。如果动画需要与其他状态(如触摸事件)同步,则可以使用snapTo。animateDecay 用于启动播放从给定速度变慢的动画。有兴趣的可以试试,这里不做赘述。
updateTransition
Transition 可管理一个或多个动画,并在多个状态之间同时运行这些动画(有没有想起5.0的场景变换动画,但这是两个东西)。updateTransition 可创建并记住 Transition 的实例,并更新其状态,达到实现多个动画组合的效果。通俗来讲就是,通知单个或多个值发生变化来设置动画。当状态发生改变时,多个值要一起发生变化。官方推荐使用枚举来定义多个状态以确保类型安全,例如以下示例:
fun showAnim() {
val targetState = remember {
mutableStateOf(BoxState.Collapsed)
}
val transition = updateTransition(
targetState = targetState
)
val rect by transition.animateRect { state ->
when (state.value) {
BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
}
}
val offsets by transition.animateDp { state ->
when (state.value) {
BoxState.Collapsed -> 2.dp
BoxState.Expanded -> 0.dp
}
}
val color by transition.animateColor {state ->
when(state.value){
BoxState.Expanded->Color.Red
BoxState.Collapsed->Color.Yellow
}
}
Column(
Modifier
.padding(16.dp)
.size(300.dp)) {
Button(onClick = { if (targetState.value == BoxState.Expanded) targetState.value =
BoxState.Collapsed else targetState.value = BoxState.Expanded}) {
Text(text = "updateTransition Animation")
}
Box(modifier = Modifier.size(rect.width.dp, rect.height.dp)
.offset(x=offsets)
.background(color = color))
}
}
private enum class BoxState {
Collapsed,
Expanded
}
对应效果为:
可以看到,点击按钮之后,页面在Collapsed和Expanded两种状态来回切换。不仅如此,Transition中有很多其他扩展函数:
可在实际开发中根据不同需求场景选择。
rememberInfiniteTransition
InfiniteTransition主要是在Compose中用于实现重复动画,它可以像 Transition 一样保存一个或多个子动画,与updateTransition不同的是,这些动画一进入组合阶段就开始运行,除非被移除,否则不会停止。使用方式:使用 rememberInfiniteTransition 创建 InfiniteTransition 实例,通过 animateColor、animatedFloat 或 animatedValue 添加子动画,同时,通过 infiniteRepeatable 指定动画规范。例如如下示例:
fun showAnim() {
val mInfiniteTransition = rememberInfiniteTransition()
val state by mInfiniteTransition.animateColor(
initialValue = Color.Red,
targetValue = Color.Green,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 800,easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
))
Box(
Modifier
.size(200.dp)
.background(state)
)
}
对应效果为:
可见这动画一直不断运行,800ms一间隔时间,颜色来回切换,这类动画除非被移除,否则不会自动停止。
TargetBasedAnimation
开发者可直接使用的最低级 API,主要用于控制动画的执行时间。updateTransition以及rememberInfiniteTransition以及Animatable动画都是由该api封装得来,可使用此动画的场景都可用animateTo()替代,这里就不再赘述。当然,以上KPI不一定适合所有场景,与此对应还有高级别动画API(下一篇讲解)。