Jetpack Compose:如何实现LazyVerticalGrid单个网格项点击展开
在Jetpack Compose中实现单个可展开的LazyVerticalGrid项
我想要实现一个基于LazyVerticalGrid的UI布局,点击单个网格元素时,仅展开该元素并显示其原本隐藏的子项详情列表。
核心问题:如何实现点击效果仅作用于被点击的项,而非其所在列的所有项?
失败尝试
我曾用LazyColumn实现,但chunked方法将数据按3个元素一组拆分为二维列表,导致只能按行管理展开状态,且无法准确定位单个项的索引,最终出现点击一个元素时,所在列的所有元素同时展开的问题。
当时的实现代码如下:
@Composable fun ExpandableGridUsingLazyColumn() { val items = listOf( CollapsableSection( title = "电子与家电", rows = listOf("电子产品", "笔记本配件", "手机配件") ), CollapsableSection( title = "服饰", rows = listOf("男装", "女装") ), CollapsableSection( title = "家具与家居装饰", rows = listOf("家具", "家居与园艺") ), CollapsableSection( title = "食品与饮料", rows = listOf("饮料", "乳制品与烘焙", "杂货与主食", "零食与品牌食品", "蔬菜与水果") ), CollapsableSection( title = "健康与美妆", rows = listOf("洗浴用品", "男士护理", "个人护理") ), CollapsableSection( title = "家居清洁用品", rows = listOf("清洁产品", "家居必需品") ), CollapsableSection( title = "汽车配件", rows = listOf() ), CollapsableSection( title = "运动与健身", rows = listOf() ), CollapsableSection( title = "玩具、书籍与爱好", rows = listOf("书籍与文具", "玩具") ) ) // 示例数据 val columns = 3 // 列数 val rows = remember(items) { items.chunked(columns) } val collapsedState = remember { rows.map { true }.toMutableStateList() } var itemIndex = 0 LazyColumn( modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(8.dp) ) { rows.forEachIndexed { i, rowItems -> item { Column { // 标题行 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { rowItems.forEachIndexed { index, item -> Box( modifier = Modifier .weight(1f) .aspectRatio(1f) .background(Color.LightGray, shape = RoundedCornerShape(8.dp)) .clickable { itemIndex = index collapsedState[index] = !collapsedState[index] }, contentAlignment = Alignment.Center ) { Text( text = item.title, style = MaterialTheme.typography.bodyMedium ) } } // 填充行内剩余空白 repeat(columns - rowItems.size) { Spacer(modifier = Modifier.weight(1f)) } } Spacer(modifier = Modifier.height(8.dp)) // 标题行之间的间距 // 展开内容 if (!collapsedState[itemIndex]) { Column( modifier = Modifier .fillMaxWidth() .padding(8.dp) ) { rowItems[itemIndex].rows.forEach { item -> Text( text = "$item 详情", modifier = Modifier .padding(vertical = 4.dp) .background( Color(0xFFEFEFEF), shape = RoundedCornerShape(4.dp) ) .padding(8.dp) ) } } } } } } } } // 补充数据类 data class CollapsableSection(val title: String, val rows: List<String>)
正确实现方案:使用LazyVerticalGrid+独立项状态
要实现单个网格项的独立展开/折叠,核心是给每个网格项维护独立的展开状态,而非按行或列维护。直接使用LazyVerticalGrid替代LazyColumn+chunked的方式,就能避免行级状态的干扰。
完整代码
@Composable fun ExpandableLazyVerticalGrid() { val items = listOf( CollapsableSection( title = "电子与家电", rows = listOf("电子产品", "笔记本配件", "手机配件") ), CollapsableSection( title = "服饰", rows = listOf("男装", "女装") ), CollapsableSection( title = "家具与家居装饰", rows = listOf("家具", "家居与园艺") ), CollapsableSection( title = "食品与饮料", rows = listOf("饮料", "乳制品与烘焙", "杂货与主食", "零食与品牌食品", "蔬菜与水果") ), CollapsableSection( title = "健康与美妆", rows = listOf("洗浴用品", "男士护理", "个人护理") ), CollapsableSection( title = "家居清洁用品", rows = listOf("清洁产品", "家居必需品") ), CollapsableSection( title = "汽车配件", rows = listOf() ), CollapsableSection( title = "运动与健身", rows = listOf() ), CollapsableSection( title = "玩具、书籍与爱好", rows = listOf("书籍与文具", "玩具") ) ) // 为每个项维护独立的展开状态,初始全部折叠 val expandedStates = remember { mutableStateMapOf<Int, Boolean>().apply { items.indices.forEach { put(it, false) } } } LazyVerticalGrid( columns = GridCells.Fixed(3), modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { itemsIndexed(items) { index, section -> Column( modifier = Modifier .background(Color.LightGray, RoundedCornerShape(8.dp)) .clickable { // 切换当前项的展开状态 expandedStates[index] = !(expandedStates[index] ?: false) } ) { // 网格项标题区域 Box( modifier = Modifier .fillMaxWidth() .aspectRatio(1f), contentAlignment = Alignment.Center ) { Text( text = section.title, style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center, modifier = Modifier.padding(8.dp) ) } // 展开的详情列表,仅当当前项展开时显示 if (expandedStates[index] == true) { Column( modifier = Modifier .fillMaxWidth() .background(Color(0xFFEFEFEF), RoundedCornerShape(bottomStart = 8.dp, bottomEnd = 8.dp)) .padding(8.dp) ) { section.rows.forEach { item -> Text( text = "$item", modifier = Modifier.padding(vertical = 4.dp) ) } } } } } } } data class CollapsableSection(val title: String, val rows: List<String>)
关键实现要点
- 独立状态管理:使用
mutableStateMapOf<Int, Boolean>,以每个项的索引为key存储展开状态,确保每个项的状态互不干扰。 - 直接使用LazyVerticalGrid:通过
GridCells.Fixed(3)指定3列布局,无需手动拆分数据,每个网格项都是独立的Composable。 - 点击事件绑定到单个项:点击事件直接作用于当前网格项的Column,切换对应索引的状态,仅影响当前项的展开/折叠。
内容的提问来源于stack exchange,提问作者Tirumal Phutane
相关产品推荐
相关产品推荐

