一、核心原理(理解底层逻辑)
1. 与 Column 的核心差异(必懂)
| 特性 | Column | LazyColumn |
|---|---|---|
| 渲染逻辑 | 一次性渲染所有子项(无论是否可见) | 仅渲染屏幕可见项(约 10-20 个),滑动时销毁出屏项、创建入屏项 |
| 内存占用 | 随子项数量线性增长(1000 项 = 1000 个 Composable) | 恒定(仅保留可见项 + 少量缓存项) |
| 启动耗时 | 子项越多越慢(需初始化所有组件) | 秒开(仅初始化可见项) |
| 滚动能力 | 需手动加 verticalScroll,无复用 | 内置滚动,自带项复用逻辑 |
| 特殊能力 | 无(仅基础布局) | 粘性头部、精准滚动、网格布局 |
2. 复用机制
LazyColumn 内部通过 LazyListLayout 实现项复用:
- 维护「可见项列表」和「缓存池」,出屏的列表项会被放入缓存池,入屏时优先从缓存池复用,而非重新创建;
- 复用粒度是「Composable 项」,无需像 RecyclerView 一样手动写 Adapter/ViewHolder,Compose 自动处理。
二、基础用法(必掌握,覆盖 80% 场景)
1. 最简长列表(静态数据)
@Composable
fun BasicLazyColumn() {
// 模拟1000条静态数据
val dataList = List(1000) { "列表项 $it" }
// 核心:LazyColumn + items 遍历数据
LazyColumn(
// 可选:列表内边距
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp),
// 可选:列表项间距(无需手动加Spacer)
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// 遍历数据生成列表项(核心API)
items(dataList) { item ->
// 自定义列表项UI(Composable)
ListItemUI(text = item)
}
}
}
// 自定义列表项(可复用)
@Composable
fun ListItemUI(text: String) {
Text(
text = text,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
.background(Color.White)
.clip(RoundedCornerShape(8.dp)),
fontSize = 16.sp
)
}
2. 带索引的列表项(itemsIndexed)
若需要列表项的索引(如 “第 N 项”),使用 itemsIndexed:
LazyColumn {
itemsIndexed(dataList) { index, item ->
Text(
text = "索引 $index:$item",
modifier = Modifier.padding(12.dp)
)
}
}
3. 混合固定项 + 动态项(item + items)
支持 “固定头部 / 底部 + 动态列表” 的混合布局:
LazyColumn {
// 固定头部(单个项)
item {
Text(
text = "列表头部",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(12.dp)
)
}
// 动态列表项
items(dataList) { item ->
Text(text = item, modifier = Modifier.padding(12.dp))
}
// 固定底部(单个项)
item {
Text(
text = "列表底部(共${dataList.size}项)",
modifier = Modifier.padding(12.dp),
color = Color.Gray
)
}
}
4. 列表项点击事件
直接给列表项添加 clickable 修饰符:
LazyColumn {
items(dataList) { item ->
Text(
text = item,
modifier = Modifier
.fillMaxWidth()
.clickable {
// 点击事件逻辑
println("点击了:$item")
}
.padding(12.dp)
)
}
}
三、进阶特性(覆盖 95% 场景)
1. 列表状态管理(滚动控制 / 监听)
LazyListState 是 LazyColumn 的核心状态类,用于控制滚动、监听滚动位置,必须通过 rememberLazyListState() 创建(绑定 Composable 生命周期)。
核心 API:
| API | 作用 | 示例 |
|---|---|---|
animateScrollToItem(index) | 带动画滚动到指定项 | scope.launch { state.animateScrollToItem(50) } |
scrollToItem(index) | 无动画滚动到指定项 | scope.launch { state.scrollToItem(0) } |
firstVisibleItemIndex | 获取当前可见的第一个项的索引 | state.firstVisibleItemIndex |
firstVisibleItemScrollOffset | 获取第一个可见项的滚动偏移量 | state.firstVisibleItemScrollOffset |
isScrollInProgress | 判断是否正在滚动 | state.isScrollInProgress |
完整示例:
@Composable
fun LazyColumnWithState() {
val dataList = List(1000) { "列表项 $it" }
// 创建列表状态(remember避免重组丢失)
val lazyListState = rememberLazyListState()
// 协程作用域(滚动操作是挂起函数)
val scope = rememberCoroutineScope()
Column {
// 控制按钮区
Row(modifier = Modifier.padding(8.dp)) {
Button(onClick = {
scope.launch {
// 滚动到顶部(带动画)
lazyListState.animateScrollToItem(index = 0)
}
}) {
Text("滚动到顶部")
}
Spacer(modifier = Modifier.width(8.dp))
Button(onClick = {
scope.launch {
// 滚动到第50项(无动画)
lazyListState.scrollToItem(index = 50)
}
}) {
Text("滚动到第50项")
}
}
// 绑定状态到LazyColumn
LazyColumn(
state = lazyListState,
modifier = Modifier.fillMaxSize()
) {
items(dataList) { item ->
Text(text = item, modifier = Modifier.padding(12.dp))
}
}
}
}
2. 粘性头部(Sticky Header)
实现 “滚动时头部固定在顶部” 的效果(如联系人列表的字母索引),核心 API 是 stickyHeader:
@Composable
fun LazyColumnWithStickyHeader() {
// 模拟分组数据(A/B/C组)
val groups = mapOf(
"A" to listOf("Apple", "Ant", "Air"),
"B" to listOf("Banana", "Bear", "Book"),
"C" to listOf("Cat", "Car", "Coffee")
)
LazyColumn {
groups.forEach { (groupName, items) ->
// 粘性头部(滚动时固定在顶部)
stickyHeader {
Text(
text = groupName,
modifier = Modifier
.fillMaxWidth()
.background(Color.Gray.copy(alpha = 0.8f))
.padding(8.dp),
color = Color.White,
fontWeight = FontWeight.Bold
)
}
// 分组内的列表项
items(items) { item ->
Text(text = item, modifier = Modifier.padding(12.dp))
}
}
}
}
3. 网格布局(LazyVerticalGrid)
LazyColumn 的网格版本,用于实现多列列表(如九宫格):
@Composable
fun LazyGridDemo() {
val dataList = List(100) { "网格项 $it" }
LazyVerticalGrid(
// 配置列数:两种方式
// 方式1:固定列数
columns = GridCells.Fixed(2), // 2列
// 方式2:自适应列数(每列最小宽度120dp)
// columns = GridCells.Adaptive(minSize = 120.dp),
// 可选:列间距/行间距
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxSize()
) {
items(dataList) { item ->
Text(
text = item,
modifier = Modifier
.fillMaxWidth()
.background(Color.LightGray)
.padding(16.dp)
.clip(RoundedCornerShape(8.dp)),
textAlign = TextAlign.Center
)
}
}
}
4. 动态数据刷新(MutableState)
配合 mutableStateListOf 实现数据动态刷新(自动重组列表):
@Composable
fun DynamicLazyColumn() {
// 可变状态列表(数据变化自动刷新)
val dataList = remember { mutableStateListOf<String>() }
// 初始化数据
LaunchedEffect(Unit) {
repeat(50) { dataList.add("动态项 $it") }
}
Column {
// 添加新项按钮
Button(onClick = {
dataList.add("新增项 ${dataList.size}")
}) {
Text("添加新项")
}
LazyColumn {
items(dataList) { item ->
Text(text = item, modifier = Modifier.padding(12.dp))
}
}
}
}
四、性能优化(避坑关键)
1. 给列表项设置唯一 Key(核心)
当列表数据增删 / 排序时,key 能避免 Compose 错误复用列表项,减少不必要的重组:
// 推荐:数据为实体类,用唯一ID作为key
data class Item(val id: Int, val content: String)
@Composable
fun LazyColumnWithKey() {
val dataList = List(100) { Item(it, "列表项 $it") }
LazyColumn {
// 关键:设置key为数据的唯一ID
items(dataList, key = { it.id }) { item ->
Text(text = item.content, modifier = Modifier.padding(12.dp))
}
}
}
2. 避免列表项内创建新对象 / 函数
每次重组都会生成新实例,导致列表项不必要的重组:
// 错误写法:每次重组创建新的onClick函数
LazyColumn {
items(dataList) { item ->
ListItemUI(
text = item,
onClick = { println("点击 $item") } // 每次重组新建函数
)
}
}
// 正确写法:用remember缓存函数
@Composable
fun ListItemUI(text: String, onClick: () -> Unit) {
// 缓存点击函数,避免重组
val cachedOnClick = remember { onClick }
Text(
text = text,
modifier = Modifier
.clickable { cachedOnClick() }
.padding(12.dp)
)
}
3. 抽离列表项为独立 Composable
缩小重组范围,仅当列表项数据变化时才重组,而非整个 LazyColumn:
// 推荐:独立的列表项Composable
@Composable
fun ItemCard(item: Item) {
// 仅当item变化时,此Composable才重组
Text(
text = item.content,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
)
}
// 使用:
LazyColumn {
items(dataList, key = { it.id }) { item ->
ItemCard(item) // 独立重组,不影响其他项
}
}
4. 避免过度绘制
- 列表项背景统一设置(如 LazyColumn 设背景,列表项不重复设);
- 减少列表项内的嵌套 Composable 层级;
- 图片使用
rememberAsyncImagePainter缓存(Coil/Glide)。
5. 限制列表项的重组范围
使用 remember/derivedStateOf 缓存列表项内的计算逻辑:
@Composable
fun ComplexListItem(item: Item) {
// 缓存计算结果,仅当item变化时重新计算
val processedText = remember(item) {
// 复杂计算(如文本格式化)
"处理后的:${item.content.uppercase()}"
}
Text(text = processedText, modifier = Modifier.padding(12.dp))
}
五、常见坑点 & 解决方案
| 问题现象 | 原因 | 解决方案 |
|---|---|---|
| 滚动到指定项无反应 | 滚动操作是挂起函数,未在协程中执行 | 用 rememberCoroutineScope() 创建协程,在 launch 中调用滚动 API |
| 列表项高度不一致导致滚动跳动 | 列表项高度动态变化,Compose 计算异常 | 固定列表项高度,或给 LazyColumn 设置 fillParentMaxSize() |
| 粘性头部失效 | stickyHeader 嵌套在其他函数中,未直接写在 LazyColumn DSL 内 | 将 stickyHeader 直接写在 LazyColumn 的 {} 内,不嵌套 |
| LazyColumn 嵌套在 Column 中滚动失效 | 内层 LazyColumn 无固定高度,撑满父容器 | 给 LazyColumn 设置固定高度(如 height(300.dp)) |
| 列表项点击事件不响应 | 列表项被遮罩层覆盖,或 pointerEvents 设置为 None | 检查 modifier 顺序,确保 clickable 在最外层,且未禁用 pointerEvents |
六、总结
-
核心定位:LazyColumn 是 Compose 高性能长列表组件,替代 RecyclerView,声明式写法更简洁;
-
基础用法:
- 核心 API:
items(遍历数据)、itemsIndexed(带索引)、item(固定项); - 状态管理:
rememberLazyListState()控制滚动、监听位置;
- 核心 API:
-
进阶能力:粘性头部(stickyHeader)、网格布局(LazyVerticalGrid)、动态数据刷新;
-
性能核心:
- 给列表项设置唯一
key; - 避免列表项内创建新对象 / 函数;
- 抽离列表项为独立 Composable,缩小重组范围;
- 给列表项设置唯一
-
避坑关键:滚动操作需在协程中执行,嵌套 LazyColumn 需设固定高度。