联系我们
简单又实用的WordPress网站制作教学
当前位置:网站首页 > 程序开发学习 > 正文

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

作者:访客发布时间:2024-01-03分类:程序开发学习浏览:89


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

一、动画分类

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

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

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

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

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

Snipaste_2023-12-29_15-33-34.png

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

二、高级别动画API

1、AnimatedVisibility

源码如下

@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在出现与消失时,会伴随着过渡动画效果。

var editable by remember { mutableStateOf(true) }
AnimatedVisibility(visible = editable) {
    Text(text = "Edit")
}

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

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

@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效果

Screen_recording_20240102_091606_V2.gif

如上面的示例,可以使用+运算符来组合多个已有的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的代码示例:

@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效果

Screen_recording_20240102_091606_V3.gif

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

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

(1)MutableTransitionState监听动画状态

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

@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。两个状态的不同驱动了动画的执行。

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)
}

看一段示例代码

@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效果

Screen_recording_20240102_091606_V4.gif

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

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

@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效果

Screen_recording_20240102_091606_V5.gif

主要看一下日志

//进入页面时进入动画的状态
动画的状态: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为每个子元素单独设置进出屏幕的过渡动画。看下面的示例代码:

@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效果

Screen_recording_20240102_091606_V6.gif

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

(3)自定义Enter/Exit动画

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

@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效果

Screen_recording_20240102_091606_V8.gif

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

2、AnimatedContent

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

@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发生重组时附加动画效果。

@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效果

Screen_recording_20240102_091606_V9.gif

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

(1)ContentTransform自定义动画

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

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

看下面的示例代码:

@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效果

Screen_recording_20240102_091606_V10.gif

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

slideInVertically { fullHeight -> fullHeight } + fadeIn() togetherWith slideOutVertically { fullHeight -> -fullHeight } + fadeOut()

UI效果

Screen_recording_20240102_091606_V11.gif

(2)SizeTranstion定义大小动画

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

看一个ContentTransform+SizeTranform的例子:

@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(1500150)) 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效果

Screen_recording_20240102_091606_V13.gif

如上面的例子中,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。

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

下面是一个示例

@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效果

Screen_recording_20240102_091606_V14.gif

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

4、Modifier.animateContentSize

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

@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效果

Screen_recording_20240102_091606_V15.gif

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

参考资料

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


标签:动画级别Compose喷气式飞机应用编程接口十六


程序开发学习排行
最近发表
网站分类
标签列表