You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 02:07:35