Compose 组件:LazyColumn 核心参数与 key/contentType 详解

本文内容结合扔物线视频课程、与个人理解、ai案例。

1. 关键入参

  1. contentPadding:使得 item 可覆盖间距。
  1. verticalArrangement:通常用 spacedBy,设置子项间间距
  1. reverseLayout:倒着排版
  1. LazyListState:控制与监听列表
  • 跳转;控制点击 item 时滑动到对应 item。
kotlin 复制代码
@Preview
@Composable
private fun LazyRowTest() {
    val listState = rememberLazyListState()
    val scope = rememberCoroutineScope()
    LazyColumn(
        ...,
    state = listState
    ) {
        items(30) { index ->
            ListItem(index) {
                scope.launch {
                    listState.scrollToItem(index)
                }
            }
        }
    }
}
  • 监听第一个可见 item;

除了 firstVisibleItemIndex,还有 firstVisibleItemScrollOffset/layoutInfo 可以获取更多信息。

  1. userScrollEnabled

控制列表是否可以滑动

  1. flingBehavior

控制 fling 惯性滑动的物理引擎,默认情况是慢慢减速移动。例如可以控制 fling 速度、停止时将第一个可见的 item 吸附顶部。

  1. overscrollEffect

滑动过头继续滑动的效果。

2. key(减少重建次数)

首先,构造一个实验场景:

kotlin 复制代码
val users = remember {
    listOf(
        User(1, "张三"),
        User(2, "李四"),
        User(3, "王五"),
        User(4, "赵六"),
        User(5, "钱七"),
    ).toMutableStateList()
}

// 有 key 
LazyColumn(...) {
    items(users.size, key = { users[it].id } ) { index ->
        UserItem(users[index])
    }
}

// 无 key,默认 key 为 index
LazyColumn(...) {
    items(users.size) { index ->
        UserItem(users[index])
    }
}

点击「改名」:"张三" 改名为 "张三✓" -> users0发生改变 -> 触发两个列表重组。

这是 StateList 快照 的作用。

这时头部插入一个 item,没有 key 的列表项都发生了一次重组。

而有 key 的列表,没有发生新的重组。

这个过程中发生了什么:

  1. 列表数据变了,LazyColumn 重新遍历所有 item,调用 key lambda 生成新的 key 列表:

旧 key 列表:1, 2, 3, 4, 5

新 key 列表:105, 1, 2, 3, 4, 5

  1. 逐个匹配新 key → 旧 key

对新 key 列表中的每一个 key,去旧 key 列表里找:

新 key=105 → 旧列表里找不到 → 标记为「新建」

新 key=1 → 旧列表里找到了 → 标记为「复用」

新 key=2 → 旧列表里找到了 → 标记为「复用」

新 key=3 → 旧列表里找到了 → 标记为「复用」

新 key=4 → 旧列表里找到了 → 标记为「复用」

新 key=5 → 旧列表里找到了 → 标记为「复用」

  1. 对每个「复用」的 item,执行 composable lambda

这一步才是决定重不重组的关键。以 key=1(张三)为例:

scss 复制代码
items(users.size, key = { users[it].id }) { index ->                                  
      UserItem(users[index])  // ← 执行这个 lambda          
}

Compose 执行 lambda,里面会读取 usersindex。这时 Compose 的 State 快照系统介入:

key=1 的 item,现在 index 从 0 变成了 1

读取 users1 → 得到 User(1, "张三✓")

上次这个 item 读到的也是 User(1, "张三✓")

→ 数据没变 → 重组

而对于没有 key 的场景:

旧 key 列表:0, 1, 2, 3, 4

新 key 列表:0, 1, 2, 3, 4, 5

新 key=0 → 旧列表找到了 → 复用 → 但 users0 从"张三✓"变成"新人5" → 重组

新 key=1 → 旧列表找到了 → 复用 → 但 users1 从"李四"变成"张三✓" → 重组

新 key=2 → 旧列表找到了 → 复用 → 但 users2 从"王五"变成"李四" → 重组

...

新 key=5 → 旧列表找不到 → 新建

3. contentType(减少重建次数)

告诉 LazyColumn:不同 item 的布局类型是什么,以便更高效地复用组合节点。

假设列表有两种布局:文字消息和图片消息,交替排列。

diff 复制代码
index 0 → TextBubble   (一个 Text)
index 1 → ImageBubble  (一个 Image + 一个 Text)
index 2 → TextBubble
index 3 → ImageBubble
...
  1. 不填 contentType:

向下滚动,index 0 滑出,index 4 (TextBubble) 要滑入:

运气好:从池里拿到 TextBubble 节点 → 结构匹配 → 只更新数据 → 快

继续滚动,index 1 滑出,index 5 (ImageBubble) 要滑入:

运气差:池里先进先出,拿到的是 TextBubble 节点 → 结构不匹配 → 整个 composition 要拆掉重建 → 慢

  1. 填 contentType

回收池按类型分开,永远不会拿错类型。

相关推荐
wendZzoo13 小时前
前端工程师的 3D 第一课:一个模型如何进入网页
前端
颜进强14 小时前
04 · NestJS 依赖注入:你在 `@Module` 写的 providers,和构造参数里那个类型,是怎么"对上"的?
前端·后端·ai编程
肆仲冬14 小时前
不用框架,用 TypeScript 从零搭一个 Agent 框架
前端
林语琛14 小时前
我写的 switch…break 被 Babel 偷偷吞了
前端·javascript·babel
烈风逍遥14 小时前
第六篇:RAG 知识库构建与检索全链路
前端·人工智能·后端
爱丶不疚14 小时前
什么是 Jev 决策模型?它适合干什么?
前端·agent
江畔柳前堤14 小时前
字节跳动·大模型应用知识手册
前端·人工智能·深度学习·opencv·目标检测·重构·transformer
hai_android15 小时前
Android MeasureSpec 详解
android·java·kotlin
LEE15 小时前
前端转型全栈 03:接口失败也返回 200,OpenAPI 契约与错误码怎么定
前端·后端·全栈
我命由我1234515 小时前
CSS - CSS 媒体查询 orientation
前端·javascript·css·html·css3·html5·js