Android Compose 动画使用详解(十三)Crossfade动画的使用

2,079 阅读4分钟

前言

Crossfade 是 Android Compose 的动画组件之一,用于实现在两个组件之间切换的动画效果,在两个组件进行切换时通过透明度的变化来形成平滑过渡的动画效果, 例如在图片轮播、导航栏切换或其他切换屏幕上的UI元素时使用。

使用

Crossfade的使用相对于上一篇介绍的 AnimatedVisibility而言要简单很多,他没有那么多的参数可以设置,除了用于动画切换的状态和要切换的 UI 组件外,就只有一个动画规格可进行配置,使用示例如下:

var shown by remember { mutableStateOf(true) }

Box{

    // 创建 Crossfade ,传入 shown 状态
    // 设置动画规格为 tween,动画时长 1000 ms
    Crossfade(shown, animationSpec = tween(1000)) {
        // 使用状态进行判断
        if(it){
            Box(
                Modifier
                    .size(100.dp, 100.dp)
                    .background(Color.Blue)
            )
        }else{
            Box(
                Modifier
                    .padding(all = 10.dp)
                    .size(80.dp, 80.dp)
                    .clip(RoundedCornerShape(10.dp))
                    .background(Color.Red)
            )
        }
    }
    Button(onClick = {
        shown = !shown
    }, Modifier.padding(top = 100.dp)) {
        Text(text = "Switch", style = TextStyle(fontSize = 10.sp))
    }
}

这里 Crossfade的状态传入的是 shown,当为 true 时显示蓝色方块 ,当为 false 时显示红色圆角矩形。

运行效果:

131.gif

切换时当前控件先谈出,即透明度从 1 变为 0,在这个过程中目标组件再淡入,即透明度从 0 变为 1,从而实现淡入淡出的切换效果。

为了更明显的看到切换效果,我们修改两个组件的位置,让其从重叠变为并排显示,然后再来看一下动画效果:

132.gif

这样就能很明显的看出 Crossfade 动画是如何进行切换的了。

下面再来看一下 Crossfade 的源码:

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

参数看着挺多的,有 5 个,但实际都是我们前面介绍过的,我们来一一看一下:

  • targetState:目标状态,可以根据当前的状态来显示不同的组件,例如上面通过设置一个布尔类型的变量来控制两个组件的显示状态
  • modifier:用于设置组件的属性,例如设置透明度、旋转角度等。可以通过设置modifier属性来控制组件的外观和行为,从而实现更加自然和流畅的过渡效果。
  • animationSpec:动画规格,FiniteAnimationSpec类型,即除了InfiniteRepeatableSpec无限循环动画规格之外的其他几个都可以使用,如果对动画规格的配置不了解可以去看看本专栏关于动画规格的介绍
  • label:动画标签,用于在预览时对动画进行标识,跟前面讲的 Transition动画的 label 参数作用相同
  • content:组件内容,函数类型参数,传入参数为 T,即 targetState 的类型,这样在 content 里就可以使用这个参数进行状态的判断

再来看一下方法的实现,发现只有两行,而且第一行代码很熟悉,这不就是我们前面介绍的 Transition动画的使用么。所以其实他就是通过 Transition来实现的,直接调用的 TransitionCrossfade方法。

也就是说我们也可以直接使用 Transition 来实现,代码如下:

var shown by remember { mutableStateOf(true) }
// 创建 Transition 
val transition = updateTransition(targetState = shown, label = "Crossfade")

Box{
    // 使用 transition.Crossfade
    transition.Crossfade(animationSpec = tween(1000) ) {
        if(it){
            Box(
                Modifier
                    .size(100.dp, 100.dp)
                    .background(Color.Blue)
            )
        }else{
            Box(
                Modifier
                    .padding(all = 10.dp)
                    .size(80.dp, 80.dp)
                    .clip(RoundedCornerShape(10.dp))
                    .background(Color.Red)
            )
        }
    }
    Button(onClick = {
        shown = !shown
    }, Modifier.padding(top = 100.dp)) {
        Text(text = "Switch", style = TextStyle(fontSize = 10.sp))
    }
}

动画效果跟上面使用 Crossfade 的完全一样。

实际上上一篇将的 AnimatedVisibility内部也是通过 Transition 实现的,也可以直接通过 transition.AnimatedVisibility� 来进行使用

实战

下面使用 Crossfade来实现一个底部导航页面切换,页面内容使用 Crossfade包裹从而实现页面切换的过渡动画,代码如下:

var selectedIndex by remember {
    mutableStateOf(0)
}

Scaffold(
    Modifier.fillMaxSize(),
    bottomBar = {
        BottomNavigation() {
            // 底部导航按钮
            BottomNavigationItem(
                selected = selectedIndex == 0,
                onClick = { selectedIndex = 0 },
                icon = { Icon(imageVector = Icons.Filled.Home, contentDescription = "首页") },
                label = { Text(text = "首页", fontSize = 10.sp) })
            BottomNavigationItem(
                selected = selectedIndex == 1,
                onClick = { selectedIndex = 1 },
                icon = { Icon(imageVector = Icons.Filled.Menu, contentDescription = "分类") },
                label = { Text(text = "分类", fontSize = 10.sp) })
            BottomNavigationItem(
                selected = selectedIndex == 2,
                onClick = { selectedIndex = 2 },
                icon = { Icon(imageVector = Icons.Filled.Send, contentDescription = "广场") },
                label = { Text(text = "广场", fontSize = 10.sp) })
            BottomNavigationItem(
                selected = selectedIndex == 3,
                onClick = { selectedIndex = 3 },
                icon = { Icon(imageVector = Icons.Filled.Person, contentDescription = "我的") },
                label = { Text(text = "我的", fontSize = 10.sp) })
        }
    }
) { innerPadding ->
    // 必须要使用一下 innerPadding,不然会报错,这里就简单的打印一下
    println(innerPadding)
    // Crossfade 包裹页面内容
    Crossfade(targetState = selectedIndex, animationSpec = tween(1000)) {
        // 根据 selectedIndex 的值控制显示不同的页面内容
        // 为了看到切换效果,这里将内容错位显示
        when (it) {
            0 -> Box(
                modifier = Modifier
                    .fillMaxSize()
            ) {
                Box(
                    Modifier
                        .size(100.dp)
                        .background(Color.Red))
            }
            1 -> Box(
                modifier = Modifier
                    .fillMaxSize()
            ) {
                Box(
                    Modifier
                        .padding(start = 100.dp, top = 100.dp)
                        .size(100.dp)
                        .background(Color.Blue)
                )
            }
            2 -> Box(
                modifier = Modifier
                    .fillMaxSize()
            ) {
                Box(
                    Modifier
                        .padding(start = 200.dp, top = 200.dp)
                        .size(100.dp)
                        .background(Color.Yellow)
                )
            }
            else -> Box(
                modifier = Modifier
                    .fillMaxSize()
            ) {
                Box(
                    Modifier
                        .padding(top = 300.dp)
                        .size(100.dp)
                        .background(Color.Cyan))
            }
        }
    }
}

为了能更好的看到切换效果,上面代码将页面内容放置了不同位置不同颜色的方块,下面运行看一下效果:

133.gif

最后

本文详细介绍了 Android Compose 中的 Crossfade 动画的使用方法和相关属性,并简单阐述了其实现原理以及它在实际应用中的应用场景。最后,我们通过一个简单的例子分享了关于 Crossfade 动画在导航页面切换中的应用,让大家更好地理解和使用这种动画效果。如果你对更多的 Compose 动画使用感兴趣,请持续关注本专栏。

本篇文章的源码地址:ComposeAnimationDemo

本文正在参加「金石计划」