Lazy 列表 · AndroidX 源码指南
AAndroidX 源码指南
Compose Foundation
Compose 底层 · androidx.compose.foundation

Lazy 列表1.13.0-alpha01

用稳定 key、contentType、LazyListState 和分页结构建立可维护的长列表。

最后更新 2026-08-01

最小结构

val listState = rememberLazyListState()

LazyColumn(state = listState) {
    items(
        items = messages,
        key = { it.id },
        contentType = { it.kind },
    ) { message ->
        MessageRow(message)
    }
}

Lazy 容器根据视口按需组合和布局,适合数量多或规模不定的集合。

items 变体

函数用途
item { }单个头部/尾部
items(list, key, contentType)普通列表
itemsIndexed需要 index 时
items(count)按数量(如骨架屏)
stickyHeader { }粘性头部(LazyColumn 专属)
LazyColumn {
    item(key = "header") { Header() }
    items(items = data, key = { it.id }, contentType = { "row" }) { Row(it) }
    item(key = "footer") { Footer() }
}

key 保护状态身份

列表头部插入项目后,没有稳定 key 时原位置的 remember 状态跟随位置而不是业务项:

// ❌ 位置身份:头部插入一条后,后续项状态错位
items(messages) { MessageRow(it) }

// ✅ 业务身份:id 不变状态就不变
items(messages, key = { it.id }) { MessageRow(it) }

key 要求:唯一、稳定,类型满足状态保存要求(可序列化/可比较)。

contentType 帮助复用

混合标题、图片卡片、普通行时:

LazyColumn {
    items(
        items = feed,
        contentType = { item ->
            when (item) {
                is FeedItem.Header -> "header"
                is FeedItem.Card -> "card"
                else -> "row"
            }
        },
    ) { ... }
}
  • contentType 告诉实现哪些 item 结构可互相复用(减少重组/重绘)。
  • 只有多种布局类型时才需要;不要为每个 item 生成唯一类型(会破坏复用)。

读取滚动状态

// UI 判断:derivedStateOf,滚动时才重算
val showTop by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 0 }
}

// 埋点/触发:snapshotFlow 流式处理
LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .distinctUntilChanged()
        .collect { index -> analytics.onScroll(index) }
}

不要在组合中高频读 layoutInfo——每次滚动都会让读取范围重组。

常见陷阱

  • 无 key:插入/重排后状态错位。
  • contentType 每项唯一:破坏复用,反而更慢。
  • 组合中读 layoutInfo:滚动全程重组。
  • Lazy 里嵌无界高度 ColumnLazyColumn { item { Column(Modifier.fillMaxHeight()) } } 可能崩或行为怪异——用固定高度或 heightIn(max)
  • 乱用 scrollToItem 大 index:跳很远用 scrollToItem,就近用 animateScrollToItem

复制即用

@Composable
fun MessageList(messages: List<Message>) {
    val listState = rememberLazyListState()
    val scope = rememberCoroutineScope()

    LazyColumn(
        state = listState,
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        item(key = "header") { ListHeader() }
        items(
            items = messages,
            key = { it.id },
            contentType = { "message" },
        ) { message ->
            MessageRow(message)
        }
    }
}

要点

  • Lazy 容器按需组合;items 变体覆盖头部/列表/粘性头。
  • key 用稳定业务 id 保护状态身份。
  • contentType 区分布局类型帮助复用;别每项唯一。
  • 滚动状态用 derivedStateOf/snapshotFlow,别高频读 layoutInfo。

相关页面