LazyColumn(垂直滚动列表) 和 LazyRow(水平滚动列表)
延迟组件和Compose中的大多数布局不同,延迟组件不接受@composable内容块参数。而是lazyListScope.()块。 item() //用于添加单个列表 items(Int)用于添加多个列表
LazyVerticalGrid和LazyHorizontialGrid延迟网格 LazyGridScope.()来描述内容
在 Compose 中,延迟列表 指的是 LazyColumn、LazyRow 和 LazyVerticalGrid 等组件。它们用于高效地显示大型数据集 ,其核心原理是 "按需组合" :只组合和布局当前屏幕上(或即将进入屏幕)的列表项,对移出屏幕的项进行重用或释放,从而保证极致的性能和内存效率。
🎯 核心组件与选择
| 组件 | 方向 | 特点 | 适用场景 |
|---|---|---|---|
LazyColumn |
垂直 | 最常见的垂直滚动列表。 | 聊天记录、新闻流、联系人列表。 |
LazyRow |
水平 | 水平滚动列表。 | 图片轮播、横向分类标签。 |
LazyVerticalGrid |
垂直网格 | 固定列数的网格布局。 | 相册、商品网格、表情面板。 |
LazyHorizontalGrid |
水平网格 | 固定行数的水平网格。 | 日历的周视图、横向表格。 |
📝 基础用法示例
1. 最简单的列表
kotlin
@Composable
fun SimpleMessageList(messages: List<String>) {
LazyColumn {
// items:用于迭代集合
items(messages) { message ->
Text(
text = message,
modifier = Modifier.padding(16.dp),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
2. 带索引和复杂项的列表
kotlin
@Composable
fun UserList(users: List<User>) {
LazyColumn {
itemsIndexed(
items = users,
key = { index, user -> user.id } // 关键:提供稳定且唯一的 key
) { index, user ->
UserCard(user = user, index = index)
}
}
}
@Composable
fun UserCard(user: User, index: Int) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Row(modifier = Modifier.padding(16.dp)) {
Text(text = "${index + 1}.", modifier = Modifier.width(24.dp))
Column(modifier = Modifier.weight(1f)) {
Text(text = user.name, style = MaterialTheme.typography.titleMedium)
Text(text = user.email, style = MaterialTheme.typography.bodySmall)
}
}
}
}
⚙️ 关键特性与高级用法
1. 内容块与多种项类型
LazyColumn 等的内容 DSL 非常灵活,支持插入不同项。
kotlin
LazyColumn {
// 单个项
item {
HeaderTitle(title = "用户列表")
}
// 多个相同类型的项(items)
items(activeUsers) { user ->
ActiveUserItem(user)
}
// 另一个单个项(如分隔线)
item {
Divider(modifier = Modifier.padding(vertical = 8.dp))
}
// 另一组项,甚至类型不同
items(inactiveUsers) { user ->
InactiveUserItem(user)
}
// 粘性头部(需结合 stickyHeader)
stickyHeader {
CategoryHeader("已归档")
}
items(archivedUsers) { user ->
UserItem(user)
}
}
2. 项键(Key)------ 性能与状态保持的核心
为 items 或 itemsIndexed 提供 key 参数是最佳实践,它对性能和正确性至关重要:
- 作用 :
- 智能重组 :当数据顺序变化时(如排序、插入、删除),Compose 能通过键识别出哪些项是"移动"而非"全新创建",从而复用已有项的组合和状态,避免不必要的重组和状态丢失。
- 状态保持 :如果列表项内部有状态(如
remember),键能确保该状态在数据位置变化时依然跟随正确的数据项。
kotlin
// ✅ 正确做法:为每个项提供唯一、稳定的键
items(
items = books,
key = { book -> book.isbn } // 使用唯一标识,而非索引!
) { book ->
BookItem(book)
}
3. 控制布局与滚动状态
-
内容间距与内边距 :
kotlinLazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp), // 项间距 contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp) // 内容内边距 ) { ... } -
监听与控制滚动 :
kotlinval listState = rememberLazyListState() LazyColumn(state = listState) { ... } // 例如:监听是否滚动到底部 LaunchedEffect(listState) { snapshotFlow { listState.layoutInfo.visibleItemsInfo } .collect { visibleItems -> if (visibleItems.lastOrNull()?.index == listState.layoutInfo.totalItemsCount - 1) { onLoadMore() // 触发加载更多 } } } // 或者编程式滚动 fun scrollToTop() { scope.launch { listState.animateScrollToItem(0) } }
🚀 性能优化指南
| 优化项 | 做法 | 理由 |
|---|---|---|
| 提供唯一键 | key = { item -> item.id } |
启用项重用,避免状态错乱和不必要重组。 |
| 避免过度组合 | 使用 derivedStateOf 或 remember 缓存列表项内部的昂贵计算。 |
减少每次重组的计算量。 |
| 保持项轻量 | 避免在项的可组合函数中直接读取大型资源或发起网络请求。 | 保证滚动流畅性。 |
使用 contentType |
contentType = { item -> item.type } |
帮助 Compose 只复用同类型的项,进一步提升效率。 |
| 固定项尺寸 | 尽可能为项指定固定高度/宽度或使用 fillMaxWidth。 |
避免动态测量,滚动更平滑。 |
| 惰性加载图片 | 使用 Coil 或 Glide 的 Compose 扩展,它们内置了延迟加载优化。 |
图片是常见的性能瓶颈。 |
💎 与 Column 的根本区别
理解延迟列表,关键要明白它和普通 Column 的本质差异:
| 特性 | Column / Row |
LazyColumn / LazyRow |
|---|---|---|
| 工作方式 | 立即组合:一次性组合所有子项,无论是否在屏幕内。 | 延迟组合:只组合可见(或即将可见)项。 |
| 性能 | 数据集很大时,会严重卡顿并耗尽内存。 | 处理大型数据集流畅,内存占用恒定。 |
| 滚动 | 需嵌套在 Column 等可滚动容器内。 |
自身支持滚动,并管理滚动状态。 |
| 重组 | 状态变化可能导致整个列表重组。 | 智能作用域重组,通常只影响变化的项。 |
| 适用场景 | 少量、固定的子项(如设置页的5个选项)。 | 大量、可能动态变化的数据项(如消息列表)。 |
简单总结:对于任何可能超出屏幕范围的数据集,都应使用延迟列表。
🧠 最佳实践与常见陷阱
一定要做的:
- 始终提供唯一且稳定的
key。 - 将列表的状态提升到 ViewModel 中管理。
- 使用
collectAsStateWithLifecycle()在 UI 层收集数据流。
需要避免的:
kotlin
// ❌ 陷阱1:在列表项内部执行副作用(如 LaunchedEffect 发起请求)
items(users) { user ->
UserItem(user)
LaunchedEffect(user) { fetchDetail(user.id) } // 错误!会为每个可见项重复触发
}
// ❌ 陷阱2:使用不稳定的键(如随机数或索引本身)
key = { index -> index } // 索引在数据增删时会"漂移",导致状态错乱
// ❌ 陷阱3:在项内容中直接进行昂贵转换
items(list) { data ->
val expensiveResult = performHeavyCalculation(data) // 每次重组都计算!
ItemContent(expensiveResult)
}
// ✅ 正确:将转换提升到列表外部或使用 remember 缓存
掌握延迟列表的关键在于理解其 "按需组合" 和 "项重用" 的核心理念,并通过提供正确的 key 和遵循性能优化原则,使其发挥最大效能。