Android LazyColumn的使用

一、核心原理(理解底层逻辑)

1. 与 Column 的核心差异(必懂)

特性 Column LazyColumn
渲染逻辑 一次性渲染所有子项(无论是否可见) 仅渲染屏幕可见项(约 10-20 个),滑动时销毁出屏项、创建入屏项
内存占用 随子项数量线性增长(1000 项 = 1000 个 Composable) 恒定(仅保留可见项 + 少量缓存项)
启动耗时 子项越多越慢(需初始化所有组件) 秒开(仅初始化可见项)
滚动能力 需手动加 verticalScroll,无复用 内置滚动,自带项复用逻辑
特殊能力 无(仅基础布局) 粘性头部、精准滚动、网格布局

2. 复用机制

LazyColumn 内部通过 LazyListLayout 实现项复用:

  • 维护「可见项列表」和「缓存池」,出屏的列表项会被放入缓存池,入屏时优先从缓存池复用,而非重新创建;
  • 复用粒度是「Composable 项」,无需像 RecyclerView 一样手动写 Adapter/ViewHolder,Compose 自动处理。

二、基础用法(必掌握,覆盖 80% 场景)

1. 最简长列表(静态数据)

scss 复制代码
@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

ini 复制代码
LazyColumn {
    itemsIndexed(dataList) { index, item ->
        Text(
            text = "索引 $index:$item",
            modifier = Modifier.padding(12.dp)
        )
    }
}

3. 混合固定项 + 动态项(item + items)

支持 "固定头部 / 底部 + 动态列表" 的混合布局:

ini 复制代码
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 修饰符:

scss 复制代码
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

完整示例:

scss 复制代码
@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

less 复制代码
@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 的网格版本,用于实现多列列表(如九宫格):

scss 复制代码
@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 实现数据动态刷新(自动重组列表):

scss 复制代码
@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 错误复用列表项,减少不必要的重组:

kotlin 复制代码
// 推荐:数据为实体类,用唯一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. 避免列表项内创建新对象 / 函数

每次重组都会生成新实例,导致列表项不必要的重组:

kotlin 复制代码
// 错误写法:每次重组创建新的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:

scss 复制代码
 // 推荐:独立的列表项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 缓存列表项内的计算逻辑:

kotlin 复制代码
@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

六、总结

  1. 核心定位:LazyColumn 是 Compose 高性能长列表组件,替代 RecyclerView,声明式写法更简洁;

  2. 基础用法

    • 核心 API:items(遍历数据)、itemsIndexed(带索引)、item(固定项);
    • 状态管理:rememberLazyListState() 控制滚动、监听位置;
  3. 进阶能力:粘性头部(stickyHeader)、网格布局(LazyVerticalGrid)、动态数据刷新;

  4. 性能核心

    • 给列表项设置唯一 key
    • 避免列表项内创建新对象 / 函数;
    • 抽离列表项为独立 Composable,缩小重组范围;
  5. 避坑关键:滚动操作需在协程中执行,嵌套 LazyColumn 需设固定高度。

相关推荐
杉氧16 小时前
副作用 (Side Effects) 全攻略:如何像大师一样掌控 Composable 的生命周期?
android·架构·android jetpack
杉氧2 天前
深入理解 Compose 重组机制:快照系统如何驱动 UI 精准刷新?
android·架构·android jetpack
杉氧2 天前
深度解析:Jetpack Compose 核心架构与底层原理 —— 十年安卓老兵的“破茧重生”
android·架构·android jetpack
李斯维4 天前
从历史的角度看 Android 软件架构
android·架构·android jetpack
alexhilton4 天前
Android车载OS中的Remote Compose
android·kotlin·android jetpack
alexhilton11 天前
使用Android Archive进行打包
android·kotlin·android jetpack
Junerver14 天前
我写了一个 Compose Multiplatform 组件库,你可能会用到
kotlin·android jetpack
我命由我1234515 天前
Jetpack Room - Room 查询返回列表无需判空、LIKE 关键字
android·java·开发语言·java-ee·android jetpack·android-studio·android runtime
QING61816 天前
Kotlin 日常开发常用语法糖整理 —— 速记
android·kotlin·android jetpack
我命由我1234516 天前
Android 开发问题:EditText 控件的 android:imeOptions=“actionDone“ 属性不生效
android·java·java-ee·android studio·android jetpack·android-studio·android runtime