Jetpack Compose 动画 API: AnimatedVisibility & AnimatedContent

Posted fundroid_方卓

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack Compose 动画 API: AnimatedVisibility & AnimatedContent相关的知识,希望对你有一定的参考价值。

Jetpack Compose 的动画相关的 API 数量众多,分为低级别 API 和高级别 API,其中高级别 API 便于使用者针对具体场景开箱即用 ,其中最常用的当属 AnimatedVisibilityAnimatedContent 这两个了。


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 的 currentStateisIdle 可以暴露当前动画的执行状态给外面参考,我们可以定义一个枚举表示动画的状态

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 可以通过 enterexit 参数指定动画样式,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 动画开发实践:微博长按点赞彩虹

Jetpack Compose 动画开发实践:微博长按点赞彩虹

Jetpack Compose中的动画

Jetpack Compose 从入门到入门

Jetpack Compose 从入门到入门

Jetpack Compose Effect 的作用