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

Jetpack Compose中LazyVerticalGrid选中项滚动后状态丢失问题

LazyVerticalGrid滚动后选中项状态丢失问题

我使用LazyVerticalGrid实现网格列表,点击卡片可切换背景色,未滚动时功能正常,但滚动后选中项状态丢失。已知Compose会在隐藏元素重新可见时重绘,但无法正确管理列表项状态,尝试过状态提升、rememberSaveable{},但UI状态仍无法在滚动后保持。

动图演示:
滚动后状态丢失演示

代码实现

数据类与列表组件

data class MonthFee(var isPaid :Boolean,var month :String,var monthIndex :Long)

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MonthSelectList2(
monthList: List<MonthFee>,
color: Color,
onMonthSelect: (list: List<MonthFee>) -> Unit) {

val selectedItems = remember { mutableStateListOf<MonthFee>() }

LazyVerticalGrid(
    userScrollEnabled = true,
    modifier = Modifier.background(color = MaterialTheme.colors.background.copy(0.6f)),
    columns = GridCells.Adaptive(150.dp)
) {
    items(items = monthList,key = {it.monthIndex}) { item ->

        val isSelected = selectedItems.contains(item)
        val cardBgColor = remember { 
          mutableStateOf(item.isPaid.getActivatedColor(color))
        }

        Card(backgroundColor = MaterialTheme.colors.background.copy(1f),
            onClick = {

                if (!item.isPaid) {
                    if (isSelected) {
                        selectedItems.remove(item)
                        cardBgColor.value = Color.LightGray
                    } else {
                        selectedItems.add(item)
                        cardBgColor.value = color.copy(0.65f)
                    }
                    onMonthSelect(selectedItems.toList())
                }

            }) {
            Box{
                Column(
                    modifier = Modifier.background(color = cardBgColor.value)
                ) {
                    Text(text = item.month)
                }
                if (item.isPaid) {
                    Image(
                        painter = painterResource(id = R.drawable.green_tick),
                        modifier = Modifier
                            .requiredSize(25.dp)
                            .padding(start = 0.dp, end = 0.dp, top = 0.dp, bottom =0.dp)
                            .clip(CircleShape),
                        contentDescription = ""
                    )
                }
            }}}}}

扩展函数

fun Boolean.getActivatedColor(color: Color): Color {
return if (this) {
    color
} else {
    Color.LightGray
}}

问题原因与解决方案

核心问题

  1. cardBgColor状态存储逻辑错误:每个列表项的cardBgColor用remember存储,当列表项被滚出屏幕后重绘时,remember会重新初始化该值,导致背景色回到item.isPaid对应的初始状态,而非选中后的状态。
  2. 选中状态与UI状态分离:选中状态存于selectedItems,但UI背景色单独维护在cardBgColor,两者未联动,重绘时无法从选中状态反推正确的背景色。

修复方案

移除单独维护的cardBgColor状态,直接通过item.isPaid和isSelected计算背景色,确保每次重绘都能依据最新状态渲染:

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MonthSelectList2(
    monthList: List<MonthFee>,
    color: Color,
    onMonthSelect: (list: List<MonthFee>) -> Unit
) {
    val selectedItems = remember { mutableStateListOf<MonthFee>() }

    LazyVerticalGrid(
        userScrollEnabled = true,
        modifier = Modifier.background(color = MaterialTheme.colors.background.copy(0.6f)),
        columns = GridCells.Adaptive(150.dp)
    ) {
        items(items = monthList, key = { it.monthIndex }) { item ->
            val isSelected = selectedItems.contains(item)
            // 直接计算背景色,无需单独维护状态
            val cardBgColor = when {
                item.isPaid -> item.isPaid.getActivatedColor(color)
                isSelected -> color.copy(0.65f)
                else -> Color.LightGray
            }

            Card(
                backgroundColor = MaterialTheme.colors.background.copy(1f),
                onClick = {
                    if (!item.isPaid) {
                        if (isSelected) {
                            selectedItems.remove(item)
                        } else {
                            selectedItems.add(item)
                        }
                        onMonthSelect(selectedItems.toList())
                    }
                }
            ) {
                Box {
                    Column(modifier = Modifier.background(color = cardBgColor)) {
                        Text(text = item.month)
                    }
                    if (item.isPaid) {
                        Image(
                            painter = painterResource(id = R.drawable.green_tick),
                            modifier = Modifier
                                .requiredSize(25.dp)
                                .padding(0.dp)
                                .clip(CircleShape),
                            contentDescription = ""
                        )
                    }
                }
            }
        }
    }
}

额外优化建议

  • 建议将MonthFee改为不可变数据类(把var isPaid改成val),避免意外的数据修改导致状态混乱。
  • 若需要持久化选中状态,可将选中逻辑改为存储monthIndex集合(而非MonthFee对象,避免序列化问题),并使用rememberSaveable保存:
// 父组件或ViewModel中维护选中的monthIndex集合
val selectedMonthIndices = rememberSaveable { mutableStateSetOf<Long>() }

// 在列表中判断选中状态
val isSelected = selectedMonthIndices.contains(item.monthIndex)

// 点击事件更新逻辑
onClick = {
    if (!item.isPaid) {
        if (isSelected) {
            selectedMonthIndices.remove(item.monthIndex)
        } else {
            selectedMonthIndices.add(item.monthIndex)
        }
        onMonthSelect(monthList.filter { selectedMonthIndices.contains(it.monthIndex) })
    }
}

内容的提问来源于stack exchange,提问作者Hritik Gupta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 12:50:12