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

滚动LazyRow时Box组件丢失圆角形状问题求助

滚动LazyRow时Box组件圆角丢失问题

滚动LazyRow时,移出可视区域的Box组件会丢失圆角形状。使用Modifier.clip(RoundedCornerShape(20.dp))修饰符后,组件在可视区域内时圆角正常,移出后形状消失,现象如下:

滚动LazyRow后组件形状发生变化

以下是对应的Composable函数代码:

@Composable
fun ChartCaloriesSection(
    allCalorieData: List<DayCalorieData>,
    requiredCalorieAmount: Int
) {
    Surface(
        modifier = Modifier
            .fillMaxWidth()
            .height(210.dp)
            .padding(horizontal = 16.dp),
        color = MaterialTheme.colorScheme.surfaceVariant,
        shape = RoundedCornerShape(20.dp),
        shadowElevation = 2.dp
    ) {
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.SpaceBetween
        ) {
            Text(
                text = "Calories statistics",
                modifier = Modifier.padding(16.dp)
            )
            val basicFormatter = DateTimeFormatter.ofPattern("MMMM d, yyyy", Locale.ENGLISH)
            val sortedCalorieData = allCalorieData.sortedBy {
                LocalDate.parse(it.date, basicFormatter)
            }

            LazyRow(
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.spacedBy(8.dp),
                contentPadding = PaddingValues(
                    start = 16.dp,
                    end = 16.dp
                ),
                modifier = Modifier.padding(bottom = 16.dp)
            ) {
                items(sortedCalorieData, key = { data -> data.date }) { data ->
                    Column(
                        horizontalAlignment = Alignment.CenterHorizontally,
                        verticalArrangement = Arrangement.spacedBy(8.dp),
                        modifier = Modifier
                            .animateItem()
                    ) {
                        val uiFormatter = DateTimeFormatter.ofPattern("dd.MM")
                        val date = LocalDate.parse(data.date, basicFormatter)
                        val outputDateString = date.format(uiFormatter)

                        var progress by rememberSaveable { mutableFloatStateOf(0f) }
                        LaunchedEffect(key1 = requiredCalorieAmount, key2 = data.receivedCaloriesAmount) {
                            progress = if(requiredCalorieAmount == 0) {
                                0f
                            } else {
                                (data.receivedCaloriesAmount - data.spentCaloriesAmount).toFloat() / requiredCalorieAmount.toFloat()
                            }
                        }
                        val percentFromRequired by animateFloatAsState(
                            targetValue = progress,
                            label = "progress animation",
                            animationSpec = tween(1000)
                        )

                        Box(
                            modifier = Modifier
                                .weight(1f)
                                .width(20.dp)
                                .clip(RoundedCornerShape(20.dp))
                                .background(MaterialTheme.colorScheme.surfaceDim),
                            contentAlignment = Alignment.BottomCenter,
                        ) {
                            Box(
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .fillMaxHeight(percentFromRequired)
                                    .background(
                                        Brush.verticalGradient(
                                            colors = listOf(
                                                MaterialTheme.colorScheme.primary,
                                                MaterialTheme.colorScheme.secondary
                                            )
                                        )
                                    )
                            )
                        }

                        Text(
                            text = outputDateString,
                            fontSize = 13.sp,
                        )
                    }
                }
            }
        }
    }
}

已尝试以下操作但未解决问题:

  • 调整父Box的修饰符顺序
  • 将weight(1f)改为固定高度height(100.dp)
  • 移除Column的Modifier.animateItem()
  • 为子Box添加clip修饰符

解决方案

这个问题本质是Lazy列表复用Item时,裁剪状态未正确同步或子组件背景超出裁剪范围导致的,可尝试以下方案:

方案1:给子Box添加同规格裁剪并调整修饰符顺序

确保子Box的clip修饰符在background之前,且与父Box使用相同的圆角参数,避免背景溢出:

Box(
    modifier = Modifier
        .weight(1f)
        .width(20.dp)
        .clip(RoundedCornerShape(20.dp))
        .background(MaterialTheme.colorScheme.surfaceDim),
    contentAlignment = Alignment.BottomCenter,
) {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight(percentFromRequired)
            .clip(RoundedCornerShape(20.dp))
            .background(
                Brush.verticalGradient(
                    colors = listOf(
                        MaterialTheme.colorScheme.primary,
                        MaterialTheme.colorScheme.secondary
                    )
                )
            )
    )
}

方案2:用drawBehind替代嵌套Box

直接在父Box的drawBehind中绘制背景和进度条,统一受裁剪规则约束:

Box(
    modifier = Modifier
        .weight(1f)
        .width(20.dp)
        .clip(RoundedCornerShape(20.dp))
        .drawBehind {
            // 绘制底部背景
            drawRect(MaterialTheme.colorScheme.surfaceDim.toArgb())
            // 绘制进度条
            val progressHeight = size.height * percentFromRequired
            drawRect(
                brush = Brush.verticalGradient(
                    colors = listOf(
                        MaterialTheme.colorScheme.primary,
                        MaterialTheme.colorScheme.secondary
                    )
                ),
                size = Size(size.width, progressHeight),
                topLeft = Offset(0f, size.height - progressHeight)
            )
        },
    contentAlignment = Alignment.BottomCenter,
)

方案3:优化进度状态的记忆逻辑

替换rememberSaveable为remember,并绑定Item的唯一标识,确保复用Item时状态正确初始化:

// 替换原有progress声明和LaunchedEffect
var progress by remember(data.date, requiredCalorieAmount) { 
    mutableFloatStateOf(
        if(requiredCalorieAmount == 0) 0f
        else (data.receivedCaloriesAmount - data.spentCaloriesAmount).toFloat() / requiredCalorieAmount.toFloat()
    )
}
val percentFromRequired by animateFloatAsState(
    targetValue = progress,
    label = "progress animation",
    animationSpec = tween(1000)
)

内容的提问来源于stack exchange,提问作者Егор Сауткин

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 15:16:04