Jetpack Compose sharedBounds动画:如何保持容器不透明仅内容过渡?
Jetpack Compose 实现容器不透明、仅内容过渡的转场动画
一、可以实现容器保持不透明,仅内容过渡
核心思路是将容器的视觉层(背景、阴影)与内部内容拆分,让容器只做大小/位置动画(全程不透明),内容层单独参与共享元素转场并处理淡入淡出。
具体实现代码
列表页的Card改造
val sharedState = sharedTransitionScope.rememberSharedContentState(key = "bounds_$id") val isExpanded = // 控制转场的状态变量(比如从导航参数获取) // 模拟Card的容器层:仅处理大小、阴影、位置动画,保持不透明 Box( modifier = Modifier .size( width = animateDpAsState(if (isExpanded) LocalConfiguration.current.screenWidthDp.dp else 280.dp).value, height = animateDpAsState(if (isExpanded) LocalConfiguration.current.screenHeightDp.dp else 160.dp).value ) .background(MaterialTheme.colorScheme.surface, shape = MaterialTheme.shapes.medium) .elevation(animateDpAsState(if (isExpanded) 0.dp else 4.dp).value) ) { // 内容层:参与sharedBounds转场,单独处理淡入淡出 AnimatedVisibility( visible = true, enter = fadeIn(animationSpec = tween(300)), exit = fadeOut(animationSpec = tween(300)), scope = animatedVisibilityScope ) { Box( modifier = Modifier .matchParentSize() .sharedBounds( sharedContentState = sharedState, animatedVisibilityScope = animatedVisibilityScope, resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds ) ) { // 原Card内部的内容(比如标题、图片等) ... } } }
目标页的Scaffold改造
Scaffold( modifier = Modifier.fillMaxSize() ) { paddingValues -> AnimatedVisibility( visible = true, enter = fadeIn(animationSpec = tween(300)), exit = fadeOut(animationSpec = tween(300)), scope = animatedVisibilityScope ) { Box( modifier = Modifier .padding(paddingValues) .sharedBounds( sharedContentState = sharedState, animatedVisibilityScope = animatedVisibilityScope, resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds ) ) { // 目标页面的内容 ... } } }
方案说明
- 容器层用
Box模拟Card的背景和阴影,通过animateDpAsState实现大小、阴影的平滑过渡,全程保持不透明,避免下方内容透出。 - 内容层通过
AnimatedVisibility单独控制淡入淡出,同时绑定sharedBounds实现位置和大小的同步转场,完美分离容器与内容的动画逻辑。
二、更推荐的替代方案:使用M3的Container Transform API
如果你使用Material 3,官方提供的Modifier.containerTransform()就是专门为这类容器转场设计的,默认就支持容器保持不透明、内容过渡的效果,比sharedBounds更贴合需求:
代码示例
列表页Card
val containerState = rememberContainerTransformState() val isExpanded = // 转场状态变量 ElevatedCard( modifier = Modifier .containerTransform( state = containerState, targetState = isExpanded, animationSpec = spring(stiffness = Spring.StiffnessMediumLow), // 可选:自定义容器的形状过渡 shape = { if (it) MaterialTheme.shapes.extraLarge else MaterialTheme.shapes.medium } ) ) { // Card内部内容 ... }
目标页Scaffold
Scaffold( modifier = Modifier .containerTransform( state = containerState, targetState = isExpanded, animationSpec = spring(stiffness = Spring.StiffnessMediumLow) ) ) { paddingValues -> // 目标页面内容 Box(modifier = Modifier.padding(paddingValues)) { ... } }
方案说明
containerTransform会自动处理容器的大小、位置、形状过渡,且容器背景全程不透明。- 内部内容默认会有淡入淡出的过渡效果,完全匹配Material Design转场规范,无需手动拆分容器和内容。
补充说明
你之前尝试的renderInSharedTransitionOverlay和zIndexOverlay无效,是因为sharedBounds默认会将整个绑定的Composable(包括Card容器和内容)都放入过渡Overlay中统一处理透明度,因此必须通过拆分层级来分离动画逻辑。
内容的提问来源于stack exchange,提问作者Emil
相关产品推荐
相关产品推荐

