11、列表与网格:LazyColumn、LazyRow、LazyVerticalGrid 的使用、item 与 items 函数
列表和网格,说白了就是 App 里最常用的内容展示方式。你刷朋友圈、逛淘宝、看新闻,背后全是这些东西在撑场面。在 Compose 里,Google 给我们准备了一套叫 Lazy 家族的组件,它们最大的特点就是——按需加载。你滑到哪,它才渲染到哪,不会一股脑把所有数据全塞进内存。
我记得刚接触 Compose 时,第一反应是:这玩意儿跟 RecyclerView 有啥区别?用了一段时间后,我得出一个结论:LazyColumn 就是 RecyclerView 的声明式升级版,而且写法更简洁,心智负担更低。
核心要点:LazyColumn(垂直列表)、LazyRow(水平列表)、LazyVerticalGrid(网格布局)。它们都遵循「按需加载 + 回收复用」的机制,性能上完全不用担心。
11.1 LazyColumn:最常用的垂直列表
先看一个最简单的例子。假设我们要展示 100 条文本,用传统方式你得写 Adapter、ViewHolder、LayoutManager……在 Compose 里,几行代码搞定:
@Composable
fun SimpleList() {
LazyColumn {
items(100) { index ->
Text(
text = "第 $index 条数据",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
}
}
}
嗯,这里要注意:items 函数接收一个整数,然后根据索引生成列表项。但实际项目中,我们更多是传一个数据集合进去。比如:
data class User(val name: String, val age: Int)
@Composable
fun UserList(users: List<User>) {
LazyColumn {
items(users) { user ->
UserItem(user)
}
}
}
@Composable
fun UserItem(user: User) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Text(text = user.name, style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.width(8.dp))
Text(text = "${user.age} 岁", style = MaterialTheme.typography.bodyMedium)
}
}
我个人习惯把列表项抽成独立的 Composable 函数,这样便于复用和测试。你在项目里如果遇到列表项逻辑特别复杂,也建议这么做。
小技巧:LazyColumn 默认不会给列表项添加分割线。如果你需要分割线,可以在 items 后面手动添加一个 item { Divider() },或者用 itemsIndexed 在循环里判断索引。
11.2 LazyRow:水平滑动的列表
LazyRow 和 LazyColumn 几乎一模一样,唯一的区别是滑动方向。LazyRow 是水平滑动,常用于标签页、图片轮播、横向分类等场景。
@Composable
fun HorizontalTagList(tags: List<String>) {
LazyRow(
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(tags) { tag ->
TagChip(tag)
}
}
}
@Composable
fun TagChip(tag: String) {
Surface(
shape = RoundedCornerShape(20.dp),
color = MaterialTheme.colorScheme.primaryContainer
) {
Text(
text = tag,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)
)
}
}
我在项目中遇到过一个问题:LazyRow 嵌套在 LazyColumn 里时,滑动冲突很头疼。后来发现只要给 LazyRow 设置固定的高度,或者用 Modifier.height(IntrinsicSize.Min) 就能解决。你想想看,嵌套滑动本质上就是事件分发的博弈,Compose 在这方面已经做得比传统 View 系统好很多了。
11.3 LazyVerticalGrid:网格布局
网格布局在相册、商品展示、图标列表里非常常见。Compose 提供了 LazyVerticalGrid,用法和 LazyColumn 类似,但多了一个 columns 参数来控制列数。
@Composable
fun PhotoGrid(photos: List<String>) {
LazyVerticalGrid(
columns = GridCells.Fixed(3), // 固定 3 列
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(photos) { photoUrl ->
Image(
painter = rememberAsyncImagePainter(photoUrl),
contentDescription = null,
modifier = Modifier
.aspectRatio(1f) // 保持正方形
.clip(RoundedCornerShape(8.dp))
)
}
}
}
除了 GridCells.Fixed,还有 GridCells.Adaptive(minSize)。这个 Adaptive 很有意思——它会根据屏幕宽度自动计算列数,保证每个单元格至少达到 minSize 的宽度。说白了就是自适应列数,在平板和手机上都能有不错的展示效果。
曾经踩过的坑:LazyVerticalGrid 的 columns 参数一旦设置,就不能在运行时动态改变。如果你需要根据屏幕旋转切换列数,必须重新创建 Composable 或者用 key 参数强制重组。我曾经因为这个 bug 排查了半天,最后发现是状态没重置。
11.4 item 与 items 函数详解
这两个函数是 Lazy 列表的核心 API。简单来说:
- item:添加单个列表项。适合插入头部、尾部、广告位、加载指示器等。
- items:添加多个列表项。接收一个集合或整数,配合 lambda 生成每一项。
看个混合使用的例子:
@Composable
fun MixedList() {
LazyColumn {
// 头部
item {
HeaderBanner()
}
// 数据列表
items(users) { user ->
UserItem(user)
}
// 加载更多
item {
if (isLoading) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
}
}
// 底部留白
item {
Spacer(modifier = Modifier.height(16.dp))
}
}
}
你可能会问:为什么不用 items 加一个 if 判断?因为 item 和 items 在底层是独立的「插槽」,它们各自维护自己的 key 和状态。把不同类型的 UI 拆成独立的 item,能让 Compose 更精准地做差分更新,性能更好。
重要:每个 item 或 items 块都应该有一个唯一的 key。虽然 Compose 会自动生成 key,但如果你列表项的顺序会变化(比如排序、过滤),手动指定 key 可以避免不必要的重组和动画异常。
items(users, key = { it.id }) { user ->
UserItem(user)
}
11.5 知识体系总览
下面这张图帮你理清 Lazy 家族的核心关系:
11.6 性能与最佳实践
最后聊几个我在实战中总结的经验:
- 不要嵌套 LazyColumn:LazyColumn 里再放一个 LazyColumn,会导致滑动冲突和性能问题。如果非要嵌套,考虑用
LazyColumn+LazyRow的组合,或者用Column包裹固定高度的内容。 - 合理使用 key:数据有唯一 ID 时,一定要传 key。这能帮助 Compose 精准定位哪些项需要重组,哪些可以复用。
- 避免在 items 里做耗时操作:列表项的 Composable 应该只做 UI 组装,不要在里面做网络请求、数据库查询或复杂计算。把这些逻辑提到 ViewModel 里,用 State 驱动 UI。
- contentPadding 和 arrangement:用
contentPadding给列表整体加边距,用Arrangement.spacedBy控制项之间的间距。别在每一项的 Modifier 里写 padding,那样会多出很多不必要的计算。
个人建议:刚开始学 Lazy 列表时,先拿 LazyColumn 练手。它最常用,也最容易理解。等你把 item、items、key、contentPadding 这些概念玩熟了,LazyRow 和 LazyVerticalGrid 基本就是换几个参数的事。
好了,这一章的内容就到这里。列表和网格是 Compose 里最基础也最核心的组件,你写任何 App 都绕不开它们。多写几个 Demo,把 item 和 items 的用法刻在脑子里,后面学更复杂的布局就会轻松很多。