Compose低级别API动画指南

Google官方为Compose提供的动画API与原生View体系的动画API相差甚远,目前总共分为四类:高级别动画API、低级别动画API、自定义动画、手势和动画(其他),下图是官方提供的流程指示图,以供根据不同使用场景来选择相应动画:

image.png

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之间来回改变。对应效果如下:

Anima-1.gif

以此类推,改变颜色的函数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 }
    )
}

其效果为:
Anim-2.gif

此函数不需要创建任何动画类的实例,也不需要处理中断操作。系统会在后台创建并记录一个动画对象(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 可组合项针对指定键值的时长创建一个作用域。其效果如下:

Anim-3.gif

与 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
}

对应效果为:

Anim - 4.gif

可以看到,点击按钮之后,页面在Collapsed和Expanded两种状态来回切换。不仅如此,Transition中有很多其他扩展函数:
image.gif
可在实际开发中根据不同需求场景选择。

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)
    )
}

对应效果为:

Anim - 5.gif

可见这动画一直不断运行,800ms一间隔时间,颜色来回切换,这类动画除非被移除,否则不会自动停止。

TargetBasedAnimation

开发者可直接使用的最低级 API,主要用于控制动画的执行时间。updateTransition以及rememberInfiniteTransition以及Animatable动画都是由该api封装得来,可使用此动画的场景都可用animateTo()替代,这里就不再赘述。当然,以上KPI不一定适合所有场景,与此对应还有高级别动画API(下一篇讲解)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值