> 文档中心 > 一文了解如何使用Compose动画~

一文了解如何使用Compose动画~


前言

断断续续学习Compose已经快有一个月了,在编写“正在加载框”这个效果时,遇到了动画相关的问题。当然Lottie框架也已经支持Compose了,但学习和了解Compose动画的基础知识还是很有必要的,本篇文章就来一起了解Compose动画的实现~

动画的种类

动画的种类就很多,根据使用场景有AnimationVisibility、rememberInfiniteTransition、Animation等。如果你想知道在你的需求场景中需要使用什么动画,可以参照官方的这张流程指示图。

AnimationVisibility

AnimationVisibility可以为布局中的内容变化添加动画效果,比如内容的显示、隐藏等效果。

我们用AnimationVisibility来实现控制图片的显示与隐藏,首先定义变量用来控制图片是否显示,代码如下所示

var visible by remember {  mutableStateOf(false)                     }

默认不显示,使用AnimatedVisibility函数将图片组件包裹

AnimatedVisibility(    visible = visible) {    Image(        painter = painterResource(id = R.mipmap.photon),        contentDescription = null    )}

添加一个Button,用于控制图片的显示与隐藏,代码如下所示:

Button(modifier = Modifier.padding(vertical = 5.dp), onClick = {    visible = !visible}) {    val value = if (visible) {        "隐藏"    } else {        "显示"    }    Text(text = value)}

运行程序,效果图如下所示。

从效果图中可以看出,图片出现时有自上到下弹入的效果,图片消失时有自下到上弹出的效果。那么这个动画效果是如何实现的呢?AnimatedVisibility函数的源码如下所示:

@Composablefun ColumnScope.AnimatedVisibility(    visible: Boolean,    modifier: Modifier = Modifier,    enter: EnterTransition = fadeIn() + expandVertically(),    exit: ExitTransition = fadeOut() + shrinkVertically(),    label: String = "AnimatedVisibility",    content: @Composable AnimatedVisibilityScope.() -> Unit)

visible参数用于控制是否显示,enter、exit参数分别用来设置动画进入和退出的效果。这里设置了默认效果。在EnterTransition这个密封类中定义了fadeIn、fadeOut、slideIn、slideOut 以及scaleIn、scaleOut动画效果。动画效果是可以自由组合的,如上源码所示为动画进入设置了fadeIn+expandVertically的组合效果。

接着我们自己设置动画效果为scaleIn和scaleOut,修改代码如下所示:

AnimatedVisibility(    visible = visible,    enter = scaleIn(),    exit = scaleOut()) {    Image(        painter = painterResource(id = R.mipmap.photon),        contentDescription = null    )}

运行程序,效果图如下所示。

从效果图可以看出scaleIn和scaleOut的效果为从中间扩散和向中间聚集的效果。更多的效果显示,读者可自行尝试。

AnimatedContent

AnimatedContent可以设定目标内容,当目标内容变化时,为内容添加动画效果。以点击按钮改变data变量值为例,代码如下所示:

Column() {    var data by remember { mutableStateOf(0) }    Button(onClick = { data++ }) {        Text("添加数据")    }    AnimatedContent(targetState = data) {        Text(text = "数值:${data}")    }}

 运行程序,效果图如下所示。

从效果图中可以看出,在数值变化的时候,会有淡入淡出的效果。AnimatedContent函数源码如下所示:

@ExperimentalAnimationApi@Composablefun  AnimatedContent(    targetState: S,    modifier: Modifier = Modifier,    transitionSpec: AnimatedContentScope.() -> ContentTransform = {        fadeIn(animationSpec = tween(220, delayMillis = 90)) +            scaleIn(initialScale = 0.92f, animationSpec = tween(220, delayMillis = 90)) with            fadeOut(animationSpec = tween(90))    },    contentAlignment: Alignment = Alignment.TopStart,    content: @Composable() AnimatedVisibilityScope.(targetState: S) -> Unit)

targetState参数指定目标, transitionSpec参数用来指定动画行为。编写代码如下所示:

AnimatedContent(targetState = data,    transitionSpec = {        (scaleIn() with scaleOut()).using(SizeTransform(false))    }) {    Text(text = "数值:${data}")}

 我们先来看,代码为什么可以这样写,transitionSpec参与是ContentTransform对象,我们来看ContentTransform的源码,如下所示:

@ExperimentalAnimationApiclass ContentTransform(    val targetContentEnter: EnterTransition,    val initialContentExit: ExitTransition,    targetContentZIndex: Float = 0f,    sizeTransform: SizeTransform? = SizeTransform())

可以看到参数指定了进入动画、退出动画 这一点与AnimatedVisibility的使用是相同的。

sizeTransForm参数定义了在初始内容与目标内容之间添加动画效果,进入、退出动画可以使用with函数来组合,sizeTransform参数提供了using扩展函数来使用,代码如下所示:

@ExperimentalAnimationApiinfix fun ContentTransform.using(sizeTransform: SizeTransform?) = this.apply {    this.sizeTransform = sizeTransform}

运行程序,效果图如下所示。

Crossfade与animateContentSize

animateContentSize可以在尺寸大小改变的时候添加动画,Crossfade是淡入淡出动画,可用于视图切换等操作。首先来看animateContentSize的使用。

animateContentSize

编写一个示例,包含一个Edittext和一个TextView,TextView中实时显示Edittext的输入内容,代码如下所示:

Column() {    var message by remember { mutableStateOf("") }    TextField(value = message, onValueChange = { message = it })    Box(        modifier = Modifier            .background(Color.Red)            .animateContentSize()    ) {        Text(text = message)    }}

 为了便于观察效果,我们这里设置背景为红色,运行程序,效果图如下所示。

有一种丝滑般的感觉,一起纵享丝滑吧~

Crossfade

Crossfade可用于两个视图间的切换动画,编写代码:按钮控制当前页面显示Screen1页面或Screen2页面,为了便于区分,两个页面分别设置背景为蓝色和绿色。具体代码此处就省略了。

页面切换部分代码如下所示:

var flag by remember {    mutableStateOf(false)}Column() {    Crossfade(targetState = flag, animationSpec = tween(3000)) {        when (it) {            false -> Screen1()            true -> Screen2()        }    }    Button(onClick = { flag = !flag }) {        Text(text = "视图切换")    }}               

为了便于观察效果,此处为动画设置tween的间隔时间为3秒,运行程序,效果图如下所示:

Crossfade函数源码如下所示:

@OptIn(ExperimentalAnimationApi::class)@Composablefun  Crossfade(    targetState: T,    modifier: Modifier = Modifier,    animationSpec: FiniteAnimationSpec = tween(),    content: @Composable (T) -> Unit)            

 animationSpec参数是FiniteAnimationSpec类型的参数,实现类有TweenSpec、SpringSpec等,默认值是tween,tween是一个可配置的曲线动画,源码如下所示:

@Stablefun  tween(    durationMillis: Int = DefaultDurationMillis,    delayMillis: Int = 0,    easing: Easing = FastOutSlowInEasing): TweenSpec = TweenSpec(durationMillis, delayMillis, easing)           

我们也可以设置spring等效果,这里读者可自行尝试。

其他

除此之外,还有animate*AsState、rememberInfiniteTransition等低级别的动画API,更多用法,这里不再一一讲解了。回到刚开始前言的问题,如何实现 一个正在加载的动画呢?

这里我们使用rememberInfiniteTransition来定义一个无限加载的动画,并通过infiniteRepeatable来制定动画规范。最后一起来看一下,我的Compose开源项目中所实现的加载框的效果吧~

写在最后

近期越来越感觉,学无止境,需要学习的东西太多太多~ ,期待我们下篇文章 再见~