Jetpack Compose 动画(动画修饰符和可组合项)

448 阅读4分钟

内置动画可组合项

使用 AnimatedVisibility 为出现和消失添加动画效果

petal_20240411_160956_20240411_161025.gif

@Composable
private fun example1() {
  val visible = remember { mutableStateOf(false) }
  Column {
      customTitle(title = "内置动画可组合项")

      Button(modifier = Modifier.padding(start = 15.dp), onClick = { visible.value = !visible.value }) {
          Text(text = if (visible.value) "隐藏另一个按钮" else "显示另一个按钮")
      }
      AnimatedVisibility(visible.value) {
          Button(modifier = Modifier.padding(start = 15.dp), onClick = { }) {
              Text(text = "另一个按钮")
          }
      }
  }
}

默认情况下,内容以淡入和扩大的方式出现,以淡出和缩小的方式消失。您可以通过指定 [EnterTransition] 和 [ExitTransition] 来自定义这种过渡效果。

petal_20240411_163332_20240411_163404.gif

@Composable
private fun example2() {
    val visible = remember { mutableStateOf(false) }
    Column {
        customTitle(title = "指定 EnterTransition 和 ExitTransition 来自定义过渡效果")

        Button(modifier = Modifier.padding(start = 15.dp), onClick = { visible.value = !visible.value }) {
            Text(text = if (visible.value) "隐藏另一个按钮" else "显示另一个按钮")
        }
        AnimatedVisibility(visible = visible.value,
            enter = expandVertically(),
            exit = shrinkVertically()) {
            Button(modifier = Modifier.padding(start = 15.dp), onClick = { }) {
                Text(text = "另一个按钮")
            }
        }       
    }
}

如上面的示例所示,您可以使用 + 运算符组合多个 EnterTransition 或 ExitTransition 对象,并且每个对象都接受可选参数以自定义其行为。如需了解详情,请参阅相关参考资料。

EnterTransitionExitTransition
fadeIn

fadeOut

slideIn
slideOut
slideInHorizontally
slideOutHorizontally
slideInVertically
slideOutVertically
scaleIn
scaleOut
expandIn
shrinkOut
expandHorizontally
shrinkHorizontally
expandVertically
shrinkVertically

AnimatedVisibility 还提供了接受 MutableTransitionState 的变体。这样,只要将 AnimatedVisibility 添加到组合树中,您就可以立即触发动画。该属性还有助于观察动画状态。

无标题视频——使用Clipchamp制作 (9).gif

@Composable
private fun example3() {
    val state = remember { MutableTransitionState(false) }
    Column {
        Button(modifier = Modifier.padding(start = 15.dp), enabled = state.isIdle, onClick = { state.targetState = !state.currentState }) {
            Text(text = when {
                state.isIdle && state.currentState -> "隐藏另一个按钮"
                !state.isIdle && state.currentState -> "正在隐藏..."
                state.isIdle && !state.currentState -> "显示另一个按钮"
                else -> "正在显示..."
            })
        }
        AnimatedVisibility(visibleState  = state) {
            Button(modifier = Modifier.padding(start = 15.dp), onClick = { }) {
                Text(text = "另一个按钮")
            }
        }     
    }
}

为子项添加进入和退出动画效果

AnimatedVisibility 中的内容(直接或间接子项)可以使用 [animateEnterExit] 修饰符为每个子项指定不同的动画行为。其中每个子项的视觉效果均由 AnimatedVisibility 可组合项中指定的动画与子项自己的进入和退出动画构成。

petal_20240412_093425_20240412_093447.gif

@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun example4() {
    val visible = remember { mutableStateOf(false) }
    Column {
        customTitle(title = "为子项添加进入和退出动画效果")
        Button(modifier = Modifier.padding(start = 15.dp), onClick = { visible.value = !visible.value }) {
            Text(text = if (visible.value) "隐藏" else "显示")
        }
        AnimatedVisibility(
            visible = visible.value,
            enter = fadeIn(),
            exit = fadeOut()
        ) {
            // Fade in/out the background and the foreground.
            Box(
                Modifier
                    .fillMaxSize()
                    .background(Color.DarkGray)) {
                Box(
                    Modifier
                        .align(Alignment.Center)
                        .animateEnterExit(
                            // Slide in/out the inner box.
                            enter = slideInVertically(),
                            exit = slideOutVertically()
                        )
                        .sizeIn(minWidth = 256.dp, minHeight = 64.dp)
                        .background(Color.Red)
                ) {
                    // Content of the notification…
                }
            }
        }
    }
}

添加自定义动画

如果您想在内置进入和退出动画之外添加自定义动画效果,请通过 AnimatedVisibility 的内容 lambda 内的 transition 属性访问底层 Transition 实例。添加到 Transition 实例的所有动画状态都将与 AnimatedVisibility 的进入和退出动画同时运行。AnimatedVisibility 会等到 Transition 中的所有动画都完成后再移除其内容。对于独立于 Transition(例如使用 animate*AsState)创建的退出动画,AnimatedVisibility 将无法解释这些动画,因此可能会在完成之前移除内容可组合项。

petal_20240412_094421_20240412_094443.gif

@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun example5() {
    val visible = remember { mutableStateOf(false) }
    Column {
        customTitle(title = "添加自定义动画")
        Button(modifier = Modifier.padding(start = 15.dp), onClick = { visible.value = !visible.value }) {
            Text(text = if (visible.value) "隐藏" else "显示")
        }
        AnimatedVisibility(
            visible = visible.value,
            enter = fadeIn(),
            exit = fadeOut()
        ) { // this: AnimatedVisibilityScope
            // Use AnimatedVisibilityScope#transition to add a custom animation
            // to the AnimatedVisibility.
            val background = transition.animateColor(label = "color") { state ->
                if (state == EnterExitState.Visible) Color.Blue else Color.Red
            }
            Box(modifier = Modifier.size(200.dp).background(background.value))
        }    
    }
}

使用 AnimatedContent 根据目标状态添加动画效果

[AnimatedContent] 可组合项会在内容根据目标状态发生变化时,为内容添加动画效果。

petal_20240412_100400_20240412_100437.gif

@Composable
private fun example6() {
    val count = remember { mutableStateOf(0) }
    Column {
        customTitle(title = "使用 AnimatedContent 根据目标状态添加动画效果")
        Button(modifier = Modifier.padding(start = 15.dp), onClick = { count.value++ }) {
            Text(text = "自增")
        }
        AnimatedContent(targetState = count.value, label = "AnimatedContent") { targetCount ->
            // Make sure to use `targetCount`, not `count`.
            Text(text = "计数: $targetCount",  style = TextStyle(fontSize = 20.sp), modifier = Modifier.padding(all = 15.dp))
        }
        CodeView(assetUrl = "animation/animationModifierComposable/code5.txt", modifier = Modifier.padding(top = 10.dp))
    }
}

EnterTransition 定义了目标内容应如何显示,ExitTransition 则定义了初始内容应如何消失。除了可用于 AnimatedVisibility 的所有 EnterTransition 和 ExitTransition 函数之外,AnimatedContent 还提供了 [slideIntoContainer] 和 [slideOutOfContainer]。这些是 slideInHorizontally/Vertically 和 slideOutHorizontally/Vertically 的便捷替代方案,它们可根据初始内容的大小和 AnimatedContent 内容的目标内容来计算滑动距离。

[SizeTransform] 定义了大小应如何在初始内容与目标内容之间添加动画效果。在创建动画时,您可以访问初始大小和目标大小。SizeTransform 还可控制在动画播放期间是否应将内容裁剪为组件大小。

petal_20240412_180550_20240415_151701.gif

@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun example7() {
    val expanded = remember { mutableStateOf(false) }
    Column {
        customTitle(title = "使用 AnimatedContent 根据目标状态添加动画效果")
        Surface(
            color = MaterialTheme.colorScheme.primary,
            onClick = { expanded.value = !expanded.value }
        ) {
            AnimatedContent(
                targetState = expanded.value,
                transitionSpec = {
                    fadeIn(animationSpec = tween(300, 150)) with
                            fadeOut(animationSpec = tween(300)) using
                            SizeTransform { initialSize, targetSize ->
                                if (targetState) {
                                    keyframes {
                                        // Expand horizontally first.
                                        IntSize(targetSize.width, initialSize.height) at 300
                                        durationMillis = 600
                                    }
                                } else {
                                    keyframes {
                                        // Shrink vertically first.
                                        IntSize(initialSize.width, targetSize.height) at 300
                                        durationMillis = 600
                                    }
                                }
                            }
                },
                label = "AnimatedContent"
            ) { targetExpanded ->
                if (targetExpanded) {
                    Text(text = "SizeTransform 定义了大小应如何在初始内容与目标内容之间添加动画效果。在创建动画时,您可以访问初始大小和目标大小。SizeTransform 还可控制在动画播放期间是否应将内容裁剪为组件大小",
                        modifier = Modifier.size(300.dp).padding(all = 5.dp))
                } else {
                    Icon(imageVector = Icons.Filled.Call, contentDescription = "icon",
                        modifier = Modifier.size(50.dp).padding(all = 5.dp))
                }
            }
        }
    }
}

上一篇 Jetpack Compose 列表

下一篇 Jetpack Compose 动画(基于 value 的动画)