高效动画实现原理-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
@Composable
fun 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,控制图片的出现和消失。
@Composable
fun 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
高效动画实现原理-Jetpack Compose 初探索的更多相关文章
- Jetpack Compose What and Why, 6个问题
Jetpack Compose What and Why, 6个问题 1.这个技术出现的背景, 初衷, 要达到什么样的目标或是要解决什么样的问题. Jetpack Compose是什么? 它是一个声明 ...
- Jetpack Compose 1.0 终于要投入使用了!
前言 Jetpack Compose 是用于构建原生界面的「新款 Android 工具包」.2021 Google IO 大会上,Google宣布:「Jetpack Compose 1.0 即将面世」 ...
- JavaScript是如何工作的: CSS 和 JS 动画底层原理及如何优化它们的性能
摘要: 理解浏览器渲染. 原文:JavaScript是如何工作的: CSS 和 JS 动画底层原理及如何优化它们的性能 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 这是专门探索 J ...
- Android Kotlin Jetpack Compose UI框架 完全解析
前言 Q1的时候公司列了个培训计划,部分人作为讲师要上报培训课题.那时候刚从好几个Android项目里抽离出来,正好看到Jetpack发布了新玩意儿--Compose,我被它的快速实时打包给吸引住了, ...
- css3动画机制原理和实战
这段时间喜欢上css3动画效果了,关于这个每个人都有不同的看法,在我个人看来css3在做一些小页面的动画效果还是很好用的,一些简单的小动画要是用js的话,未免浪费. 要是做大一点的话最好js+css3 ...
- Android全新UI编程 - Jetpack Compose 超详细教程
1. 简介 Jetpack Compose是在2019Google i/O大会上发布的新的库.Compose库是用响应式编程的方式对View进行构建,可以用更少更直观的代码,更强大的功能,能提高开发速 ...
- 谷歌内部流出Jetpack Compose最全上手指南,含项目实战演练!
简介 Jetpack Compose是在2019Google i/O大会上发布的新的库.Compose库是用响应式编程的方式对View进行构建,可以用更少更直观的代码,更强大的功能,能提高开发速度. ...
- Jetpack Compose学习(3)——图标(Icon) 按钮(Button) 输入框(TextField) 的使用
原文地址: Jetpack Compose学习(3)--图标(Icon) 按钮(Button) 输入框(TextField) 的使用 | Stars-One的杂货小窝 本篇分别对常用的组件:图标(Ic ...
- Jetpack Compose学习(4)——Image(图片)使用及Coil图片异步加载库使用
原文地址 Jetpack Compose学习(4)--Image(图片)使用及Coil图片异步加载库使用 | Stars-One的杂货小窝 本篇讲解下关于Image的使用及使用Coil开源库异步加载网 ...
随机推荐
- i++ ++i 理解
i++与++i怎么运算,解决办法: i++规则是在表达式中先取i的值使用,后让i的值变化成加1后的值. 举例:如在式中 j=i++,他就会变成这样的两步,第一步先执行j=i,第二步执行i=i+1. + ...
- Django项目使用requirements.txt文件
1.生成requirements.txt pip freeze > requirements.txt 2.使用requirements.txt pip install -r requiremen ...
- C# 给PPT中的图表添加趋势线
本文内容分享通过C#程序代码给PPT文档中的图表添加数据趋势线的方法. 支持趋势线的图表类型包括二维面积图.条形图.柱形图.柱形图.股价图.xy (散点图) 和气泡图中:不能向三维.堆积.雷达图.饼图 ...
- Gram-Schmidt图像融合
遥感图像融合的定义是通过将多光谱低分辨率的图像和高分辨率的全色波段进行融合从而得到信息量更丰富的遥感图像.常用的遥感图像融合方法有Brovey\PCA\Gram-Schmidt方法.其中Gram-Sc ...
- tornado2.2安装教程
最近要用到vxworks 系统,所以避免不了要安装tornado 软件,进行相关的开发. 自己在安装过程中遇到了点点问题,这里记录下来,免得以后再次安装时遇到同样的问题. 1. 首先提供一个tor ...
- 如何在C#中打开和读取EXCEL文件
这篇文章向您展示如何在C#Windows Forms Application中使用ExcelDataReader,ExcelDataReader.DataSet打开和读取Excel文件.创建一个新的W ...
- HCNP Routing&Switching之路由过滤工具Filter-Policy
前文我们了解了路由控制技术中路由策略和路由匹配工具IP-Prefix相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15314262.html:今天我们 ...
- scrapy各种持久化存储的奇淫技巧
理论 磁盘文件: 基于终端指令 1)保证parse方法返回一个可迭代类型的对象(存储解析到的页面内容) 2)使用终端指令完成数据存储到指定磁盘文件中的操作,如:scrapy crawl 爬虫文件名称 ...
- 有备无患「GitHub 热点速览 v.21.38」
作者:HelloGitHub-小鱼干 数据库最重要的一个功能是容灾备份,备份不只是对数据库重要,对日常工作生活的我们一样重要,比如花了一个工作日写的代码没有备份(虽然可能只有 1 行-)总归是一个让人 ...
- phpstorm一直 updating indices刷新
解决方法: File-> 选中 Invalidate Caches/Restart ->选中 Invalidate Caches/Restart