列表LazyColumn与RecyclerView对照

2026-03-31 21:30 列表LazyColumn与RecyclerView对照已关闭评论

004 列表:从 RecyclerView 到 LazyColumn 的迁移心得

今天我想和大家聊聊列表,这个在移动开发里几乎无处不在的组件。如果你和我一样,是从传统的 View 体系(特别是 RecyclerView)一路走过来的,那么刚开始接触 ComposeLazyColumn 时,可能会下意识地寻找那些熟悉的“对应关系”。下面这张对照表,就是我最初为了理清思路而整理的,希望能帮你快速建立认知。

1. 先来一场“心智映射”

我们在 RecyclerView 中熟悉的 在 Compose 中对应的思路
Adapter + ViewHolder items { } / itemsIndexed + 一个 Composable lambda
LayoutManager 垂直列表用 LazyColumn,水平列表用 LazyRow
DiffUtil items 提供 key = { ... },这是性能优化的关键
Item 装饰或分割线 直接用 Divider 组件,或者在 item { } 里插入间隔

这里有个根本性的思维转换需要特别注意:Compose 没有 ViewHolder 那种复用同一个 View 实例的概念。它的机制是 按需组合智能跳过重组。所以,我们的优化重点从“复用 View”变成了“提供稳定的 key”和“使用不可变/稳定的数据类型”,来避免整个列表不必要的重组(这个我们会在第 008 篇详细展开)。

2. 一个最基础的 LazyColumn 长什么样?

理论说再多,不如看代码。下面就是一个最简单的列表实现,我习惯从这样的“最小可行产品”开始,然后再往上添加功能。

import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items

data class Message(val id: String, val title: String)

@Composable
fun MessageList(messages: List<Message>, onItemClick: (Message) -> Unit) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(
            items = messages,
            key = { it.id }
        ) { msg ->
            MessageRow(msg, onClick = { onItemClick(msg) })
        }
    }
}

@Composable
private fun MessageRow(msg: Message, onClick: () -> Unit) {
    Card(
        onClick = onClick,
        modifier = Modifier.fillMaxWidth()
    ) {
        Text(
            text = msg.title,
            modifier = Modifier.padding(16.dp),
            style = MaterialTheme.typography.bodyLarge
        )
    }
}

看,是不是很清晰?LazyColumn 块里直接使用 items 函数来遍历数据,并且我强烈建议你养成习惯,总是通过 key 参数提供一个唯一标识。这就像是给每个列表项上了户口,Compose 才能高效地判断哪些项需要更新。

3. 如何实现多种 Item 类型?(类似多 viewType)

RecyclerView 里我们要写 getItemViewType,在 LazyColumn 里就直观多了。你可以把 item { }items { } 自由地混合编排。

LazyColumn {
    item { Text("我是列表头部", modifier = Modifier.padding(8.dp)) }
    items(list, key = { it.id }) { ItemRow(it) }
    item {
        Button(onClick = { /* 加载更多 */ }, modifier = Modifier.fillMaxWidth()) {
            Text("加载更多")
        }
    }
}

这种声明式的方式,让列表的头部、尾部和分隔项变得非常容易管理,代码的可读性也大大提升。

4. 实现粘性头部(Sticky Header)

粘性头部是一个很常见的需求。在 Compose 中,我们可以使用 stickyHeader API,不过它目前还处于实验状态,记得加上 @OptIn 注解。

import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.lazy.stickyHeader

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun GroupedList(groups: List<Pair<String, List<String>>>) {
    LazyColumn {
        groups.forEach { (title, rows) ->
            stickyHeader {
                Text(
                    text = title,
                    modifier = Modifier
                        .fillMaxWidth()
                        .background(MaterialTheme.colorScheme.surfaceVariant)
                        .padding(12.dp)
                )
            }
            items(rows, key = { "$title-$it" }) { row ->
                Text(row, modifier = Modifier.padding(16.dp))
            }
        }
    }
}

它的使用逻辑和 item 很像,只是它会“粘”在顶部,直到被下一个粘性头部推走。构建分组列表变得异常简单。

5. 分页与“滑动到底部加载更多”

这是列表的进阶话题。常见的做法是监听 LazyListState 的状态,当用户滑动接近底部时触发加载。

import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow

val listState = rememberLazyListState()
LaunchedEffect(listState) {
    snapshotFlow {
        val info = listState.layoutInfo
        val lastVisible = info.visibleItemsInfo.lastOrNull()?.index ?: 0
        lastVisible to info.totalItemsCount
    }.collect { (last, total) ->
        if (total > 0 && last >= total - 3) {
            // 触发 ViewModel 中的加载更多逻辑
            // viewModel.loadMore()
        }
    }
}

LazyColumn(state = listState) { /* ... */ }

个人经验:这里的阈值(比如 total - 3)需要根据实际产品列表项的高度来调整。另外,一定要记得在 ViewModel 层做好去抖(debounce)处理,避免在快速滑动时高频触发网络请求。

当然,如果你已经在使用 Paging 3 库,那么集成会更简单,直接使用 collectAsLazyPagingItems() 即可(需要添加 paging-compose 依赖)。

6. 小心嵌套滚动!老问题,新形式

这又是一个从 RecyclerView 时代就流传下来的“祖训”:避免同向滚动的列表嵌套。在 XML 里,我们不会把 RecyclerView 放在另一个同向滚动的 RecyclerViewScrollView 里。

在 Compose 中,这条原则依然有效。优先考虑使用一个 LazyColumn 来承载所有可变长度的内容。如果万不得已需要嵌套(比如一个垂直列表里嵌一个横向列表),就需要用到 LazyColumnLazyRow 的嵌套滚动连接 API,这比之前要复杂一些,所以能避免就尽量避免。

7. 动手练习一下

光说不练假把式,我建议大家从这两个小练习开始:

  1. 迁移练习:找一个你项目中现有的 RecyclerView.Adapter,尝试把它改写成 LazyColumn + items(key=...) 的形式,感受一下代码量的变化和思维的转换。
  2. 状态处理:为你的列表加上空状态。逻辑很简单:if (list.isEmpty()) { item { EmptyView() } } else { items(...) }。这种条件性的 UI 构建在 Compose 里写起来非常自然。

列表是应用的骨架,掌握好 LazyColumn 是迈向熟练使用 Compose 的关键一步。希望我的这些经验对你有帮助。

下一篇,我们将一起看看如何用 Compose 定义主题和颜色:005-主题Material与资源字符串.md

当前文章价值9.05元,扫一扫支付后添加微信提供帮助!(如不能解决您的问题,可以申请退款)

你可能感兴趣的文章

来源:每日教程每日一例,深入学习实用技术教程,关注公众号TeachCourse
转载请注明出处: https://teachcourse.cn/3834.html ,谢谢支持!

资源分享

浅谈Collection接口和Collections封装类的区别 浅谈Collection接口和Collecti
Android SDK “Error when loading the SDK” Android SDK “Error when
Python库flask实现激活码功能具体实现 Python库flask实现激活码功能具
Android开发工程师创建项目需要掌握的Git命令 Android开发工程师创建项目需要

评论已关闭!