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

如何在LazyColumn列表中实现仅选中单个条目的功能

基于LazyColumn实现配送地址单选功能方案

核心实现逻辑很简单:选中状态统一在列表上层托管,仅记录当前选中项的唯一标识,绝对不要把选中状态存在单个列表Item内部,天然就能保证同一时间只有一个地址被选中。

1. 定义基础数据模型

地址数据必须带唯一主键,用来区分不同条目、判断选中状态:

data class DeliveryAddress(
    val id: String, // 地址唯一ID,一般用后端返回的地址主键
    val contactName: String,
    val phone: String,
    val detail: String,
    val isDefault: Boolean = false
)

2. 页面层状态托管

把选中状态提到LazyColumn的上层持有,初始值可以默认选中设置了默认标签的地址:

@Composable
fun DeliveryAddressListPage() {
    // 实际项目中替换为从ViewModel/数据层获取的地址列表
    val addressList = remember {
        mutableStateListOf(
            DeliveryAddress(id = "1", contactName = "张三", phone = "138****1234", detail = "北京市朝阳区建国路88号", isDefault = true),
            DeliveryAddress(id = "2", contactName = "李四", phone = "139****5678", detail = "上海市浦东新区陆家嘴环路1000号"),
            DeliveryAddress(id = "3", contactName = "王五", phone = "137****9012", detail = "广州市天河区珠江新城冼村路")
        )
    }

    // 核心:只存储当前选中的地址ID,单一数据源保证单选逻辑
    var selectedAddressId by remember {
        mutableStateOf(addressList.firstOrNull { it.isDefault }?.id)
    }

3. LazyColumn列表实现

渲染列表时给每个条目绑定选中判断逻辑,点击条目时直接更新全局存储的选中ID即可,旧选中项会自动取消选中:

LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        items(
            items = addressList,
            key = { it.id } // 必须设置key,用地址唯一ID作为key,避免重组时状态错乱
        ) { address ->
            val isSelected = address.id == selectedAddressId
            AddressItem(
                address = address,
                isSelected = isSelected,
                onItemClick = {
                    // 更新选中ID,直接覆盖之前的选中值
                    selectedAddressId = address.id
                    // 后续可在这里加回调,把选中的地址传给ViewModel做后续业务处理
                }
            )
        }
    }
}

4. 地址条目组件实现

根据传入的选中状态切换UI样式即可,组件本身不需要持有任何选中状态:

@Composable
fun AddressItem(
    address: DeliveryAddress,
    isSelected: Boolean,
    onItemClick: () -> Unit
) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .clickable { onItemClick() },
        border = if (isSelected) {
            BorderStroke(2.dp, Color(0xFF1677FF))
        } else {
            BorderStroke(1.dp, Color.LightGray)
        },
        colors = CardDefaults.cardColors(
            containerColor = if (isSelected) Color(0xFFE6F4FF) else Color.White
        )
    ) {
        Row(
            modifier = Modifier.padding(16.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Column(modifier = Modifier.weight(1f)) {
                Row(verticalAlignment = Alignment.CenterVertically) {
                    Text(
                        text = address.contactName,
                        fontSize = 16.sp,
                        fontWeight = FontWeight.Medium
                    )
                    Spacer(modifier = Modifier.width(8.dp))
                    Text(
                        text = address.phone,
                        fontSize = 14.sp,
                        color = Color.Gray
                    )
                    if (address.isDefault) {
                        Spacer(modifier = Modifier.width(8.dp))
                        Box(
                            modifier = Modifier
                                .background(Color(0xFFFFF7E6), RoundedCornerShape(4.dp))
                                .padding(horizontal = 4.dp, vertical = 2.dp)
                        ) {
                            Text(
                                text = "默认",
                                fontSize = 12.sp,
                                color = Color(0xFFFF7D00)
                            )
                        }
                    }
                }
                Spacer(modifier = Modifier.height(8.dp))
                Text(
                    text = address.detail,
                    fontSize = 14.sp,
                    color = Color.DarkGray
                )
            }
            if (isSelected) {
                Icon(
                    imageVector = Icons.Default.CheckCircle,
                    contentDescription = "已选中",
                    tint = Color(0xFF1677FF)
                )
            }
        }
    }
}

避坑提示

  • 不要把选中状态下沉到单个Item内部用remember独立持有,LazyColumn的Item复用机制会导致状态错乱,出现多个条目同时选中的问题
  • 如果页面需要支持横竖屏切换、进程重启后状态保留,建议把selectedAddressId托管到ViewModel层,用StateFlow或SavedStateHandle存储,不要直接在Composable页面内用remember持有
  • 如果业务需要支持「点击已选中项即可取消选择」,只要把点击逻辑改成selectedAddressId = if (selectedAddressId == address.id) null else address.id即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 12:25:00