Jetpack Compose(十六)Compose动画分类和高级别动画API

一、动画分类

Compose的动画API数量较多,刚接触的人不免有些头晕。为了方便大家快速了解,我们从使用场景的维度上将它们大体分为两类:高级别API和低级别API。就像编程语言分为高级语言和低级语言一样,这里的高级和低级指的是API的易用性。

高级别API服务于常见业务,设计上力求开箱即用,例如页面转场、UI元素的过渡等,高级别API大多是一个Composable函数,便于与其他Composable组合使用。

低级别API使用场景更加广泛,可以基于协程完成任何状态驱动的动画效果,相应的接口复杂度也更高。高级别API的底层实际上都是由低级别API支持的。

Compose动画API
分类 API 说明
高级别API AnimatedVisibility UI元素进入/退化时的过渡动画
高级别API AnimatedContent 布局内容变化时的动画
高级别API Modifier.animateContentSize 布局大小变化时的动画
高级别API Crossfade 两个布局切换时的淡入/淡出动画
低级别API animate*AsState 单个值动画
低级别API Animatable 可动画的数值容器
低级别API updateTransition 组合动画
低级别API rememberInfiniteTransition 组合无限执行动画
低级别API TargetBasedAnimation 自定义执行时间的低级别动画

上表列举了Compose动画相关的所有API。这么多的API在使用时需要有策略地进行选择。下面的关系图可以反映各API的选择路径,为我们提供指引。

(上图来自于实体书插图)

二、高级别动画API

1、AnimatedVisibility

源码如下

kotlin 复制代码
@Composable
fun AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = shrinkOut() + fadeOut(),
    label: String = "AnimatedVisibility",
    content: @Composable() AnimatedVisibilityScope.() -> Unit
) {
    val transition = updateTransition(visible, label)
    AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
}

AnimatedVisibility是一个容器类的Composable,需要接收一个Boolean型的visible参数控制content是否可见,content在出现与消失时,会伴随着过渡动画效果。

AnimatedVisibility是一个容器类的Composable,需要接收一个Boolean型的visible参数控制content是否可见,content在出现与消失时,会伴随着过渡动画效果。

kotlin 复制代码
var editable by remember { mutableStateOf(true) }
AnimatedVisibility(visible = editable) {
    Text(text = "Edit")
}

在上面的代码中,当editable为true/false时,Text将会淡入/淡出屏幕。

可以通过设置EnterTransition和ExitTransition来定制出场与离场过渡动画,当出场动画完成时,content便会从视图树上移除。下面是一个代码示例:

kotlin 复制代码
@Composable
fun Greeting() {
    var visible by remember { mutableStateOf(true) }
    val density = LocalDensity.current

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        // 第一个按钮,显示动画
        Button(onClick = { visible = true }) {
            Text("Show Animation")
        }

        // 第二个按钮,隐藏动画
        Button(onClick = { visible = false }) {
            Text("Hide Animation")
        }

        // 动画部分
        AnimatedVisibility(
            visible = visible,
            enter = slideInVertically {
                // 从顶部40dp的位置开始滑入
                with(density) { -40.dp.roundToPx() }
            } + expandVertically(
                // 从顶部开始展开
                expandFrom = Alignment.Top
            ) + fadeIn(
                // 从初始透明度0.1f开始淡入
                initialAlpha = 0.1f
            ),
            exit = slideOutVertically() + shrinkVertically() + fadeOut()
        ) {
            Text(
                "Hello Kotlin",
                color = Color.Black,
                fontSize = 36.sp
            )
        }
    }
}

UI效果

如上面的示例,可以使用+运算符来组合多个已有的EnterTransition或ExitTransition,并通过enter与exit参数进行设置。

默认情况下EnterTransition是fadeIn+expandIn的效果组合,ExitTransition是fadeOut+shrinkOut的效果组合。Compose额外提供了RowScope.AnimatedVisibility和ColumnScope. AnimatedVisibility两个扩展方法,我们可以在Row或者Column中调用AnimatedVisibility,该组件的默认过渡动画效果会根据父容器的布局特征进行调整,比如在Row中默认EnterTransition是fadeIn+expandHorizontally组合方案,而在Column中默认EnterTransition则是fadeIn+expandVertically组合方案。看下面RowScope的代码示例:

kotlin 复制代码
@Composable
fun Greeting() {
    var visible by remember { mutableStateOf(true) }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        // 第一个按钮,显示动画
        Button(onClick = { visible = true }) {
            Text("Show Animation")
        }

        // 第二个按钮,隐藏动画
        Button(onClick = { visible = false }) {
            Text("Hide Animation")
        }

        //Row的扩展动画
        Row(
            modifier = Modifier.fillMaxSize(),
            horizontalArrangement = Arrangement.Center
        ) {
            AnimatedVisibility(
                visible = visible,
                enter = fadeIn() + expandHorizontally(),
                exit = fadeOut() + shrinkHorizontally()
            ) {
                Text(
                    text = "Hello Compose",
                    fontSize = 36.sp,
                    color = Color.Black
                )
            }
        }
    }
}

UI效果

下表中列举了几种EnterTransition和ExitTransition的动画效果(具体动画效果请访问官网):

EnterTransition ExitTransition
fadeIn 淡入动画 fadeOut 淡出动画
slideIn滑入动画 slideOut 滑出动画
slideInHorizontally 水平滑入动画 slideOutHorizontally 水平滑出动画
slideInVertically 垂直滑入动画 slideOutVertically垂直滑出动画
scaleIn 缩放进入动画 scaleOut 缩放退出动画
expandIn 展开进入动画 shrinkOut缩小退出动画
expandHorizontally 水平展开动画 shrinkHorizontally 水平缩小动画
expandVertically 垂直展开动画 shrinkVertically 垂直缩小动画

(1)MutableTransitionState监听动画状态

AnimatedVisibility还有一个接收MutableTransitionState类型参数的重载方法。

kotlin 复制代码
@Composable
fun AnimatedVisibility(
    visibleState: MutableTransitionState<Boolean>,   //接收MutableTransitionState类型参数
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = fadeOut() + shrinkOut(),
    label: String = "AnimatedVisibility",
    content: @Composable() AnimatedVisibilityScope.() -> Unit
) {
    val transition = updateTransition(visibleState, label)
    AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
}

MutableTransitionState的定义如上所示,关键成员有两个:当前状态currentState和目标状态targetState。两个状态的不同驱动了动画的执行。

kotlin 复制代码
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

    // Updated from Transition
    internal var isRunning: Boolean by mutableStateOf(false)
}

看一段示例代码

kotlin 复制代码
@Composable
fun Greeting() {
    val state = remember { MutableTransitionState(false).apply { targetState = true } }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        // 第一个按钮,显示动画
        Button(onClick = { state.targetState = true }) {
            Text("Show Animation")
        }

        // 第二个按钮,隐藏动画
        Button(onClick = { state.targetState = false }) {
            Text("Hide Animation")
        }

        //MutableTransitionState传入AnimatedVisibility
        AnimatedVisibility(
            visibleState = state,
            enter = fadeIn() + expandHorizontally(),
            exit = fadeOut() + shrinkHorizontally()
        ) {
            Text(
                text = "Hello Compose",
                fontSize = 36.sp,
                color = Color.Black
            )
        }

    }
}

UI效果

在上面的代码中,我们在创建MutableTransitionState时,将currentState初始值设置为false,并将targetState设为true,所以当AnimatedVisibility上屏(即Composable组件的OnActive)时,由于两个状态的不同,动画会立即执行。可以用类似的做法实现一些开屏时的动画。

此外,MutableTransitionState的意义还在于通过currentState和isIdle的值,可以获取动画的执行状态。例如下面的代码:

kotlin 复制代码
@Composable  
    fun Greeting() {  
        //动画的监听  
        TestMutableTransitionState()  
    }  
  
  
    @Composable  
    fun TestMutableTransitionState() {  
        var isStopLog = false  
        val state = remember { MutableTransitionState(false).apply { targetState = true } }  
  
        Column(  
            modifier = Modifier  
                .fillMaxSize()  
                .padding(16.dp),  
            verticalArrangement = Arrangement.spacedBy(16.dp)  
        ) {  
            // 第一个按钮,显示动画  
            Button(onClick = {  
                state.targetState = true  
                isStopLog = false  
            }) {  
                Text("Show Animation", fontSize = 22.sp)  
            }  
  
            // 第二个按钮,隐藏动画  
            Button(onClick = {  
                state.targetState = false  
                isStopLog = false  
            }) {  
                Text("Hide Animation", fontSize = 22.sp)  
  
            }  
  
            //MutableTransitionState传入AnimatedVisibility  
            AnimatedVisibility(  
                visibleState = state,  
                enter = fadeIn() + expandHorizontally(),  
                exit = fadeOut() + shrinkHorizontally()  
            ) {  
                Text(  
                    text = "Hello Compose", fontSize = 36.sp, color = Color.Black  
                )  
            }  
        }  
  
        //定时获取动画的状态  
        LaunchedEffect(Unit) {  
            timer(period = 100) {  
                if (!isStopLog) {  
                    when (val animationState = state.getAnimationState()) {  
                        AnimState.INVISIBLE -> {  
                            "动画的状态:${animationState.name}".logd()  
                            isStopLog = true  
                        }  
  
                        AnimState.VISIBLE -> {  
                            "动画的状态:${animationState.name}".logd()  
                            isStopLog = true  
                        }  
  
                        else -> {  
                            "动画的状态:${animationState.name}".logd()  
                        }  
                    }  
                }  
            }  
        }  
    }  
  
    /**  
     * 动画的各种状态  
     */  
    private fun MutableTransitionState<Boolean>.getAnimationState(): AnimState {  
        return when {  
            this.isIdle && this.currentState -> AnimState.VISIBLE   //动画已经结束,当前处于可见状态  
            this.isIdle && !this.currentState -> AnimState.INVISIBLE   //动画已经结束,当前处于不可见状态  
            !this.isIdle && this.currentState -> AnimState.DISAPPEARING   //动画执行中,且逐渐不可见  
            else -> AnimState.APPEARING          //动画执行中,且逐渐可见  
        }  
    }  
  
    /**  
     * 动画状态的枚举  
     */  
    enum class AnimState {  
        VISIBLE, INVISIBLE, APPEARING, DISAPPEARING  
    }

UI效果

主要看一下日志

kotlin 复制代码
//进入页面时进入动画的状态
动画的状态:APPEARING
动画的状态:APPEARING
动画的状态:APPEARING
动画的状态:APPEARING
动画的状态:APPEARING
动画的状态:APPEARING
动画的状态:VISIBLE

//点击隐藏时的退出页面的动画状态
动画的状态:DISAPPEARING
动画的状态:DISAPPEARING
动画的状态:DISAPPEARING
动画的状态:DISAPPEARING
动画的状态:DISAPPEARING
动画的状态:INVISIBLE

//点击显示时进入页面的动画状态
动画的状态:APPEARING
动画的状态:APPEARING
动画的状态:APPEARING
动画的状态:APPEARING
动画的状态:APPEARING
动画的状态:VISIBLE

(2)Modifier.animateEnterExit

在AnimatedVisibility的content中,可以使用Modifier.animateEnterExit为每个子元素单独设置进出屏幕的过渡动画。看下面的示例代码:

kotlin 复制代码
@Composable  
    fun AnimatedModifier() {  
        var visible by remember { mutableStateOf(true) }  
  
        Column(  
            modifier = Modifier  
                .fillMaxSize()  
                .padding(16.dp),  
            verticalArrangement = Arrangement.spacedBy(16.dp)  
        ) {  
            // 第一个按钮,显示动画  
            Button(onClick = {  
                visible = true  
            }) {  
                Text("Show Animation", fontSize = 22.sp)  
            }  
  
            // 第二个按钮,隐藏动画  
            Button(onClick = {  
                visible = false  
            }) {  
                Text("Hide Animation", fontSize = 22.sp)  
  
            }  
  
            AnimatedVisibility(  
                visible = visible,  
                enter = fadeIn(),    //外层动画淡入淡出  
                exit = fadeOut()  
            ) {  
                Box(  
                    modifier = Modifier  
                        .fillMaxSize()  
                        .background(Color.DarkGray)  
                ) {  
                    Box(  
                        modifier = Modifier  
                            .align(Alignment.Center)  
                            .animateEnterExit(  
                                enter = slideInVertically(),    //内层组件滑入滑动屏幕  
                                exit = slideOutVertically()  
                            )  
                            .sizeIn(minWidth = 256.dp, minHeight = 256.dp)  
                    ) {  
                        Image(  
                            painter = painterResource(id = R.mipmap.rabit2),  
                            contentDescription = null  
                        )  
                    }  
                }  
            }  
        }  
    }

看下UI效果

比如上面的例子中,后添加的slide动画会覆盖AnimatedVisibility设置的fade动画。有时我们希望AnimatedVisibility内部每个子组件的过渡动画各不相同,此时可以为AnimatedVisibility的enter与exit参数分别设置EnterTransition. None和ExitTransition. None,并在每个子组件分别指定animateEnterExit就可以了。

(3)自定义Enter/Exit动画

如果想在EnterTransition和ExitTransition之外再增加其他动画效果,可以在AnimatedVisibilityScope内设置transition。添加到transition的动画都会在AnimatedVisibility进出屏幕动画的同时运行。AnimatedVisibility会等到Transition中的所有动画都完成后,再移出屏幕。看下面的示例代码:

kotlin 复制代码
@Composable  
    fun AnimatedCustom() {  
        var visible by remember { mutableStateOf(true) }  
  
        Column(  
            modifier = Modifier  
                .fillMaxSize()  
                .padding(16.dp),  
            verticalArrangement = Arrangement.spacedBy(16.dp)  
        ) {  
            // 第一个按钮,显示动画  
            Button(onClick = {  
                visible = true  
            }) {  
                Text("Show Animation", fontSize = 22.sp)  
            }  
  
            // 第二个按钮,隐藏动画  
            Button(onClick = {  
                visible = false  
            }) {  
                Text("Hide Animation", fontSize = 22.sp)  
  
            }  
  
            AnimatedVisibility(  
                visible = visible,  
                enter = fadeIn(animationSpec = tween(3000)),    //外层动画淡入淡出,动画时长3秒  
                exit = fadeOut(animationSpec = tween(3000))  
            ) {  
                //this:AnimatedVisibilityScope  
                //使用AnimatedVisibilityScope#transition添加自定义动画  
                //创建一个跟随动画状态改变的背景  
                val background by transition.animateColor(label = "") { enterExitState ->  
                    when (enterExitState) {  
                        EnterExitState.Visible -> {  
                            Color.Blue    //可见时蓝色
                        }  
                        EnterExitState.PostExit -> {  
                            Color.Green   //退出时绿色
                        }  
                        else -> {   //EnterExitState.PreEnter  
                            Color.DarkGray   //进入时灰色
                        }  
                    }  
                }  
                Box(  
                    modifier = Modifier  
                        .fillMaxSize()  
                        .background(background)   //使用动画背景  
                ) {  
                    Box(  
                        modifier = Modifier  
                            .align(Alignment.Center)  
                            .animateEnterExit(  
                                enter = slideInVertically(),    //内层组件滑入滑动屏幕  
                                exit = slideOutVertically()  
                            )  
                            .sizeIn(minWidth = 256.dp, minHeight = 256.dp)  
                    ) {  
                        Image(  
                            painter = painterResource(id = R.mipmap.rabit2),  
                            contentDescription = null  
                        )  
                    }  
                }  
            }  
        }  
    }

UI效果

在上面的代码中,向transition添加了一个颜色渐变动画,并将其设置为Box背景色。关于Transition的更多内容可以参考低级别动画API中的updateTransition。

2、AnimatedContent

AnimatedContent和AnimatedVisibility相类似,都是用来为content添加动画效果的Composable。区别在于AnimatedVisibility用来添加组件的出场与离场过渡动画,而AnimatedContent则是用来实现不同组件间的切换动画。

kotlin 复制代码
@Composable  
fun <S> AnimatedContent(  
    targetState: S,  
    modifier: Modifier = Modifier,  
    transitionSpec: AnimatedContentTransitionScope<S>.() -> ContentTransform = {  
        (fadeIn(animationSpec = tween(220, delayMillis = 90)) +  
            scaleIn(initialScale = 0.92f, animationSpec = tween(220, delayMillis = 90)))  
            .togetherWith(fadeOut(animationSpec = tween(90)))  
    },  
    contentAlignment: Alignment = Alignment.TopStart,  
    label: String = "AnimatedContent",  
    contentKey: (targetState: S) -> Any? = { it },  
    content: @Composable() AnimatedContentScope.(targetState: S) -> Unit  
) {...}

AnimatedContent参数上接收一个targetState和一个content, content是基于targetState创建的Composable。当targetState变化时,content的内容也会随之变化。AnimatedContent内部维护着targetState到conent的映射表,查找targetState新旧值对应的content后,在content发生重组时附加动画效果。

kotlin 复制代码
@Composable  
    fun TestAnimatedContent() {  
        var count by remember { mutableIntStateOf(0) }  
        Column(  
            modifier = Modifier  
                .fillMaxSize()  
                .padding(16.dp),  
            verticalArrangement = Arrangement.spacedBy(16.dp)  
        ) {  
           //点击按钮增加值  
            Button(onClick = {  
                count++  
            }) {  
                Text("增加", fontSize = 22.sp)  
            }  
            //count赋值给targetState  
            AnimatedContent(targetState = count, label = "") { targetState ->  //targetState不使用会报红  
                Text("count的值:$targetState", fontSize = 22.sp)  
            }  
        }  
    }

UI效果

上述代码中,单击按钮触发count发生变化,AnimatedContent中Text的重组会应用动画效果。需要注意的是targetState一定要在content中被使用。

(1)ContentTransform自定义动画

AnimatedContent默认动画是淡入淡出效果,还可以将transitionSpec参数指定为一个ContentTransform来自定义动画效果。ContentTransform也是由EnterTransition与ExitTransition组合的,可以使用togetherWith中缀运算符将EnterTransition与ExitTransition组合起来。

kotlin 复制代码
package androidx.compose.animation

infix fun EnterTransition.togetherWith(exit: ExitTransition) = ContentTransform(this, exit)

可以很容易猜到,ContentTransform本质上就是currentContent的ExitTransition与targetContent的EnterTransition组合。例如使用ContentTransform实现一个Slide效果的切换动画:

从右到左切换,并伴随淡入淡出效果:

  • EnterTransion:使用slideInHorizontally,初始位置initialOffsetX=width
  • ExitTransition:使用slideOutHorizontally,目标位置targetOffsetX=-width

看下面的示例代码:

kotlin 复制代码
@Composable  
    fun TestAnimatedContent() {  
        var count by remember { mutableIntStateOf(0) }  
        Column(  
            modifier = Modifier  
                .fillMaxSize()  
                .padding(16.dp),  
            verticalArrangement = Arrangement.spacedBy(16.dp)  
        ) {  
            //点击按钮增加值  
            Button(onClick = {  
                count++  
            }) {  
                Text("增加", fontSize = 22.sp)  
            }  
            //count赋值给targetState  
            //targetState不使用会报红  
            AnimatedContent(targetState = count, transitionSpec = {
                //重点就是下面这行
                slideInHorizontally { fullWidth -> fullWidth } + fadeIn() togetherWith slideOutHorizontally { fullWidth -> -fullWidth } + fadeOut()  
            }, label = "") { targetState ->  
                Text("count的值:$targetState", fontSize = 22.sp)  
            }  
        }  
    }

UI效果

看了上面的效果,做一个竖直跳动的数字也就很简单了,只需要修改动画为垂直方向即可:

kotlin 复制代码
slideInVertically { fullHeight -> fullHeight } + fadeIn() togetherWith slideOutVertically { fullHeight -> -fullHeight } + fadeOut()

UI效果

(2)SizeTranstion定义大小动画

在使用ContentTransform来创建自定义过渡动画的同时,还可以使用using操作符连接SizeTransform。SizeTransform可以使我们预先获取到currentContent和targetContent的Size值,并允许我们来定制尺寸变化的过渡动画效果。

看一个ContentTransform+SizeTranform的例子:

kotlin 复制代码
@Composable  
    fun TestAnimatedSize() {  
        var expanded by remember { mutableStateOf(false) }  
        Column(modifier = Modifier  
            .fillMaxWidth()  
            .height(300.dp)  
            .clickable {  
                expanded = !expanded  
            }) {  
            AnimatedContent(targetState = expanded, transitionSpec = {  
                //淡入淡出  
                fadeIn(animationSpec = tween(1500, 150)) togetherWith  
                        fadeOut(animationSpec = tween(1500)) using   //使用using操作符连接SizeTransform  
                        SizeTransform { initialSize, targetSize ->  
                            if (targetState) {  
                                keyframes {   //动画的关键帧  
                                    //展开时,先水平方向展开  
                                    //在动画的 1500 毫秒处达到目标尺寸  
                                    IntSize(targetSize.width, initialSize.height) at 1500  
                                    durationMillis = 3000  
                                }  
                            } else {  
                                keyframes {   //动画的关键帧  
                                    //收起时,先垂直方向收起  
                                    //在动画的 1500 毫秒处达到目标尺寸  
                                    IntSize(initialSize.width, targetSize.height) at 1500  
                                    durationMillis = 3000  
                                }  
                            }  
                        }  
            }, label = "") { targetState ->  
                ExpandLayout(targetState)  
            }  
        }  
    }  
  
    @Composable  
    private fun ExpandLayout(targetState: Boolean) {  
        if (targetState) {  
            Text(text = stringResource(id = R.string.verse))  
        } else {  
            Icon(imageVector = Icons.Filled.AddCircle, contentDescription =null )  
        }  
    }

UI效果

如上面的例子中,currentContent是一个小尺寸的icon, targtContent是一段大尺寸的文本,从icon到文本切换的过程中,可以使用SizeTransform实现尺寸变化的过渡动画。在SizeTransform中可以通过关键帧keyframes指定Size在某一个时间点的尺寸,以及对应的动画时长。比如例子中表示expend过程持续时间为3000ms,在1500ms前,高度保持不变,宽度逐渐增大,而在到达1500ms之后,宽度到达目标值将不再变化,高度再逐渐增大。

(3)定义子元素动画

与AnimatedVisibility一样,AnimatedContent内部的子组件也可以通过Modifier.animatedEnterExit单独指定动画。

(4)自定义Enter/Exit动画

通过AnimatedContent的定义可知,其content同样是在AnimatedVisibilityScope作用域中,所以内部也可以通过transition添加额外的自定义动画。

3、Crossfade

Crossfade可以理解为AnimatedContent的一种功能特性,它使用起来更简单,如果只需要淡入淡出效果,可以使用Crossfade替代AnimatedContent。

kotlin 复制代码
@Composable  
fun <T> Crossfade(  
    targetState: T,  
    modifier: Modifier = Modifier,  
    animationSpec: FiniteAnimationSpec<Float> = tween(),  
    label: String = "Crossfade",  
    content: @Composable (T) -> Unit  
) {...}

下面是一个示例

kotlin 复制代码
@Composable  
    fun TestAnimatedCrossfade() {  
        var currentPage by remember { mutableStateOf("A") }  
        Crossfade(targetState = currentPage, animationSpec = tween(1500), label = "") { screen ->  
            when (screen) {  
                "A" -> Text(  
                    text = "PAGE LOGIN",  
                    fontSize = 26.sp,  
                    color = Color.Blue,  
                    modifier = Modifier.clickable { currentPage = "B" })  
  
                else -> Text(  
                    text = "PAGE MAIN",  
                    fontSize = 26.sp,  
                    color = Color.Red,  
                    modifier = Modifier.clickable { currentPage = "A" })  
            }  
        }  
    }

UI效果

如上所述,Crossfade内的文本会以淡入淡出的形式进行切换。其实更正确的说法应该是AnimatedContent是Crossfade的一种泛化,Crossfade的API出现后,为了强化切换动画的能力,增加了AnimatedContent。需要注意Crossfade无法实现SizeTransform那样尺寸变化的动画效果,如果content变化前后尺寸不同,想使用动画进行过渡,可以使用AnimatedContent+SizeTranform的组合方案,或者使用Crossfade和接下来要介绍的Modifier.animateContentSize。

4、Modifier.animateContentSize

animateContentSize是一个Modifier修饰符方法。它的用途非常专一,当容器尺寸发生变化时,会通过动画进行过渡,开箱即用。下面是一个简单的例子:

kotlin 复制代码
@Composable  
    fun TestAnimatedContentSize() {  
        var expend by remember { mutableStateOf(false) }  
        Column(Modifier.padding(16.dp)) {  
            Button(  
                onClick = { expend = !expend }  
            ) {  
                Text(if (expend) "Shrink" else "Expand")  
            }  
            Spacer(Modifier.height(16.dp))  
            Box(  
                Modifier  
                    .background(Color.LightGray)  
                    .animateContentSize()     //父控件使用动画
            ) {  
                Text(  
                    text = stringResource(id = R.string.verse),  
                    modifier = Modifier.padding(16.dp),  
                    fontSize = 16.sp,  
                    textAlign = TextAlign.Justify,  
                    maxLines = if (expend) Int.MAX_VALUE else 2  
                )  
            }  
        }  
    }

UI效果

如上代码所示,expend决定文本的最大行数,也就决定了Box的整体尺寸,正常情况下大小的变化会立即生效,但是为Box添加Modifieir.animatedContentSize后,文本大小的变化会使用动画过渡。

参考资料

本文为学习博客,内容来自书籍《Jetpack Compose 从入门到实战》,代码为具体实践。致谢!

相关推荐
深海呐4 小时前
Android 最新的AndroidStudio引入依赖失败如何解决?如:Failed to resolve:xxxx
android·failed to res·failed to·failed to resol·failed to reso
解压专家6664 小时前
安卓解压软件推荐:高效处理压缩文件的实用工具
android·智能手机·winrar·7-zip
Rverdoser4 小时前
Android 老项目适配 Compose 混合开发
android
️ 邪神6 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】标题栏
android·flutter·ios·鸿蒙·reatnative
努力遇见美好的生活6 小时前
Mysql每日一题(行程与用户,困难※)
android·数据库·mysql
图王大胜8 小时前
Android Framework AMS(17)APP 异常Crash处理流程解读
android·app·异常处理·ams·crash·binderdied·讣告
ch_s_t8 小时前
电子商务网站之首页设计
android
豆 腐11 小时前
MySQL【四】
android·数据库·笔记·mysql
想取一个与众不同的名字好难13 小时前
android studio导入OpenCv并改造成.kts版本
android·ide·android studio
Jewel10514 小时前
Flutter代码混淆
android·flutter·ios