4. 基础瀑布流实现代码:LazyVerticalStaggeredGrid 基本用法、item 布局绑定、滚动方向与跨列设置
本章引导你通过 Compose 的 LazyVerticalStaggeredGrid 组件,从零搭建一个标准瀑布流布局。我们将依次拆解构造器参数、item 绑定方式、滚动方向控制以及跨列行为的设定,确保后续所有优化实践都建立在正确的 API 之上。
4.1 核心原理:StaggeredGrid 与 LazyList 的差异
关键认知:普通
LazyVerticalGrid 强制所有行等高、所有列等宽;LazyVerticalStaggeredGrid 允许每一列内 item 高度自由变化,形成“参差排列”的瀑布流效果。
底层通过两个度量策略实现:
- Fixed(固定跨度):每列宽度一致,item 高度随内容撑开,列间无水平对齐。
- Adaptive(自适应跨度):根据 item 自身宽高比动态分配列数,较少用于瀑布流,本文以 Fixed 为主。
4.2 基础代码骨架
在 build.gradle.kts 中确认已引入最新 foundation 依赖(含 StaggeredGrid):
// build.gradle.kts (module)
dependencies {
implementation("androidx.compose.foundation:foundation:1.7.0-rc01")
}
4.3 核心 API 参数详解
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
columns | StaggeredGridCells | 列定义:Fixed(n) / Adaptive(minSize) | — (必填) |
modifier | Modifier | 布局修饰,如 .fillMaxSize() | Modifier |
contentPadding | PaddingValues | 整体内边距 | PaddingValues(0.dp) |
horizontalArrangement | Arrangement.Horizontal | 列间水平排列(常用 SpaceEvenly) | Arrangement.Start |
verticalItemSpacing | Dp | 垂直方向上 item 间距 | 0.dp |
userScrollEnabled | Boolean | 是否允许用户滚动 | true |
flingBehavior | FlingBehavior | 滚动后惯性行为(可用于自定义减速) | 默认 fling |
4.4 逐步实现示例
步骤 1:定义数据模型与假数据
data class WaterfallItem(
val imageUrl: String,
val title: String,
val height: Int // 模拟不同高度
)
private fun generateDemoItems(): List<WaterfallItem> {
return (0..99).map { index ->
WaterfallItem(
imageUrl = "https://picsum.photos/300?random=$index",
title = "Item #$index",
height = (150..350).random()
)
}
}
步骤 2:构建 StaggeredGrid 主体
@Composable
fun WaterfallScreen() {
val items = remember { generateDemoItems() }
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2), // 固定2列
modifier = Modifier.fillMaxSize().padding(16.dp),
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalItemSpacing = 12.dp,
userScrollEnabled = true
) {
items(items, key = { it.title }) { item ->
WaterfallCard(item)
}
}
}
步骤 3:item 布局绑定(关键)
每个 item 必须通过 Modifier.fillMaxWidth() 确保宽度占满所在列;高度由内容自然撑起,或用 .height(item.height.dp) 模拟不同高度。
@Composable
fun WaterfallCard(item: WaterfallItem) {
Card(
modifier = Modifier
.fillMaxWidth() // 关键:占满所在列宽度
.padding(4.dp),
elevation = CardDefaults.cardElevation(4.dp)
) {
Column {
Image(
painter = rememberAsyncImagePainter(item.imageUrl),
contentDescription = item.title,
modifier = Modifier
.fillMaxWidth()
.height(item.height.dp), // 模拟瀑布流高度变化
contentScale = ContentScale.Crop
)
Text(
text = item.title,
modifier = Modifier.padding(8.dp),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
4.5 滚动方向与跨列设置
滚动方向切换:
使用 LazyHorizontalStaggeredGrid 可实现水平瀑布流,参数结构与垂直版本对称(rows 代替 columns)。
// 水平瀑布流示例
LazyHorizontalStaggeredGrid(
rows = StaggeredGridCells.Fixed(3),
modifier = Modifier.fillMaxSize(),
horizontalItemSpacing = 12.dp
) {
items(20) { index ->
Box(modifier = Modifier.width((100..200).random().dp).height(120.dp)) {
Text("H$index")
}
}
}
跨列设置(span):
通过 item 的 span 参数让某个 item 占据多列宽度,常用于“大图”或“广告位”。
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(3),
modifier = Modifier.fillMaxSize()
) {
// 普通 item 占 1 列
items(10) { ... }
// 跨列 item:占据 2 列
item(span = StaggeredGridItemSpan.FullLine) { // FullLine 占整行
FeatureBannerCard()
}
// 或自定义跨度数(仅 Fixed 模式下可用)
item(span = StaggeredGridItemSpan(2)) {
WideCard()
}
// 继续普通 item
items(10) { ... }
}
⚠ 关键约束:
StaggeredGridItemSpan 的跨度数不能超过总列数;跨列 item 会强制占据连续列,可能导致该行剩余列出现空位。在 StaggeredGridCells.Adaptive 下请使用 FullLine,自定义跨度可能引发布局异常。
4.6 常见错误与调试技巧
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
| item 显示不全或溢出 | item 未设置 fillMaxWidth(),宽度默认为 wrap | item 根元素添加 Modifier.fillMaxWidth() |
| 跨列 item 位置错乱 | 在 Adaptive 模式使用了自定义 span | 改用 FullLine 或切换到 Fixed |
| 滚动卡顿、布局跳动 | 未提供 key 参数,Compose 无法稳定辨识 item | 为 items 添加 key = { it.id } |
| 列间距不生效 | 用了 horizontalArrangement 但缺少 padding | 确保 contentPadding 和 verticalItemSpacing 都设置 |
4.7 性能基线检查
✅ 验证 checklist:
- 确认每屏 item 数量 ≤ 20(瀑布流高度不同,测量成本更高)
- 图片使用
rememberAsyncImagePainter或 Coil,避免同步加载 - 检查 Layout Inspector 中是否出现
Recompose次数过多