Android LazyColumn的使用

5 阅读4分钟

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

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

特性ColumnLazyColumn
渲染逻辑一次性渲染所有子项(无论是否可见)仅渲染屏幕可见项(约 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

六、总结

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

  2. 基础用法

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

  4. 性能核心

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