Jetpack Compose中的动画
Posted 川峰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack Compose中的动画相关的知识,希望对你有一定的参考价值。
Jetpack Compose中没有沿用android原有的View动画和属性动画,而是新创建了一套全新的动画系统API,这是理所当然的,因为旧的动画系统主要是基于View体系的,而Compose中需要针对的是Composable可组合函数进行处理,那么势必要创造一套新的玩具出来,同时,这也无疑增加了开发者的学习成本。
乍一看Jetpack Compose中的动画Api,尼玛是真的多呀,我C了,简直令人眼花缭乱、云里雾里、天马行空、小兔乱撞、手脚慌乱、头冒虚汗、四肢抓狂、不知所措呀 。。。😭
但是我们可以对其进行分一下类,如果按照使用的方便程度划分,大概可以分为两大类:高级动画API和低级动画API(这里类比高级开发语言的分类,并不是指效果多高级)。
其中高级动画API使用比较简单方便,封装度高,更加适用于日常业务开发,而低级动画API则使用起来较为麻烦,因为其配置项或流程较多,但是却更加灵活,能对动画效果做出更加精细的控制,适合自定义要求度较高的业务场景。
我们还可以按照功能类型进行一个大概的分类,也就是上图中的划分,这里再用表格归类一下:
功能需求点 | 可能符合的API类型 |
---|---|
单个组件的显示隐藏转场动画 每个子组件需要不同的入场/出场效果 | AnimatedVisibility |
根据组件内容状态变化的动画(数据、尺寸等) 不同组件间的切换动画 | AnimatedContent Modifier.animateContentSize |
单纯的淡入淡出动画 | Crossfade |
根据数据估值状态自动执行连续动画 基于单个数据值的状态变化执行动画 基于自定义数据类型进行估值动画 指定每一帧/每一时刻的动画状态 替代传统属性动画的方案 | animateXXXAsState |
根据不同状态同时管理和运行多个动画 进入界面时自动执行一次动画 监听动画状态 替代传统View动画中的AnimationSet的方案。 | updateTransition MutableTransitionState |
永不停止、无限循环的动画 | rememberInfiniteTransition |
更加底层的低级动画API 可高度自由定制的估值属性动画 需要在协程中执行的动画 需要控制一些动画并行执行 | Animatable |
更加底层的低级动画API 需要手动精确控制动画的时间 手势动画,fling衰减动画 | TargetBasedAnimation DecayAnimation |
高级动画API
AnimatedVisibility
AnimatedVisibility主要用于页面显示状态的动画,即显示/隐藏的过渡动画,或者入场/离场动画。
可以使用 + 运算符组合多个 EnterTransition 或 ExitTransition 对象,并且每个对象都接受可选参数以自定义其行为。
@Composable
fun AnimatedVisibilityExample()
var visible by remember mutableStateOf(true)
val density = LocalDensity.current
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.TopCenter)
AnimatedVisibility(
visible = visible,
enter = slideInVertically with(density) -40.dp.roundToPx() // 从顶部 40dp 的地方开始滑入
+ expandVertically(expandFrom = Alignment.Top) // 从顶部开始展开
+ fadeIn(initialAlpha = 0.3f), // 从初始透明度 0.3f 开始淡入
exit = slideOutVertically() + shrinkVertically() + fadeOut()
)
Text("Hello",
Modifier.background(Color.Green).fillMaxWidth().height(200.dp)
.wrapContentWidth(Alignment.CenterHorizontally),
fontSize = 20.sp
)
Button(
onClick = visible = !visible ,
modifier = Modifier.padding(top = 200.dp)
)
Text(text = if(visible) "隐藏" else "显示")
运行效果:
默认情况下 EnterTransition 是 fadeIn() + expandIn() 的效果,而 ExitTransition 是 shrinkOut() + fadeOut() 的效果, Compose额外提供了RowScope.AnimatedVisibility和ColumnScope.AnimatedVisibility两个扩展方法, 当我们在Row或Column中调用时,该组件的默认动画效果会根据父容器的布局特征进行调整,比如在Row中EnterTransition默认是fadeIn + expandHorizontally组合,而在Column中EnterTransition默认是fadeIn + expandVertically组合方案。
EnterTransition 和 ExitTransition 动画分类效果示例:
EnterTransition | ExitTransition |
---|---|
FadeIn | FadeOut |
slideIn | slideOut |
slideInHorizontally | slideOutHorizontally |
slideInVertically | slideOutVertically |
scaleIn | scaleOut |
expandIn | shrinkOut |
expandHorizontally | shrinkHorizontally |
expandVertically | shrinkVertically |
为子项添加进入和退出动画效果
AnimatedVisibility 中的内容(直接或间接子项)可以使用 Modifier.animateEnterExit 修饰符为每个子项指定不同的动画行为。
其中每个子项的视觉效果均由 AnimatedVisibility 可组合项中指定的动画与子项自己的进入和退出动画构成。
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AnimatedVisibilityExample3()
var visible by remember mutableStateOf(true)
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.TopCenter)
AnimatedVisibility(visible = visible, enter = fadeIn(), exit = fadeOut())
// 外层Box组件淡入淡出进出屏幕
Box(Modifier.fillMaxSize().background(Color.DarkGray))
Box(Modifier.align(Alignment.Center)
.sizeIn(minWidth = 256.dp, minHeight = 64.dp).background(Color.Green)
.animateEnterExit(enter = slideInVertically(), exit = slideOutVertically())
)
Text(text = "内层Box组件滑动进出屏幕", Modifier.align(Alignment.Center))
Box(Modifier.padding(top = 150.dp).align(Alignment.Center)
.sizeIn(minWidth = 256.dp, minHeight = 64.dp).background(Color.Cyan)
.animateEnterExit(enter = scaleIn(), exit = scaleOut())
)
Text(text = "内层层Box组件缩放进出屏幕", Modifier.align(Alignment.Center))
Button(
onClick = visible = !visible ,
modifier = Modifier.padding(top = 50.dp)
)
Text(text = if(visible) "隐藏" else "显示")
运行效果:
有时我们希望 AnimatedVisibility 内的每个子组件有不同的过渡动画,此时请在 AnimatedVisibility 可组合项中指定 EnterTransition.None 和 ExitTransition.None,即完全不应用任何动画,这样子项就可以通过 Modifier.animateEnterExit 拥有各自的不同动画了。
自定义Enter/Exit动画
如果想在内置进入和退出动画之外添加自定义动画效果,请在 AnimatedVisibilityScope 内设置 transition, 添加到 Transition 实例的所有动画状态都将与 AnimatedVisibility 的进入和退出动画同时运行。
AnimatedVisibility 会等到 Transition 中的所有动画都完成后再移除其内容。对于独立于 Transition(例如使用 animate*AsState)创建的退出动画,AnimatedVisibility 将无法解释这些动画,因此可能会在完成之前移除内容可组合项。
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AnimatedVisibilityExample4()
var visible by remember mutableStateOf(true)
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.TopCenter)
AnimatedVisibility(visible = visible, enter = scaleIn(), exit = scaleOut())
// 使用 AnimatedVisibilityScope#transition 添加自定义的动画与AnimatedVisibility同时执行
val background by transition.animateColor(label = "backgroundTransition") state ->
if (state == EnterExitState.Visible) Color.Blue else Color.Green
Box(modifier = Modifier.size(100.dp).background(background))
Button(
onClick = visible = !visible ,
modifier = Modifier.padding(top = 120.dp)
)
Text(text = if(visible) "隐藏" else "显示")
运行效果:
AnimatedContent
AnimatedContent 可组合项会在内容根据目标状态发生变化时,为内容添加动画效果。
与 AnimatedVisibility 的区别是: AnimatedVisibility用来添加组件自身的入场/离场动画,而AnimatedContent是实现不同组件间的切换动画
AnimatedContent接收一个targetState和一个content,content 是基于 targetState 创建的Composable,当targetState变化时,content的内容也会随之变化。AnimatedContent内部维护着targetState到content的映射表,查找 targetState新旧值对应的content后,在content发生重组时附加动画效果。
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AnimatedContentExample()
Column
var count by remember mutableStateOf(0)
Button(onClick = count++ ) Text("Add")
AnimatedContent(targetState = count) targetCount ->
// 这里要使用lambda的参数 `targetCount`, 而不是 `count`,否则将没有意义(API 会将此值用作键,以标识当前显示的内容)
Text(text = "Count: $targetCount", Modifier.background(Color.Green), fontSize = 25.sp)
运行效果:
ContentTransform
AnimatedContent默认是淡入淡出效果,可以为 transitionSpec 参数指定 ContentTransform 对象,以自定义此动画行为。
可以使用 with infix 函数来组合 EnterTransition 与 ExitTransition,以创建 ContentTransform
@ExperimentalAnimationApi
infix fun EnterTransition.with(exit: ExitTransition) = ContentTransform(this, exit)
ContentTransform本质上就是currentContent(initial) 的 ExitTransition与targetContent的EnterTransition组合, EnterTransition 定义了目标内容应如何显示,ExitTransition 则定义了初始内容应如何消失。
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AnimatedContentExample2()
Column
var count by remember mutableStateOf(0)
Button(onClick = count++ ) Text("Add")
AnimatedContent(
targetState = count,
transitionSpec =
// 从右往左切换,并伴随淡入淡出效果(initialOffsetX = width, targetOffsetX = -width)
slideInHorizontallywidth -> width + fadeIn() with
slideOutHorizontallywidth -> -width + fadeOut()
) targetCount ->
Text(text = "Count: $targetCount", Modifier.background(Color.Green), fontSize = 25.sp)
运行效果:
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AnimatedContentExample3()
Column(horizontalAlignment = Alignment.CenterHorizontally)
var count by remember mutableStateOf(0)
Button(onClick = count++ ) Text("Add")
val animationSpec = tween<IntOffset>(200)
val animationSpec2 = tween<Float>(200)
AnimatedContent(
targetState = count,
transitionSpec =
slideInVertically(animationSpec) height -> height + fadeIn(animationSpec2) with
slideOutVertically(animationSpec) height -> height + fadeOut(animationSpec2)
) targetCount ->
Text(text = "$targetCount", fontSize = 40.sp)
运行效果:
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AnimatedContentExample4()
Column
var count by remember mutableStateOf(0)
Row(horizontalArrangement = Arrangement.SpaceAround)
Button(onClick = count-- ) Text("Minus")
Spacer(Modifier.size(60.dp))
Button(onClick = count++ ) Text("Plus ")
Spacer(Modifier.size(20.dp))
AnimatedContent(
targetState = count,
transitionSpec =
if (targetState > initialState)
// 如果targetState更大,则从下往上切换并伴随淡入淡出效果
slideInVertically height -> height + fadeIn() with
slideOutVertically height -> -height + fadeOut()
else
// 如果targetState更小,则从上往下切换并伴随淡入淡出效果
slideInVertically height -> -height + fadeIn() with
slideOutVertically height -> height + fadeOut()
.using(
// Disable clipping since the faded slide-in/out should be displayed out of bounds.
SizeTransform(clip = false)
)
) targetCount ->
Text(text = "Count: $targetCount", Modifier.background(Color.Green), fontSize = 25.sp)
运行效果:
slideIntoContainer 和 slideOutOfContainer
除了可用于 AnimatedVisibility 的所有 EnterTransition 和 ExitTransition 函数之外,AnimatedContent 还提供了 slideIntoContainer 和 slideOutOfContainer。这些是 slideInHorizontally/Vertically 和 slideOutHorizontally/Vertically 的便捷替代方案,它们可根据初始内容的大小和 AnimatedContent 内容的目标内容来计算滑动距离。(官方例子可见:slideIntoContainer)
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun SlideIntoContainerSample()
val transitionSpec: AnimatedContentScope<Int>.() -> ContentTransform =
if (initialState < targetState)
slideIntoContainer(towards = AnimatedContentScope.SlideDirection.Up) + fadeIn() with
slideOutOfContainer(towards = AnimatedContentScope.SlideDirection.Up) + fadeOut()
else
slideIntoContainer(towards = AnimatedContentScope.SlideDirection.Down) + fadeIn() with
slideOutOfContainer(towards = AnimatedContentScope.SlideDirection.Down) + fadeOut()
.apply
// 这里可指定目标内容的 zIndex ,值越大越上层,值越小越下层
// targetContentZIndex = when (targetState)
// NestedMenuState.Level1 -> 1f
// NestedMenuState.Level2 -> 2f
// NestedMenuState.Level3 -> 3f
//
.using(SizeTransform(clip = false))
Column
var count by remember mutableStateOf(0)
Row(horizontalArrangement = Arrangement.SpaceAround)
Button(onClick = count-- ) Text("Minus")
Spacer(Modifier.size(60.dp))
Button(onClick = count++ ) Text("Plus ")
Spacer(Modifier.size(20.dp))
AnimatedContent(
targetState = count,
transitionSpec = transitionSpec,
) targetCount ->
Text(text = "Count: $targetCount", Modifier.background(Color.Green), fontSize = 25.sp)
运行效果:同上一个例子一样
SizeTransform
SizeTransform 定义了大小应如何在初始内容与目标内容之间添加动画效果。在创建动画时,您可以访问初始大小和目标大小。 SizeTransform 还可控制在动画播放期间是否应将内容裁剪为组件大小。
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class)
@Composable
fun SizeTransformAnimatedContentSample()
var expanded by remember mutableStateOf(false)
Surface(
color = MaterialTheme.colors.primary,
onClick = expanded = !expanded ,
modifier = Modifier.padding(10.dp).onSizeChanged
)
AnimatedContent以上是关于Jetpack Compose中的动画的主要内容,如果未能解决你的问题,请参考以下文章
Jetpack Compose 列表的展开与收起颜色动画效果