Compose动画(二)

270 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情

概述

在上一篇笔记中,已经学习了使用AnimatedVisibility在子项显示和隐藏的时候使用动画,这篇学习笔记将会学些其它一些动画的用法。

animateContentSize

animateContentSize是一个修饰符,可以在可组合项的大小变化的时候使用相应的动画,如下面的代码所示:

            Text(
                text = inputContentState, modifier = Modifier
                    .background(Color.DarkGray)
                    .animateContentSize()
                    .padding(top = 10.dp)
            )

在上面的代码中,我们对Text可组合项指定了animateContentSize修饰符,这样,当文本框中的内容变化引起尺寸变化的时候就能够看到相应的动画效果了。运行上面的程序,可以看到如下的效果:

animateContentSize简单使用

从上面的动图可以看出,当Text的尺寸变化的时候使用的时动画。

Crossfade

这个可组合项用于在多个布局之间切换的时候使用动画,通过接收targetState参数,可以使用淡入淡出动画切换内容,如下面的代码所示:

Crossfade(targetState = currentScreen.value) {
                when (it) {
                    "A" -> Surface(
                        modifier = Modifier
                            .size(30.dp),
                        shape = CircleShape,
                        color = Color.Blue,
                        contentColor = Color.White
                    ) {
                        Text(text = "A", modifier = Modifier.wrapContentSize(Alignment.Center))
                    }
                    "B" -> Surface(
                        modifier = Modifier
                            .size(40.dp),
                        shape = RoundedCornerShape(10.dp),
                        color = Color.Blue,
                        contentColor = Color.White
                    ) {
                        Text(
                            text = "B",
                            textAlign = TextAlign.Center,
                            modifier = Modifier.wrapContentSize(
                                Alignment.Center
                            )
                        )
                    }
                    else -> Surface(
                        modifier = Modifier
                            .size(50.dp),
                        shape = RectangleShape,
                        color = Color.Blue,
                        contentColor = Color.White
                    ) {
                        Text(text = "Other", Modifier.wrapContentSize(Alignment.Center))
                    }
                }
            }

在上面的代码中,我们定义的参数有三种状态,分别是"A","B","C",每种状态显示的大小和内容都是不一样的,当我们点击按钮改变状态的时候,Crossfade会使用淡入淡出动画切换其子项,如下面的图片所示:

使用Crossfade切换布局

在之前的学习中,我们都是使用现有的可组合项或者修饰符对当前的可组合项或者其子项使用动画,我们可以指定动画类型但是无法指定动画的过程,很多时候系统提供的这些动画可能并不能满足我们的需求,我们需要能够更好的控制动画的实现,在Compose中同样提供了一些较为底层的API来帮我们实现这样的需求。

animate[*]AsState

这个函数用于为单个值提供动画,我们只需要向其提供结束值,这个方法就会自动从当前值开始向目标值执行动画。这个函数是由Animatable提供支持,Animatable是一种基于协程的API,用于为单个值添加动画。

在下面的代码中,我们通过一个按钮来控制另一个可组合项的尺寸,点击按钮修改可组合项的尺寸,并且让这个尺寸以动画的形式变化,如下面的代码所示:

val width: Int by animateIntAsState(targetValue = if (scale) 120 else 60)
Surface(
    modifier = Modifier.size(width.dp, 80.dp),
    shape = RoundedCornerShape(10.dp),
    color = Color.Blue,
    contentColor = Color.White
) {
    Text(
        text = "演示\n动画",
        modifier = Modifier.wrapContentSize(Alignment.Center),
    )
}

在上面的代码中,我们对Surface可组合项的宽度使用动画animateIntAsState去定义,宽度大小会在120到60之间变化,如果当前已经是120了,那么就变化到60,否则变化到120.

运行上面的代码,可以看到如下的效果:

使用animateIntAsState改变可组合项的宽度

上面使用的是animateIntAsState,在Compose中为Float,Color,Dp,Size,Offset,Rect,Int,IntOffset和IntSize均提供了相应的animate[*]AsState,我们可以在需要的地方直接使用。

Animatable

在上面一节我们已经提到了AnimatableAnimate[*]AsState就是通过Animatable实现的。这是一个值容器,在通过animateTo()更改值时为值添加动画效果。Animatable的许多功能都是使用挂起函数的形式实现,因此,它需要封装在适当的协程作用域内,我们可以使用LaunchedEffect可组合项针对指定键值的时长创建一个作用域。如下面的代码所示:

            val color = remember {
                Animatable(Color.Green)
            }

            LaunchedEffect(isRed) {
                color.animateTo(if (isRed) Color.Green else Color.Red)
            }
            
            Surface(
                color = color.value,
                contentColor = Color.White,
                modifier = Modifier.size(80.dp),
                shape = CircleShape
            ) {
                Text(text = "演示\n动画", modifier = Modifier.wrapContentSize(Alignment.Center))
            }

在上面的代码中,我们创建了一个Animatable对象, 并传递颜色的初始值为绿色,接着获取到当前的协程作用域,在其中调用了改变颜色值的方法,然后创建了一个Surface可组合项,设置其背景颜色为动画的当前值,通过改变isRed的值,可以引起界面发生重组,重组的时候就可以看到可组合项背景的动画,如下面的图片显示:

使用Animatable改变可组合项的背景颜色

需要注意的是:如果是ColorFloat类型,则可以直接使用Animatable,对于其它类型可以使用TwoWayConverter转换之后即可使用。

AnimationSpec

在上面的代码中,我们一直在使用系统或者可组合项内部预定义的动画类型,我们可以看到它们能够实现什么样的效果,但是我们无法改变其参数,甚至我们没有改变动画执行的时间。通过使用AnimationSpec参数,我们可以定义自己的动画行为,AnimationSpec包含多种类型,我们可以根据具体的需求使用其中的某一种类型。

spring

这种类型可以在起始值和目标值之间使用基于物理特性的动画,也就是我们之前说过的弹性动画,这个类型可以接收dampingRatiostiffness这两个参数, 其中dampingRatio表示弹簧阻尼比,stiffness则表示弹框的刚性。可以认为阻尼比表示的是动画回弹的次数,阻尼比越小,弹簧的会谈次数越多。而刚性则表示的是弹簧从开始位置到目标位置所需要的时间,刚性越大,需要的时间就越少,下面的代码使用弹性动画改变组合项的尺寸:

    val size by animateDpAsState(
        targetValue = if (scale) 100.dp else 50.dp,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioHighBouncy,
            stiffness = Spring.StiffnessHigh
        )
    )
    
    Surface(
        color = Color.Blue,
        contentColor = Color.White,
        shape = CircleShape,
        modifier = Modifier.size(size)
    ) {
        Text(text = "演示\n动画", modifier = Modifier.wrapContentSize(Alignment.Center))
    }

在上面的代码中,我们指定了动画的类型为弹性动画,其中阻尼比是较小的值,刚性设置的是较大的值,下面的表格中演示了使用不同值时动画的执行效果:

dampingRatiostiffness执行效果
DampingRatioHighBouncyStiffnessHighhigh_high
DampingRatioHighBouncyStiffnessMediumhigh_medium
DampingRatioHighBouncyStiffnessMediumLowhigh_mediumlow
DampingRatioHighBouncyStiffnessLowhigh_low
DampingRatioLowBouncyStiffnessLowlow_low
DampingRatioLowBouncyStiffnessMediumLowlow_mediumlow
DampingRatioLowBouncyStiffnessMediumlow_medium
DampingRatioLowBouncyStiffnessHighlow_high

需要注意的是:DampingRatioHighBouncy从数值上看其实是较小的值。从上面的动图中也可以看出:阻尼比越小,动画回弹的次数就越多,刚性越大,从起始值到目标值执行的速度就越快。

tween

这个动画类型可以指定动画的执行时间和曲线,使用这个动画类型通过durationMillis参数指定动画的执行时间,通过easing参数指定动画的执行曲线,也就是以什么样的曲线在起始值和目标值之间变化,同时还可以通过delayMillis指定延迟多长时间启动动画,下面的代码演示了这个动画的使用方法:

    val size by animateDpAsState(
        targetValue = if (scale) 100.dp else 50.dp,
        animationSpec = tween(durationMillis = 2000,easing = LinearOutSlowInEasing)
    )
    Surface(
        color = Color.Blue,
        contentColor = Color.White,
        shape = CircleShape,
        modifier = Modifier.size(size)
    ) {
        Text(text = "演示\n动画", modifier = Modifier.wrapContentSize(Alignment.Center))
    }    

在上面的代码中我们指定了动画的执行时间为2秒,同时设置了执行曲线为LinearOutSlowInEasing,运行上面的程序可以看到下面的效果:

tween简单使用