Compose Foundation
Compose 底层 · androidx.compose.foundation
滚动容器怎么选1.13.0-alpha01
区分 verticalScroll、LazyColumn、scrollable 与 nestedScroll。
最后更新 2026-08-01
内容规模决定容器
// 短内容:整体滚动,子项全部组合
Column(
Modifier.verticalScroll(rememberScrollState())
) {
settings.forEach { SettingRow(it) }
}
// 长内容:视口驱动,按需组合
LazyColumn {
items(messages) { MessageRow(it) }
}选择规则:
- 短表单/设置页(几十项以内)→
verticalScroll - 长列表/动态数据 →
LazyColumn - 网格 →
LazyVerticalGrid - 横向 → 对应
horizontalScroll/LazyRow
四层抽象
| API | 负责什么 |
|---|---|
verticalScroll / horizontalScroll | 内容测量、位移、手势、语义、滚动状态 |
| Lazy 列表/网格 | 视口驱动的按需内容(只组合可见项) |
scrollable | 消费滚动增量,但不自动移动内容 |
nestedScroll | 父子滚动前后分发与 fling 协调 |
关键区别:scrollable 只是”告诉你滚了多少”,不会移动任何内容——自定义控件加它必须自己根据 state 改变布局/绘制。verticalScroll 是”帮你滚”。
ScrollState 与 LazyListState
| 状态 | 适用 |
|---|---|
ScrollState | verticalScroll/horizontalScroll |
LazyListState / LazyGridState | Lazy 容器(可访问组合中的项) |
命令式滚动
ScrollState / LazyListState 都提供挂起的动画滚动 API,必须从协程调用:
val scope = rememberCoroutineScope()
val listState = rememberLazyListState()
// 回到顶部(动画)
scope.launch { listState.animateScrollToItem(0) }
// 立即跳转
scope.launch { listState.scrollToItem(50) }
// 滚动到具体 index(Lazy 专用)
scope.launch { listState.animateScrollToItem(index = itemIndex) }响应式滚动(nestedScroll)
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override suspend fun onPreFling(available: Velocity, state: ScrollState): Velocity {
// 子滚动前先消费:返回已消费部分
return Velocity.Zero
}
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource,
): Offset {
// 子滚动后:处理剩余位移
return Offset.Zero
}
}
}
Column(
Modifier.nestedScroll(nestedScrollConnection)
) {
// 子滚动容器
LazyColumn { ... }
}适合:AppBar 折叠、视差、无限加载触发器。
滚动位置与派生状态
val listState = rememberLazyListState()
val showTop by remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
// 当前可见项
val firstVisible = listState.firstVisibleItemIndex常见陷阱
- 长列表用 verticalScroll:全部组合,性能崩。
- scrollable 当滚动用:它不移动内容,需自己处理。
- scrollTo 不在协程:非挂起调用会编译错;在
scope.launch里。 - nestedScroll 连接忘传:只写在子容器上不生效,父要接 connection。
- 重建状态丢失:滚动位置用
rememberLazyListState(配合 rememberSaveable 保留跨配置)。
复制即用
@Composable
fun ScrollableList(messages: List<Message>) {
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
Box {
LazyColumn(state = listState) {
items(messages, key = { it.id }) { MessageRow(it) }
}
val showTop by remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
if (showTop) {
FloatingActionButton(
onClick = { scope.launch { listState.animateScrollToItem(0) } },
modifier = Modifier.align(Alignment.BottomEnd),
) { Text("↑") }
}
}
}要点
- 短内容 verticalScroll,长内容 LazyColumn;scrollable 不自动移动内容。
- 命令式滚动从协程调用 animateScrollToItem/scrollToItem。
- nestedScroll 连接父滚动的分发包;fling 协调用 onPreFling。
- 滚动位置派生状态用 derivedStateOf 避免重组。