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

Jetpack Compose中ElevatedCard异常阴影问题排查

问题:ElevatedCard 添加组件后阴影异常

正常显示状态

初始仅包含文本时,ElevatedCard 显示正常:
正常显示的卡片

对应代码:

ElevatedCard(
    content = {
        Text(
            fontSize = 20.sp,
            text = "All cars",
            modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp, vertical = 12.dp)
        )
    },
    onClick = {

    },
    modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
)

异常显示状态

添加 IconButton 和展开文本后,卡片角落出现异常阴影,侧边、上下区域阴影几乎消失,折叠和展开状态均存在该问题:
折叠状态:
折叠状态下角落有阴影的卡片
展开状态:
展开状态下角落有阴影的卡片

对应代码:

var expanded by rememberSaveable { mutableStateOf(false) }

ElevatedCard(
    content = {
        Row(
            content = {
                Text(
                    fontSize = 20.sp,
                    text = "All cars",
                    modifier = Modifier.weight(weight = 1f).padding(start = 6.dp)
                )

                IconButton(
                    onClick = {
                        expanded = !expanded
                    },
                    checked = expanded,
                    icon = R.drawable.ic_chevron,
                    contentDescription = R.string.expand,
                    tint = MaterialTheme.colorScheme.secondary
                )
            },
            modifier = Modifier.padding(horizontal = 4.dp),
            verticalAlignment = Alignment.CenterVertically
        )

        ScaleVisible(
            content = {
                Text(
                    fontSize = 14.sp,
                    color = Color.DarkGray,
                    text = "All cars from the latest catalog.",
                    modifier = Modifier.padding(bottom = 4.dp)
                )
            },
            visible = expanded,
            modifier = Modifier.padding(horizontal = 10.dp)
        )
    },
    onClick = {
        expanded = !expanded
    },
    modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp).animateContentSize()
)

多次调整卡片内外边距无改善,请问问题出在哪里?

自定义组件代码

@Composable
fun ScaleVisible(
    visible: Boolean,
    modifier: Modifier = Modifier,
    duration: Int = AnimationConstants.DefaultDurationMillis,
    content: @Composable AnimatedVisibilityScope.() -> Unit
) {
    AnimatedVisibility(
        content = content,
        visible = visible,
        modifier = modifier,
        enter = scaleIn(tween(duration)),
        exit = scaleOut(tween(duration))
    )
}

@Composable
@OptIn(ExperimentalMaterial3Api::class, ExperimentalAnimationGraphicsApi::class)
fun Icon(
    modifier: Modifier = Modifier,
    iconSize: Dp = 30.dp,
    @DrawableRes icon: Int,
    visible: Boolean = true,
    checked: Boolean? = null,
    @StringRes contentDescription: Int,
    tint: Color = LocalContentColor.current,
) {
    ScaleVisible(
        content = {
            TooltipBox(
                tooltip = {
                    PlainTooltip(
                        contentColor = Color.White,
                        containerColor = MaterialTheme.colorScheme.secondary
                    ) {
                        Text(
                            fontWeight = FontWeight.Light,
                            text = stringResource(id = contentDescription)
                        )
                    }
                },
                content = {
                    Icon(
                        tint = tint,
                        painter = if (checked != null)
                            rememberAnimatedVectorPainter(
                                atEnd = checked,
                                animatedImageVector = AnimatedImageVector.animatedVectorResource(id = icon)
                            )
                        else
                            painterResource(id = icon),
                        modifier = Modifier.size(size = iconSize),
                        contentDescription = onString(id = contentDescription)
                    )
                },
                state = rememberTooltipState(),
                positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider()
            )
        },
        visible = visible,
        modifier = modifier
    )
}

@Composable
fun IconButton(
    modifier: Modifier = Modifier,
    iconSize: Dp = 30.dp,
    @DrawableRes icon: Int,
    enabled: Boolean = true,
    visible: Boolean = true,
    checked: Boolean? = null,
    @StringRes contentDescription: Int,
    tint: Color = LocalContentColor.current,
    onClick: () -> Unit
) {
    ScaleVisible(
        content = {
            IconButton(
                content = {
                    val color = if (enabled)
                        tint
                    else
                        Color.LightGray
                    Icon(
                        icon = icon,
                        tint = color,
                        checked = checked,
                        iconSize = iconSize,
                        contentDescription = contentDescription
                    )
                },
                onClick = onClick,
                enabled = enabled
            )
        },
        visible = visible,
        modifier = modifier
    )
}

问题原因及解决方法

问题出在**animateContentSize()修饰符与ElevatedCard的阴影渲染冲突**,同时自定义IconButton嵌套多层ScaleVisible(即AnimatedVisibility),叠加动画干扰了卡片的布局测量,导致阴影边界计算异常。

具体修复步骤:

  1. 将animateContentSize()从ElevatedCard移到卡片内部
    把动画逻辑限定在可展开区域,避免影响整个卡片的阴影计算:

    // 替换原卡片内的Column/布局根节点
    Column(modifier = Modifier.padding(16.dp).animateContentSize()) {
        // 原Row和ScaleVisible内容
    }
    
  2. 简化动画嵌套层级
    自定义IconButton已经嵌套了ScaleVisible,卡片本身又有点击展开逻辑,多层动画会导致布局测量偏差。可以先移除IconButton内的ScaleVisible测试,确认问题后再按需保留外层动画。

  3. 遵循Material3内边距规范
    给ElevatedCard的内容区域添加标准内边距(如16.dp),避免内容紧贴卡片边缘干扰阴影渲染。

修复后的示例代码:

var expanded by rememberSaveable { mutableStateOf(false) }

ElevatedCard(
    content = {
        Column(modifier = Modifier.padding(16.dp).animateContentSize()) {
            Row(
                verticalAlignment = Alignment.CenterVertically
            ) {
                Text(
                    fontSize = 20.sp,
                    text = "All cars",
                    modifier = Modifier.weight(1f)
                )

                IconButton(
                    onClick = { expanded = !expanded },
                    checked = expanded,
                    icon = R.drawable.ic_chevron,
                    contentDescription = R.string.expand,
                    tint = MaterialTheme.colorScheme.secondary
                )
            }

            ScaleVisible(
                content = {
                    Text(
                        fontSize = 14.sp,
                        color = Color.DarkGray,
                        text = "All cars from the latest catalog.",
                        modifier = Modifier.padding(top = 8.dp)
                    )
                },
                visible = expanded
            )
        }
    },
    onClick = { expanded = !expanded },
    modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
)

另外注意自定义Icon组件中的onString应为stringResource,这个笔误也可能间接影响布局稳定性。

内容的提问来源于stack exchange,提问作者Mark Delphi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 23:07:05