Compose 延迟列表

LazyColumn(垂直滚动列表) 和 LazyRow(水平滚动列表)

延迟组件和Compose中的大多数布局不同,延迟组件不接受@composable内容块参数。而是lazyListScope.()块。 item() //用于添加单个列表 items(Int)用于添加多个列表

LazyVerticalGrid和LazyHorizontialGrid延迟网格 LazyGridScope.()来描述内容

在 Compose 中,延迟列表 指的是 LazyColumn、LazyRow 和 LazyVerticalGrid 等组件。它们用于高效地显示大型数据集 ,其核心原理是 "按需组合" :只组合和布局当前屏幕上(或即将进入屏幕)的列表项,对移出屏幕的项进行重用或释放,从而保证极致的性能和内存效率。

🎯 核心组件与选择

组件 方向 特点 适用场景
LazyColumn 垂直 最常见的垂直滚动列表。 聊天记录、新闻流、联系人列表。
LazyRow 水平 水平滚动列表。 图片轮播、横向分类标签。
LazyVerticalGrid 垂直网格 固定列数的网格布局。 相册、商品网格、表情面板。
LazyHorizontalGrid 水平网格 固定行数的水平网格。 日历的周视图、横向表格。

📝 基础用法示例

1. 最简单的列表

kotlin 复制代码
@Composable
fun SimpleMessageList(messages: List<String>) {
    LazyColumn {
        // items:用于迭代集合
        items(messages) { message ->
            Text(
                text = message,
                modifier = Modifier.padding(16.dp),
                style = MaterialTheme.typography.bodyMedium
            )
        }
    }
}

2. 带索引和复杂项的列表

kotlin 复制代码
@Composable
fun UserList(users: List<User>) {
    LazyColumn {
        itemsIndexed(
            items = users,
            key = { index, user -> user.id } // 关键:提供稳定且唯一的 key
        ) { index, user ->
            UserCard(user = user, index = index)
        }
    }
}

@Composable
fun UserCard(user: User, index: Int) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp, vertical = 8.dp)
    ) {
        Row(modifier = Modifier.padding(16.dp)) {
            Text(text = "${index + 1}.", modifier = Modifier.width(24.dp))
            Column(modifier = Modifier.weight(1f)) {
                Text(text = user.name, style = MaterialTheme.typography.titleMedium)
                Text(text = user.email, style = MaterialTheme.typography.bodySmall)
            }
        }
    }
}

⚙️ 关键特性与高级用法

1. 内容块与多种项类型

LazyColumn 等的内容 DSL 非常灵活,支持插入不同项。

kotlin 复制代码
LazyColumn {
    // 单个项
    item {
        HeaderTitle(title = "用户列表")
    }
    // 多个相同类型的项(items)
    items(activeUsers) { user ->
        ActiveUserItem(user)
    }
    // 另一个单个项(如分隔线)
    item {
        Divider(modifier = Modifier.padding(vertical = 8.dp))
    }
    // 另一组项,甚至类型不同
    items(inactiveUsers) { user ->
        InactiveUserItem(user)
    }
    // 粘性头部(需结合 stickyHeader)
    stickyHeader {
        CategoryHeader("已归档")
    }
    items(archivedUsers) { user ->
        UserItem(user)
    }
}

2. 项键(Key)------ 性能与状态保持的核心

为 items 或 itemsIndexed 提供 key 参数是最佳实践,它对性能和正确性至关重要:

  • 作用 :
    1. 智能重组 :当数据顺序变化时(如排序、插入、删除),Compose 能通过键识别出哪些项是"移动"而非"全新创建",从而复用已有项的组合和状态,避免不必要的重组和状态丢失。
    2. 状态保持 :如果列表项内部有状态(如 remember),键能确保该状态在数据位置变化时依然跟随正确的数据项。
kotlin 复制代码
// ✅ 正确做法:为每个项提供唯一、稳定的键
items(
    items = books,
    key = { book -> book.isbn } // 使用唯一标识,而非索引!
) { book ->
    BookItem(book)
}

3. 控制布局与滚动状态

  • 内容间距与内边距 :

    kotlin 复制代码
    LazyColumn(
        verticalArrangement = Arrangement.spacedBy(8.dp), // 项间距
        contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp) // 内容内边距
    ) { ... }
  • 监听与控制滚动 :

    kotlin 复制代码
    val listState = rememberLazyListState()
    
    LazyColumn(state = listState) { ... }
    
    // 例如:监听是否滚动到底部
    LaunchedEffect(listState) {
        snapshotFlow { listState.layoutInfo.visibleItemsInfo }
            .collect { visibleItems ->
                if (visibleItems.lastOrNull()?.index == listState.layoutInfo.totalItemsCount - 1) {
                    onLoadMore() // 触发加载更多
                }
            }
    }
    // 或者编程式滚动
    fun scrollToTop() {
        scope.launch { listState.animateScrollToItem(0) }
    }

🚀 性能优化指南

优化项 做法 理由
提供唯一键 key = { item -> item.id } 启用项重用,避免状态错乱和不必要重组。
避免过度组合 使用 derivedStateOf 或 remember 缓存列表项内部的昂贵计算。 减少每次重组的计算量。
保持项轻量 避免在项的可组合函数中直接读取大型资源或发起网络请求。 保证滚动流畅性。
使用 contentType contentType = { item -> item.type } 帮助 Compose 只复用同类型的项,进一步提升效率。
固定项尺寸 尽可能为项指定固定高度/宽度或使用 fillMaxWidth。 避免动态测量,滚动更平滑。
惰性加载图片 使用 Coil 或 Glide 的 Compose 扩展,它们内置了延迟加载优化。 图片是常见的性能瓶颈。

💎 与 Column 的根本区别

理解延迟列表,关键要明白它和普通 Column 的本质差异:

特性 Column / Row LazyColumn / LazyRow
工作方式 立即组合:一次性组合所有子项,无论是否在屏幕内。 延迟组合:只组合可见(或即将可见)项。
性能 数据集很大时,会严重卡顿并耗尽内存。 处理大型数据集流畅,内存占用恒定。
滚动 需嵌套在 Column 等可滚动容器内。 自身支持滚动,并管理滚动状态。
重组 状态变化可能导致整个列表重组。 智能作用域重组,通常只影响变化的项。
适用场景 少量、固定的子项(如设置页的5个选项)。 大量、可能动态变化的数据项(如消息列表)。

简单总结:对于任何可能超出屏幕范围的数据集,都应使用延迟列表。

🧠 最佳实践与常见陷阱

一定要做的:

  1. 始终提供唯一且稳定的 key。
  2. 将列表的状态提升到 ViewModel 中管理。
  3. 使用 collectAsStateWithLifecycle() 在 UI 层收集数据流。

需要避免的:

kotlin 复制代码
// ❌ 陷阱1:在列表项内部执行副作用(如 LaunchedEffect 发起请求)
items(users) { user ->
    UserItem(user)
    LaunchedEffect(user) { fetchDetail(user.id) } // 错误!会为每个可见项重复触发
}

// ❌ 陷阱2:使用不稳定的键(如随机数或索引本身)
key = { index -> index } // 索引在数据增删时会"漂移",导致状态错乱

// ❌ 陷阱3:在项内容中直接进行昂贵转换
items(list) { data ->
    val expensiveResult = performHeavyCalculation(data) // 每次重组都计算!
    ItemContent(expensiveResult)
}
// ✅ 正确:将转换提升到列表外部或使用 remember 缓存

掌握延迟列表的关键在于理解其 "按需组合" 和 "项重用" 的核心理念,并通过提供正确的 key 和遵循性能优化原则,使其发挥最大效能。

相关推荐
大圣编蚕7 小时前
初探 NDK 的世界:从零开始理解 Android 原生开发
android
云贝贝贝7 小时前
MySQL 慢查询定位与索引优化实战
android
骑着蜗牛撵大象3278 小时前
服务下线不再炸:分层关闭、TCP 存活探测与负载均衡排水的落地套路
android·tcp/ip·负载均衡·tcp·高可用·健康检查·优雅关闭
三少爷的鞋11 小时前
别再这么写协程了!Marcin Moskała 剖析的 几个常见协程误区与重构
android
mmsx12 小时前
Android 防二次打包第一道锁:签名校验与完整性校验
android·kotlin
mmsx12 小时前
Android 混淆不等于安全:二次打包链路完整走一遍
android·kotlin
小宋102114 小时前
Agent轨迹级评测实战:工具选择、预算超限与回归门禁
android·网络·人工智能·回归
墨天梦16 小时前
D05_ViewModel与单向数据流
android·kotlin
蒸鱼Yuzheng17 小时前
Android 构建可复现性:APK 指纹、文件级差异与供应链审计
android·apk·devops·软件供应链·可复现构建
墨天梦19 小时前
D03_Compose列表与稳定身份
android·gitee·kotlin