Compose 附带用于处理常见动画用例的内置可组合项和修饰符。
内置动画可组合项
使用 AnimatedVisibility
动画化外观和消失
The AnimatedVisibility
可组合项动画化其内容的外观和消失。
var visible by remember { mutableStateOf(true) } // Animated visibility will eventually remove the item from the composition once the animation has finished. AnimatedVisibility(visible) { // your composable here // ... }
默认情况下,内容通过淡入和展开出现,并通过淡出和缩小消失。可以通过指定 EnterTransition
和 ExitTransition
来自定义过渡。
var visible by remember { mutableStateOf(true) } val density = LocalDensity.current AnimatedVisibility( visible = visible, enter = slideInVertically { // Slide in from 40 dp from the top. with(density) { -40.dp.roundToPx() } } + expandVertically( // Expand from the top. expandFrom = Alignment.Top ) + fadeIn( // Fade in with the initial alpha of 0.3f. initialAlpha = 0.3f ), exit = slideOutVertically() + shrinkVertically() + fadeOut() ) { Text("Hello", Modifier.fillMaxWidth().height(200.dp)) }
如您在上面的示例中所见,您可以将多个 EnterTransition
或 ExitTransition
对象与 +
运算符组合在一起,并且每个运算符都接受可选参数来自定义其行为。有关更多信息,请参阅参考。
EnterTransition
和 ExitTransition
示例
AnimatedVisibility
还提供了一个变体,该变体接受 MutableTransitionState
。这允许您在将 AnimatedVisibility
添加到组合树后立即触发动画。它对于观察动画状态也很有用。
// Create a MutableTransitionState<Boolean> for the AnimatedVisibility. val state = remember { MutableTransitionState(false).apply { // Start the animation immediately. targetState = true } } Column { AnimatedVisibility(visibleState = state) { Text(text = "Hello, world!") } // Use the MutableTransitionState to know the current animation state // of the AnimatedVisibility. Text( text = when { state.isIdle && state.currentState -> "Visible" !state.isIdle && state.currentState -> "Disappearing" state.isIdle && !state.currentState -> "Invisible" else -> "Appearing" } ) }
为子项动画化进入和退出
在 AnimatedVisibility
内的内容(直接或间接子项)可以使用 animateEnterExit
修饰符为它们中的每一个指定不同的动画行为。这些子项中的每一个的视觉效果是 AnimatedVisibility
可组合项中指定的动画与其自身进入和退出动画的组合。
var visible by remember { mutableStateOf(true) } AnimatedVisibility( visible = visible, 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
完全不应用动画,以便子项可以通过 animateEnterExit
拥有各自的独特动画。为了实现这一点,请在 AnimatedVisibility
可组合项中指定 EnterTransition.None
和 ExitTransition.None
。
添加自定义动画
如果您希望在内置进入和退出动画之外添加自定义动画效果,请通过 AnimatedVisibility
的内容 lambda 中的 transition
属性访问底层 Transition
实例。添加到 Transition 实例的任何动画状态都将与 AnimatedVisibility
的进入和退出动画同时运行。 AnimatedVisibility
会等到 Transition
中的所有动画都完成之后才会删除其内容。对于独立于 Transition
创建的退出动画(例如使用 animate*AsState
),AnimatedVisibility
将无法考虑它们,因此可能会在它们完成之前删除内容可组合项。
var visible by remember { mutableStateOf(true) } AnimatedVisibility( visible = visible, enter = fadeIn(), exit = fadeOut() ) { // this: AnimatedVisibilityScope // Use AnimatedVisibilityScope#transition to add a custom animation // to the AnimatedVisibility. val background by transition.animateColor(label = "color") { state -> if (state == EnterExitState.Visible) Color.Blue else Color.Gray } Box(modifier = Modifier.size(128.dp).background(background)) }
有关 Transition
的详细信息,请参阅 updateTransition。
根据目标状态使用 AnimatedContent
动画化
The AnimatedContent
可组合项动画化其内容,因为它根据目标状态而改变。
Row { var count by remember { mutableStateOf(0) } Button(onClick = { count++ }) { Text("Add") } AnimatedContent(targetState = count) { targetCount -> // Make sure to use `targetCount`, not `count`. Text(text = "Count: $targetCount") } }
请注意,您应该始终使用 lambda 参数并将其反映到内容。API 使用此值作为识别当前显示内容的键。
默认情况下,初始内容淡出,然后目标内容淡入(此行为称为 淡入淡出)。您可以通过将 ContentTransform
对象指定给 transitionSpec
参数来自定义此动画行为。您可以通过将 EnterTransition
与 ExitTransition
组合使用 with
中缀函数来创建 ContentTransform
。您可以通过使用 using
中缀函数附加 SizeTransform
来应用于 ContentTransform
。
AnimatedContent( targetState = count, transitionSpec = { // Compare the incoming number with the previous number. if (targetState > initialState) { // If the target number is larger, it slides up and fades in // while the initial (smaller) number slides up and fades out. slideInVertically { height -> height } + fadeIn() with slideOutVertically { height -> -height } + fadeOut() } else { // If the target number is smaller, it slides down and fades in // while the initial number slides down and fades out. slideInVertically { height -> -height } + fadeIn() with slideOutVertically { height -> height } + fadeOut() }.using( // Disable clipping since the faded slide-in/out should // be displayed out of bounds. SizeTransform(clip = false) ) } ) { targetCount -> Text(text = "$targetCount") }
EnterTransition
定义目标内容应如何出现,而 ExitTransition
定义初始内容应如何消失。除了可用于 AnimatedVisibility
的所有 EnterTransition
和 ExitTransition
函数之外,AnimatedContent
还提供 slideIntoContainer
和 slideOutOfContainer
。这些是 slideInHorizontally/Vertically
和 slideOutHorizontally/Vertically
的便捷替代方案,它们根据 AnimatedContent
内容的初始内容和目标内容的大小来计算滑动距离。
SizeTransform
定义大小应如何在初始内容和目标内容之间动画化。在创建动画时,您可以访问初始大小和目标大小。 SizeTransform
还控制在动画期间内容是否应裁剪到组件大小。
var expanded by remember { mutableStateOf(false) } Surface( color = MaterialTheme.colorScheme.primary, onClick = { expanded = !expanded } ) { AnimatedContent( targetState = expanded, transitionSpec = { fadeIn(animationSpec = tween(150, 150)) with fadeOut(animationSpec = tween(150)) using SizeTransform { initialSize, targetSize -> if (targetState) { keyframes { // Expand horizontally first. IntSize(targetSize.width, initialSize.height) at 150 durationMillis = 300 } } else { keyframes { // Shrink vertically first. IntSize(initialSize.width, targetSize.height) at 150 durationMillis = 300 } } } } ) { targetExpanded -> if (targetExpanded) { Expanded() } else { ContentIcon() } } }
动画化子项进入和退出过渡
就像 AnimatedVisibility
一样,animateEnterExit
修饰符可在 AnimatedContent
的内容 lambda 内使用。使用它分别对每个直接或间接子项应用 EnterAnimation
和 ExitAnimation
。
添加自定义动画
就像 AnimatedVisibility
一样,transition
字段可在 AnimatedContent
的内容 lambda 内使用。使用它来创建与 AnimatedContent
过渡同时运行的自定义动画效果。有关详细信息,请参阅 updateTransition。
使用 Crossfade
在两个布局之间动画化
Crossfade
使用交叉淡入淡出动画在两个布局之间动画化。通过切换传递给 current
参数的值,内容将使用交叉淡入淡出动画切换。
var currentPage by remember { mutableStateOf("A") } Crossfade(targetState = currentPage) { screen -> when (screen) { "A" -> Text("Page A") "B" -> Text("Page B") } }
内置动画修饰符
使用 animateContentSize
动画化可组合项大小变化
The animateContentSize
修饰符动画化大小变化。
var expanded by remember { mutableStateOf(false) } Box( modifier = Modifier .background(colorBlue) .animateContentSize() .height(if (expanded) 400.dp else 200.dp) .fillMaxWidth() .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { expanded = !expanded } ) { }
列表项动画
如果您想在 Lazy 列表或网格中动画化项目重新排序,请查看 Lazy 布局项目动画文档。
推荐给您
- 注意:当 JavaScript 关闭时,链接文本将显示
- 基于值的动画
- Compose 中的动画
- 动画工具支持 {:#tooling}