写点什么

高效动画实现原理 -Jetpack Compose 初探索

发布于: 刚刚

一、简介


Jetpack Compose 是 Google 推出的用于构建原生界面的新 Android 工具包,它可简化并加快 Android 上的界面开发。Jetpack Compose 是一个声明式的 UI 框架,随着该框架的推出,标志着 Android 开始全面拥抱声明式 UI 开发。Jetpack Compose 存在很多优点:代码更加简洁直观、应用开发效率显著提升、Kotlin API 功能直观、预览工具强大等。


二、开发环境


为了获得更好的开发体验,笔者这里使用的是 Android Studio Canary 版本,这样可以无需配置一些设置和依赖。(下载地址


打开工程,新建 Empty Compose activity 模版,需要注意的是根目录下的 build.gradle,相关的依赖 com.android.tools.build 和 org.jetbrains.kotlin 版本需要对应,否则可能出现出错的情形,这里使用的是:

dependencies {	classpath "com.android.tools.build:gradle:7.0.0-alpha15"	classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30"}
复制代码


这样就完成了项目的新建。


三、Jetpack Compose 动画


Jetpack Compose 提供了一些功能强大且可扩展的 API,可用于在应用界面中轻松实现各种动画效果。下文将会对 Jetpack Compose Animations 的常用方法进行介绍。


3.1 状态驱动动画:State


Jetpack Compose 动画是通过对状态的监听,即监听状态值的变化,使 UI 能实现自动更新。可组合函数可以使用 remember 或者 mutableStateOf 监听状态值的变化。如果状态值是不变的,remember 函数会在每次重新组合中保持该值;如果状态是可变的,它会在值发生变化的时候触发重组,mutableStateOf 将得到一个 MutableState 对象,它是一个可观察类型。


这种重组是创建状态驱动动画的关键。利用重组,它们会在可组合组件的状态发生任何变化时被触发。Compose 动画是由 State 驱动的,动画相关的 API 也较容易上手,能比较容易创造出漂亮的声明式动画。


3.2 可见性动画: AnimatedVisibility


首先看下函数定义:

@ExperimentalAnimationApi@Composablefun AnimatedVisibility(    visible: Boolean,    modifier: Modifier = Modifier,    enter: EnterTransition = fadeIn() + expandIn(),    exit: ExitTransition = shrinkOut() + fadeOut(),    initiallyVisible: Boolean = visible,    content: @Composable () -> Unit) {    AnimatedVisibilityImpl(visible, modifier, enter, exit, initiallyVisible, content)}
复制代码


可以看出默认的动画是淡入放大、淡出收缩,实际中通过传入不同函数实现各种动效。


随着可见值的变化,AnimatedVisibility 可为其内容的出现和消失设置动画。如下代码,可以通过点击 Button,控制图片的出现和消失。

@Composablefun AinmationDemo() {
//AnimatedVisibility 可见动画 var visible by remember { mutableStateOf(true) }
Column( Modifier .fillMaxWidth() .fillMaxHeight(), Arrangement.Top, Alignment.CenterHorizontally ) { Button( onClick = { visible = !visible } ) { Text(text = if (visible) "Hide" else "Show") }
Spacer(Modifier.height(16.dp))
AnimatedVisibility( visible = visible, enter = slideInVertically() + fadeIn(), exit = slideOutVertically() + fadeOut() ) { Image( painter = painterResource(id = R.drawable.pikaqiu), contentDescription = null, Modifier.fillMaxSize() ) } }}
复制代码


通过监听 visible 的变化,可实现图片的可见性动画,效果如小图所示;



3.3 布局大小动画:AnimateContentSize


先看下函数的定义:

fun Modifier.animateContentSize(    animationSpec: FiniteAnimationSpec<IntSize> = spring(),    finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null)
复制代码


可以为布局大小动画设置动画速度和监听值。


由函数的定义可以看出这个函数本质上就 Modefier 的一个扩展函数。可以通过变量 size 监听状态变化实现布局大小的动画效果,代码如下:

//放大缩小动画 animateContentSize    var size by remember { mutableStateOf(Size(300F, 300F)) }
Column( Modifier .fillMaxWidth() .fillMaxHeight(), Arrangement.Top, Alignment.CenterHorizontally ) { Spacer(Modifier.height(16.dp))
Button( onClick = { size = if (size.height == 300F) { Size(500F, 500F) } else { Size(300F, 300F) } } ) { Text(if (size.height == 300F) "Shrink" else "Expand") } Spacer(Modifier.height(16.dp))
Box( Modifier .animateContentSize() ) { Image( painter = painterResource(id = R.drawable.pikaqiu), contentDescription = null, Modifier .animateContentSize() .size(size = size.height.dp) ) }} //放大缩小动画 animateContentSize var size by remember { mutableStateOf(Size(300F, 300F)) }​ Column( Modifier .fillMaxWidth() .fillMaxHeight(), Arrangement.Top, Alignment.CenterHorizontally ) { Spacer(Modifier.height(16.dp))​ Button( onClick = { size = if (size.height == 300F) { Size(500F, 500F) } else { Size(300F, 300F) } } ) { Text(if (size.height == 300F) "Shrink" else "Expand") } Spacer(Modifier.height(16.dp))​ Box( Modifier .animateContentSize() ) { Image( painter = painterResource(id = R.drawable.pikaqiu), contentDescription = null, Modifier .animateContentSize() .size(size = size.height.dp) ) }}
复制代码


通过 Button 的点击,监听 size 值的变化,利用 animateContentSize()实现动画效果,具体动效如下图所示:



3.4 布局切换动画: Crossfade


Crossfade 可以通过监听状态值的变化,使用淡入淡出的动画在两个布局之间添加动画效果,函数自身就是一个 Composable,代码如下:

//Crossfade 淡入淡出动画    var fadeStatus by remember { mutableStateOf(true) }
Column( Modifier .fillMaxWidth() .fillMaxHeight(), Arrangement.Top, Alignment.CenterHorizontally ) { Button( onClick = { fadeStatus = !fadeStatus } ) { Text(text = if (fadeStatus) "Fade In" else "Fade Out") }
Spacer(Modifier.height(16.dp))
Crossfade(targetState = fadeStatus, animationSpec = tween(3000)) { screen -> when (screen) { true -> Image( painter = painterResource(id = R.drawable.pikaqiu), contentDescription = null, Modifier .animateContentSize() .size(300.dp) ) false -> Image( painter = painterResource(id = R.drawable.pikaqiu2), contentDescription = null, Modifier .animateContentSize() .size(300.dp) ) } }
}
复制代码


同样通过监听 fadeStatus 的值,实现布局切换的动画,具体的动效如图所示:



3.5 单个值动画:animate*AsState


为单个值添加动画效果。只需提供结束值(或目标值),该 API 就会从当前值开始向指定值播放动画。


Jetpack Compose 提供了很多内置函数,可以为不同类型的数据制作动画,例如:animateColorAsState、animateDpAsState、animateOffsetAsState 等,这里将介绍下 animateFooAsState 的使用,代码如下:

//animate*AsState 单个值添加动画    var transparent by remember { mutableStateOf(true) }    val alpha: Float by animateFloatAsState(if (transparent) 1f else 0.5f)
Column( Modifier .fillMaxWidth() .fillMaxHeight(), Arrangement.Top, Alignment.CenterHorizontally ) { Button( onClick = { transparent = !transparent } ) { Text(if (transparent) "Light" else "Dark") }
Spacer(Modifier.height(16.dp))
Box {
Image( painter = painterResource(id = R.drawable.pikaqiu), contentDescription = null, Modifier .animateContentSize() .graphicsLayer(alpha = alpha) .size(300.dp) ) }}
复制代码


动画效果如下图所示:



3.6 组合动画:updateTransition


Transition 可同时追踪一个或多个动画,并在多个状态之间同步这些动画。具体的代码如下:

var imagePosition by remember { mutableStateOf(ImagePosition.TopLeft) }
Column( Modifier .fillMaxWidth() .fillMaxHeight(), Arrangement.Top, Alignment.CenterHorizontally ) { Spacer(Modifier.height(16.dp))
val transition = updateTransition(targetState = imagePosition, label = "") val boxOffset by transition.animateOffset(label = "") { position -> when (position) { ImagePosition.TopLeft -> Offset(-60F, 0F) ImagePosition.BottomRight -> Offset(60F, 120F) ImagePosition.TopRight -> Offset(60F, 0F) ImagePosition.BottomLeft -> Offset(-60F, 120F) } } Button(onClick = { imagePosition = ChangePosition(imagePosition) }) { Text("Change position") } Box {
Image( painter = painterResource(id = R.drawable.pikaqiu), contentDescription = null, Modifier .offset(boxOffset.x.dp, boxOffset.y.dp) .animateContentSize() .size(300.dp) ) }}
复制代码


其中,ImagePosition、ChangePosition 分别为定义的枚举类、自定义函数。

enum class ImagePosition {    TopRight,    TopLeft,    BottomRight,    BottomLeft}
fun ChangePosition(position: ImagePosition) = when (position) { ImagePosition.TopLeft -> ImagePosition.BottomRight ImagePosition.BottomRight -> ImagePosition.TopRight ImagePosition.TopRight -> ImagePosition.BottomLeft ImagePosition.BottomLeft -> ImagePosition.TopLeft }
复制代码


动画的如下图所示:



四、结语


Jetpack Compose 已将动画简化到只需在我们的可组合函数中创建声明性代码的程度,只需编写希望 UI 动画的方式,其余部分由 Compose 管理。最后,这也是是 Jetpack Compose 的主要目标:创建一个声明式 UI 工具包来加速应用程序开发并提高代码可读性和逻辑性。


Jetpack Compose 提供的声明式 UI 工具包,能做到使用更少的代码实现更多的功能,且代码的可读性和逻辑性也大大提高了。


作者:vivo 互联网游戏客户端团队-Ke Jie

发布于: 刚刚阅读数: 2
用户头像

官方公众号:vivo互联网技术,ID:vivoVMIC 2020.07.10 加入

分享 vivo 互联网技术干货与沙龙活动,推荐最新行业动态与热门会议。

评论

发布
暂无评论
高效动画实现原理-Jetpack Compose 初探索