Chinaunix首页 | 论坛 | 博客
  • 博客访问: 915415
  • 博文数量: 253
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2609
  • 用 户 组: 普通用户
  • 注册时间: 2019-03-08 17:29
个人简介

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

文章分类

全部博文(253)

文章存档

2022年(60)

2021年(81)

2020年(83)

2019年(29)

我的朋友

分类: Android平台

2021-10-19 10:50:12

一、简介

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版本需要对应,否则可能出现出错的情形,这里使用的是:

  1. dependencies {
  2.     classpath "com.android.tools.build:gradle:7.0.0-alpha15"
  3.     classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30"
  4. }
这样就完成了项目的新建。

三、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

首先看下函数定义:

  1. @ExperimentalAnimationApi
  2. @Composable
  3. fun AnimatedVisibility(
  4.     visible: Boolean,
  5.     modifier: Modifier = Modifier,
  6.     enter: EnterTransition = fadeIn() + expandIn(),
  7.     exit: ExitTransition = shrinkOut() + fadeOut(),
  8.     initiallyVisible: Boolean = visible,
  9.     content: @Composable () -> Unit
  10. ) {
  11.     AnimatedVisibilityImpl(visible, modifier, enter, exit, initiallyVisible, content)
  12. }
可以看出默认的动画是淡入放大、淡出收缩,实际中通过传入不同函数实现各种动效。
随着可见值的变化,AnimatedVisibility可为其内容的出现和消失设置动画。如下代码,可以通过点击Button,控制图片的出现和消失。

  1. @Composable
  2. fun AinmationDemo() {

  3.     //AnimatedVisibility 可见动画
  4.     var visible by remember { mutableStateOf(true) }

  5.     Column(
  6.         Modifier
  7.             .fillMaxWidth()
  8.             .fillMaxHeight(),
  9.         Arrangement.Top,
  10.         Alignment.CenterHorizontally
  11.     ) {
  12.         Button(
  13.             onClick = { visible = !visible }
  14.         ) {
  15.             Text(text = if (visible) "Hide" else "Show")
  16.         }

  17.         Spacer(Modifier.height(16.dp))

  18.         AnimatedVisibility(
  19.             visible = visible,
  20.             enter = slideInVertically() + fadeIn(),
  21.             exit = slideOutVertically() + fadeOut()
  22.         ) {
  23.             Image(
  24.                 painter = painterResource(id = R.drawable.pikaqiu),
  25.                 contentDescription = null,
  26.                 Modifier.fillMaxSize()
  27.             )
  28.         }
  29.     }
  30. }
通过监听visible的变化,可实现图片的可见性动画,效果如小图所示;

3.3 布局大小动画:AnimateContentSize

先看下函数的定义:

  1. fun Modifier.animateContentSize(
  2.     animationSpec: FiniteAnimationSpec<IntSize> = spring(),
  3.     finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null
  4. )
可以为布局大小动画设置动画速度和监听值。
由函数的定义可以看出这个函数本质上就Modefier的一个扩展函数。可以通过变量size监听状态变化实现布局大小的动画效果,代码如下:

  1. //放大缩小动画 animateContentSize
  2.     var size by remember { mutableStateOf(Size(300F, 300F)) }

  3.     Column(
  4.         Modifier
  5.             .fillMaxWidth()
  6.             .fillMaxHeight(),
  7.         Arrangement.Top,
  8.         Alignment.CenterHorizontally
  9.     ) {
  10.         Spacer(Modifier.height(16.dp))

  11.         Button(
  12.             onClick = {
  13.                 size = if (size.height == 300F) {
  14.                     Size(500F, 500F)
  15.                 } else {
  16.                     Size(300F, 300F)
  17.                 }
  18.             }
  19.         ) {
  20.             Text(if (size.height == 300F) "Shrink" else "Expand")
  21.         }
  22.         Spacer(Modifier.height(16.dp))

  23.         Box(
  24.             Modifier
  25.                 .animateContentSize()
  26.         ) {
  27.             Image(
  28.                 painter = painterResource(id = R.drawable.pikaqiu),
  29.                 contentDescription = null,
  30.                 Modifier
  31.                     .animateContentSize()
  32.                     .size(size = size.height.dp)
  33.             )
  34.         }
  35. }
通过Button的点击,监听size值的变化,利用animateContentSize()实现动画效果,具体动效如下图所示:

3.4布局切换动画: Crossfade

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

  1. //Crossfade 淡入淡出动画
  2.     var fadeStatus by remember { mutableStateOf(true) }

  3.     Column(
  4.         Modifier
  5.             .fillMaxWidth()
  6.             .fillMaxHeight(),
  7.         Arrangement.Top,
  8.         Alignment.CenterHorizontally
  9.     ) {
  10.         Button(
  11.             onClick = { fadeStatus = !fadeStatus }
  12.         ) {
  13.             Text(text = if (fadeStatus) "Fade In" else "Fade Out")
  14.         }

  15.         Spacer(Modifier.height(16.dp))

  16.         Crossfade(targetState = fadeStatus, animationSpec = tween(3000)) { screen ->
  17.             when (screen) {
  18.                 true -> Image(
  19.                     painter = painterResource(id = R.drawable.pikaqiu),
  20.                     contentDescription = null,
  21.                     Modifier
  22.                         .animateContentSize()
  23.                         .size(300.dp)
  24.                 )
  25.                 false -> Image(
  26.                     painter = painterResource(id = R.drawable.pikaqiu2),
  27.                     contentDescription = null,
  28.                     Modifier
  29.                         .animateContentSize()
  30.                         .size(300.dp)
  31.                 )
  32.             }
  33.         }

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

3.5单个值动画:animate*AsState

为单个值添加动画效果。只需提供结束值(或目标值),该 API 就会从当前值开始向指定值播放动画。
Jetpack Compose 提供了很多内置函数,可以为不同类型的数据制作动画,例如:animateColorAsState、animateDpAsState、animateOffsetAsState等,这里将介绍下animateFooAsState的使用,代码如下:

  1. //animate*AsState 单个值添加动画
  2.     var transparent by remember { mutableStateOf(true) }
  3.     val alpha: Float by animateFloatAsState(if (transparent) 1f else 0.5f)

  4.     Column(
  5.         Modifier
  6.             .fillMaxWidth()
  7.             .fillMaxHeight(),
  8.         Arrangement.Top,
  9.         Alignment.CenterHorizontally
  10.     ) {
  11.         Button(
  12.             onClick = { transparent = !transparent }
  13.         ) {
  14.             Text(if (transparent) "Light" else "Dark")
  15.         }

  16.         Spacer(Modifier.height(16.dp))

  17.         Box {

  18.             Image(
  19.                 painter = painterResource(id = R.drawable.pikaqiu),
  20.                 contentDescription = null,
  21.                 Modifier
  22.                     .animateContentSize()
  23.                     .graphicsLayer(alpha = alpha)
  24.                     .size(300.dp)
  25.             )
  26.         }
  27. }
动画效果如下图所示:

3.6 组合动画:updateTransition

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

  1. var imagePosition by remember { mutableStateOf(ImagePosition.TopLeft) }

  2.     Column(
  3.         Modifier
  4.             .fillMaxWidth()
  5.             .fillMaxHeight(),
  6.         Arrangement.Top,
  7.         Alignment.CenterHorizontally
  8.     ) {
  9.         Spacer(Modifier.height(16.dp))

  10.         val transition = updateTransition(targetState = imagePosition, label = "")
  11.         val boxOffset by transition.animateOffset(label = "") { position ->
  12.             when (position) {
  13.                 ImagePosition.TopLeft -> Offset(-60F, 0F)
  14.                 ImagePosition.BottomRight -> Offset(60F, 120F)
  15.                 ImagePosition.TopRight -> Offset(60F, 0F)
  16.                 ImagePosition.BottomLeft -> Offset(-60F, 120F)
  17.             }
  18.         }
  19.         Button(onClick = {
  20.             imagePosition = ChangePosition(imagePosition)
  21.         }) {
  22.             Text("Change position")
  23.         }
  24.         Box {

  25.             Image(
  26.                 painter = painterResource(id = R.drawable.pikaqiu),
  27.                 contentDescription = null,
  28.                 Modifier
  29.                     .offset(boxOffset.x.dp, boxOffset.y.dp)
  30.                     .animateContentSize()
  31.                     .size(300.dp)
  32.             )
  33.         }
  34. }
其中,ImagePosition、ChangePosition分别为定义的枚举类、自定义函数。

  1. enum class ImagePosition {
  2.     TopRight,
  3.     TopLeft,
  4.     BottomRight,
  5.     BottomLeft
  6. }

  7. fun ChangePosition(position: ImagePosition) =
  8.     when (position) {
  9.         ImagePosition.TopLeft -> ImagePosition.BottomRight
  10.         ImagePosition.BottomRight -> ImagePosition.TopRight
  11.         ImagePosition.TopRight -> ImagePosition.BottomLeft
  12.         ImagePosition.BottomLeft -> ImagePosition.TopLeft
  13.     }
动画的如下图所示:

四、结语

Jetpack Compose 已将动画简化到只需在我们的可组合函数中创建声明性代码的程度,只需编写希望 UI 动画的方式,其余部分由 Compose 管理。最后,这也是是 Jetpack Compose 的主要目标:创建一个声明式 UI 工具包来加速应用程序开发并提高代码可读性和逻辑性。
Jetpack Compose提供的声明式UI工具包,能做到使用更少的代码实现更多的功能,且代码的可读性和逻辑性也大大提高了。
作者:vivo互联网游戏客户端团队-Ke Jie
阅读(739) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~