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

0 阅读3分钟

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

1. 关键入参

  1. contentPadding:使得 item 可覆盖间距。

image.png

  1. verticalArrangement:通常用 spacedBy,设置子项间间距

  1. reverseLayout:倒着排版

  1. LazyListState:控制与监听列表
  • 跳转;控制点击 item 时滑动到对应 item。
@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(减少重建次数)

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

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])
    }
}

点击「改名」:“张三” 改名为 “张三✓” -> users[0]发生改变 -> 触发两个列表重组。

这是 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(张三)为例:

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

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

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

读取 users[1] → 得到 User(1, "张三✓")

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

→ 数据没变 → 重组

而对于没有 key 的场景:

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

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

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

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

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

...

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

3. contentType(减少重建次数)

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

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

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

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