一文了解如何使用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开源项目中所实现的加载框的效果吧~
写在最后
近期越来越感觉,学无止境,需要学习的东西太多太多~ ,期待我们下篇文章 再见~