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

如何解决Jetpack Compose中LazyVerticalGrid复选框重复问题?

问题:Bug Report界面复选框重复创建

问题描述

开发Bug Report界面时,用户输入Bug描述或旋转屏幕后,复选框会被重复创建。界面核心功能为:用户输入Bug描述、选择对应复选框后,将这些信息用于生成邮件发送。

相关数据类定义:

data class BugReport(
    val message: String,
    val bugItems: List<BugItem>,
)

data class BugItem(
    val title: String,
    val completed: Boolean = false,
)

原界面Composable代码:

@ExperimentalFoundationApi
@Composable
fun BugReportScreen(
    modifier: Modifier = Modifier
) {
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.StartActivityForResult(),
        onResult = { }
    )

    val message = remember { mutableStateOf("") }

    val bugItems = remember {
        mutableStateListOf<BugItem>()
    }

    stringArrayResource(id = R.array.bug_report).forEach {
        bugItems += BugItem(title = it, completed = false)
    }
    val bugReport = remember {
        mutableStateOf(BugReport(message.value, bugItems))
    }

    Box(
        modifier = modifier,
    ) {
        Column(
            modifier = modifier
                .fillMaxSize()
                .verticalScroll(rememberScrollState())
        ) {

            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(50.dp)
            )

            GenericToolbar(title = "Bug Report")
            Spacer(modifier = Modifier.weight(1f)) 
            Text(text = stringResource(id = R.string.bug_intro))
            Text(text = stringResource(id = R.string.bug_description))

            TextFieldBugDescription(message)
            Text(text = stringResource(id = R.string.bug_check))    
            
            LazyVerticalGrid(
                modifier = Modifier.heightIn(max = 1000.dp),
                columns = GridCells.Fixed(2),
                content = {
                    items(bugReport.value.bugItems.size) { index ->
                        Row(
                            modifier = Modifier.padding(1.dp),
                            verticalAlignment = Alignment.CenterVertically
                        ) {

                            Checkbox(
                                checked = bugItems[index].completed,
                                onCheckedChange = { checked ->
                                    bugItems[index] = bugItems[index].copy(completed = checked)
                                }
                            )
                            Text(
                                text = bugItems[index].title,
                                style = MaterialTheme.typography.bodyLarge,
                            )
                        }
                    }

                }

            )
            Text(text = stringResource(id = R.string.bug_send))
            Row(
                horizontalArrangement = Arrangement.Center,
                modifier = Modifier.fillMaxWidth(),
            ) {
                LPlusButton(
                    onClick = {
                        sendEmail(launcher, bugReport)
                    },
                    text = { Text(text = stringResource(R.string.send_email)) },
                    leadingIcon = {
                        Icon(
                            imageVector = LPlusIcons.Email,
                            contentDescription = stringResource(R.string.send_email)
                        )
                    },
                    modifier = Modifier
                        .padding(horizontal = 24.dp)
                        .widthIn(364.dp)
                        .fillMaxWidth(),
                )
            }
        }
    }
}
@Composable
fun TextFieldBugDescription(message: MutableState<String>) {
    TextField(
        value = message.value,
        onValueChange = { message.value = it },
        label = { Text(stringResource(id = R.string.bug_label)) },
        maxLines = 2,
        modifier = Modifier
            .padding(20.dp)
            .fillMaxWidth()
            .height(200.dp)
    )
}

问题原因

  1. 重复添加列表元素:stringArrayResource(id = R.array.bug_report).forEach { bugItems += BugItem(...) }这段代码写在Composable根作用域中,每次界面重组(比如输入文字触发message状态变化、屏幕旋转触发重建)时,都会执行这段代码,往bugItems这个MutableStateList中追加新元素,导致列表长度持续增加,复选框重复显示。
  2. BugReport状态未自动同步:原代码中bugReport用mutableStateOf初始化,仅在第一次创建时获取message.value和bugItems的初始值,后续message或bugItems变化时,bugReport不会自动更新。

解决方法

1. 一次性初始化bugItems

将填充bugItems的逻辑放到remember的初始化块内部,确保只在第一次创建bugItems时执行一次元素添加操作,避免每次重组重复添加:

val bugItems = remember {
    mutableStateListOf<BugItem>().apply {
        stringArrayResource(id = R.array.bug_report).forEach {
            add(BugItem(title = it, completed = false))
        }
    }
}

2. 用derivedStateOf自动更新BugReport

使用derivedStateOf替代mutableStateOf创建bugReport,这样当message或bugItems发生变化时,bugReport会自动计算新值:

val bugReport = remember {
    derivedStateOf {
        BugReport(message.value, bugItems)
    }
}

修复后的完整代码

@ExperimentalFoundationApi
@Composable
fun BugReportScreen(
    modifier: Modifier = Modifier
) {
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.StartActivityForResult(),
        onResult = { }
    )

    val message = remember { mutableStateOf("") }

    // 一次性初始化bugItems,避免重复添加
    val bugItems = remember {
        mutableStateListOf<BugItem>().apply {
            stringArrayResource(id = R.array.bug_report).forEach {
                add(BugItem(title = it, completed = false))
            }
        }
    }

    // 自动同步message和bugItems的变化
    val bugReport = remember {
        derivedStateOf {
            BugReport(message.value, bugItems)
        }
    }

    Box(
        modifier = modifier,
    ) {
        Column(
            modifier = modifier
                .fillMaxSize()
                .verticalScroll(rememberScrollState())
        ) {

            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(50.dp)
            )

            GenericToolbar(title = "Bug Report")
            Spacer(modifier = Modifier.weight(1f)) 
            Text(text = stringResource(id = R.string.bug_intro))
            Text(text = stringResource(id = R.string.bug_description))

            TextFieldBugDescription(message)
            Text(text = stringResource(id = R.string.bug_check))    
            
            LazyVerticalGrid(
                modifier = Modifier.heightIn(max = 1000.dp),
                columns = GridCells.Fixed(2),
                content = {
                    items(bugReport.value.bugItems.size) { index ->
                        Row(
                            modifier = Modifier.padding(1.dp),
                            verticalAlignment = Alignment.CenterVertically
                        ) {

                            Checkbox(
                                checked = bugItems[index].completed,
                                onCheckedChange = { checked ->
                                    bugItems[index] = bugItems[index].copy(completed = checked)
                                }
                            )
                            Text(
                                text = bugItems[index].title,
                                style = MaterialTheme.typography.bodyLarge,
                            )
                        }
                    }

                }

            )
            Text(text = stringResource(id = R.string.bug_send))
            Row(
                horizontalArrangement = Arrangement.Center,
                modifier = Modifier.fillMaxWidth(),
            ) {
                LPlusButton(
                    onClick = {
                        sendEmail(launcher, bugReport)
                    },
                    text = { Text(text = stringResource(R.string.send_email)) },
                    leadingIcon = {
                        Icon(
                            imageVector = LPlusIcons.Email,
                            contentDescription = stringResource(R.string.send_email)
                        )
                    },
                    modifier = Modifier
                        .padding(horizontal = 24.dp)
                        .widthIn(364.dp)
                        .fillMaxWidth(),
                )
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者A. Cedano

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 14:49:51