Jetpack Compose 动画 API: AnimatedVisibility & AnimatedContent
Posted fundroid_方卓
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack Compose 动画 API: AnimatedVisibility & AnimatedContent相关的知识,希望对你有一定的参考价值。
Jetpack Compose 的动画相关的 API 数量众多,分为低级别 API 和高级别 API,其中高级别 API 便于使用者针对具体场景开箱即用 ,其中最常用的当属 AnimatedVisibility
和 AnimatedContent
这两个了。
1. AnimatedVisibility
AnimatedVisibility 顾名思义是用动画的方式改变 UI 元素的 Visibility,具体来说就是针对让其内部的 Composable 以动画的形式进入或退出屏幕
AnimatedVisibility 可以接受一个 visible
的 boolean 参数,控制内部元素的显示或隐藏
@ExperimentalAnimationApi
@Composable
fun AnimatedVisibilitySample()
var editable by remember mutableStateOf(true)
Column(modifier = Modifier.padding(8.dp))
Text(
text = "AnimatedVisibility",
style = MaterialTheme.typography.h6
)
AnimatedVisibility(visible = editable)
Surface(
color = Color.Yellow,
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.align(Alignment.CenterHorizontally)
.padding(8.dp)
)
Button(
onClick = editable = !editable ,
modifier = Modifier.fillMaxWidth()
)
Text(text = "Toggle")
看上面的例子,点击 Button 时,editable
发生变化, AnimatedVisibility 内的 Surface 显示或隐藏,同时伴随动画,效果如下:
MutableTransitionState
AnimatedVisibility 还有一个重载的方法, 接收一个 MutableTransitionState 类型的 visibleState
参数,上面的代码还可以写成下面这样
@ExperimentalAnimationApi
@Composable
fun AnimatedVisibilityStateSample()
val state = remember
MutableTransitionState(false).apply
targetState = true
Column(modifier = Modifier.padding(8.dp))
Text(
text = "AnimatedVisibilityState",
style = MaterialTheme.typography.h6
)
AnimatedVisibility(visibleState = state)
Surface(
color = Color.Yellow,
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.align(Alignment.CenterHorizontally)
.padding(8.dp)
)
Text(
text = state.getAnimationState().toString()
)
Button(
onClick = state.targetState = !state.currentState ,
modifier = Modifier.fillMaxWidth()
)
Text(text = "Toggle")
MutableTransitionState 的定义如下,主要有 currentState 和 targetState 两个成员组成,
class MutableTransitionState<S>(initialState: S)
var currentState: S by mutableStateOf(initialState)
internal set
var targetState: S by mutableStateOf(initialState)
val isIdle: Boolean
get() = (currentState == targetState) && !isRunning
internal var isRunning: Boolean by mutableStateOf(false)
前面的例子中,MutableTransitionState 的初始状态 currentState
为 false,目标状态 targetState
为 true,状态差可以实现 AnimatedVisibility 上屏时立即执行动画的效果。
MutableTransitionState 的 currentState
和 isIdle
可以暴露当前动画的执行状态给外面参考,我们可以定义一个枚举表示动画的状态
enum class AnimState
VISIBLE,
INVISIBLE,
APPEARING,
DISAPPEARING
fun MutableTransitionState<Boolean>.getAnimationState(): AnimState
return when
this.isIdle && this.currentState -> AnimState.VISIBLE
!this.isIdle && this.currentState -> AnimState.DISAPPEARING
this.isIdle && !this.currentState -> AnimState.INVISIBLE
else -> AnimState.APPEARING
EnterTransition & ExitTransition
AnimatedVisibility 可以通过 enter
和 exit
参数指定动画样式,enter 和 exit 分别制定一个 EnterTransition 和一个 ExitTransition 。
例如我们可以指定 fadeIn 和 fadeOut 的动画效果:
@ExperimentalAnimationApi
@Composable
fun AnimatedVisibilityEnterExitSample()
val state = remember
MutableTransitionState(false).apply
targetState = true
Column(modifier = Modifier.padding(8.dp))
Text(
text = "AnimatedVisibilityState",
style = MaterialTheme.typography.h6
)
AnimatedVisibility(
visibleState = state,
enter = fadeIn(),
exit = fadeOut()
)
Surface(
color = Color.Yellow,
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.align(Alignment.CenterHorizontally)
.padding(8.dp)
)
Text(text = state.getAnimationState().toString())
Button(
onClick = state.targetState = !state.currentState ,
modifier = Modifier.fillMaxWidth()
)
Text(text = "Toggle")
2. AnimatedContent
AnimatedContent 和 AnimatedVisibility 类似,都是通过动画完成 content 内部的状态变化,AnimatedVisibility 是控制显隐,AnimatedContent 是控制切换:
@ExperimentalAnimationApi
@Composable
fun AnimatedContentCounterDefault()
Column
var count: Int by remember mutableStateOf(0)
AnimatedContent(targetState = count) targetCount ->
Text(
text = "$targetCount",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h2,
modifier = Modifier.fillMaxWidth()
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center)
Button(onClick = count++ )
Text("PLUS")
Button(onClick = count-- )
Text("MINUS")
如上,AnimatedContent 接收一个 targetState 参数,同时 content 基于 targetContent 构建新的 UI,targetState 的不同导致 content 的不同,当 targetState 发生变化时,content 在动画中完成切换。
targetState 可以是任意类型的值,上面例子中,我们基于 Int 型的 count 生成新的 content,点击 PLUS 和 MINUS 之后,content 的 Text 在动画中完成切换:
ContentTransform
切换动画可以通过 transitionSpec
参数设置一个 ContentTransform
, ContentTransform 可以通过 with 中缀操作符,组合 EnterTransition 和 ExitTransition 而成。
ContentTransform = EnterTransition with ExitTransition
- EnterTransition:新的 content 的进入时的动画
- ExitTransition: 旧的 content 退出时的动画
例如,我们使用 ContentTransform 实现一个 Slide 效果的切换动画:
- 从右到左的切换,并伴随淡入淡出效果:
- EnterTransion:使用 slideInHorizontally,初始位置 initialOffsetX = width
- ExitTransition:使用 slideOutHorizontally,目标位置 targetOffsetX = -width
slideInHorizontally( width -> width ) + fadeIn()
with slideOutHorizontally( width -> -width ) + fadeOut()
- 从左到右的切换,并伴随淡入淡出效果:
- EnterTransion:使用 slideInHorizontally,初始位置 initialOffsetX = -width
- ExitTransition:使用 slideOutHorizontally,目标位置 targetOffsetX = width
slideInHorizontally( width -> -width ) + fadeIn()
with slideOutHorizontally( width -> width ) + fadeOut()
我们应用上述 transitionSpec 后的代码:
@ExperimentalAnimationApi
@Composable
fun AnimatedContentCounterCustom()
Column
var count: Int by remember mutableStateOf(0)
AnimatedContent(
targetState = count,
transitionSpec =
val isPlus = targetState > initialState
if (isPlus)
slideInHorizontally( width -> width ) + fadeIn() with slideOutHorizontally( width -> -width ) + fadeOut()
else
slideInHorizontally( width -> -width ) + fadeIn() with slideOutHorizontally( width -> width ) + fadeOut()
.using(
SizeTransform(clip = false)
)
) targetCount ->
Text(
text = "$targetCount",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h2,
modifier = Modifier.fillMaxWidth()
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center)
Button(onClick = count++ )
Text("PLUS")
Button(onClick = count-- )
Text("MINUS")
点击 PLUS
时,数字从左到右移入,点击 MINUS
时,数字从右到左移出,效果如下:
SizeTransform
transitionSpec 中指定 ContentTransform 的同时,还可以通过 using
中缀添加 SizeTransform 动画
SizeTransform = EnterTransition with ExitTransition using SizeTransform
SizeTransform 中或获取旧 content 和新 content 的 size,并通过 keyframes
定义动画的执行规则:在何时应该多大且总的持续时长是多少
fadeIn() with fadeOut() using SizeTransform initialSize, targetSize ->
keyframes
// at :在 250 时刻应有的大小
IntSize(initialSize.width, initialSize.height) at 250
// durationMillis :动画的执行总时间
durationMillis = 500
添加 SizeTransform 之后的全部代码如下:
@ExperimentalMaterialApi
@ExperimentalAnimationApi
@Composable
fun AnimatedContentExpandableTextSample()
var expanded by remember mutableStateOf(false)
Surface(
color = MaterialTheme.colors.primary,
onClick = expanded = !expanded ,
modifier = Modifier.padding(8.dp)
)
AnimatedContent(
targetState = expanded,
transitionSpec =
fadeIn() with fadeOut() using SizeTransform initialSize, targetSize ->
keyframes
IntSize(initialSize.width, initialSize.height) at 250
durationMillis = 500
) targetExpanded ->
if (targetExpanded)
Text(
text = "Expanded",
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
)
else
Text(
text = "Not Expanded",
modifier = Modifier.wrapContentSize()
)
添加 SizeTransform 之后,点击按钮后的变化过程中,content 面积也会出现过度动画,效果如下:
3. 总结一下
AnimatedVisibility 和 AnimatedContent 是最常用的 Composable 动画 API ,它们向其他 Layout 元素一样都是 Composable 函数,动画效果作用于其内部的子 Composable:
- AnimatedVisibility 用来实现 content 的动画显隐,通过 enter 和 exit 可以自定义动画效果
- AnimatedContent 用来实现 content 的动画切换,等价于旧 content 的隐藏 + 新 content 的显示 ,通过 ContentTransform 和 SizeTransform 可以自定义动画效果
- 需要注意 AnimatedVisibility 和 AnimatedContent 仍然处于 @Experimental 状态,stable 之前也许会有所变化。
以上是关于Jetpack Compose 动画 API: AnimatedVisibility & AnimatedContent的主要内容,如果未能解决你的问题,请参考以下文章
Jetpack Compose 动画开发实践:微博长按点赞彩虹