4. 基础瀑布流实现代码:LazyVerticalStaggeredGrid 基本用法、item 布局绑定、滚动方向与跨列设置

本章引导你通过 Compose 的 LazyVerticalStaggeredGrid 组件,从零搭建一个标准瀑布流布局。我们将依次拆解构造器参数、item 绑定方式、滚动方向控制以及跨列行为的设定,确保后续所有优化实践都建立在正确的 API 之上。

4.1 核心原理:StaggeredGrid 与 LazyList 的差异

关键认知:普通 LazyVerticalGrid 强制所有行等高、所有列等宽;LazyVerticalStaggeredGrid 允许每一列内 item 高度自由变化,形成“参差排列”的瀑布流效果。

底层通过两个度量策略实现:

  • Fixed(固定跨度):每列宽度一致,item 高度随内容撑开,列间无水平对齐。
  • Adaptive(自适应跨度):根据 item 自身宽高比动态分配列数,较少用于瀑布流,本文以 Fixed 为主。
LazyVerticalStaggeredGrid 布局策略对比 LazyVerticalGrid item 等高 item 等高 item 等高 行高固定 → 不适合瀑布流 LazyVerticalStaggeredGrid item H=60 item H=90 item H=40 item H=100 item H=50 item H=40 特征说明 • 每列独立测量剩余空间 • 新 item 默认放入当前最短列 • 通过 maxLineHeight 控制列高差 • 支持 crossAxisSize 设置跨列 • 方向:vertical / horizontal 👇 跨列:item 同时占据多列宽度

4.2 基础代码骨架

在 build.gradle.kts 中确认已引入最新 foundation 依赖(含 StaggeredGrid):

// build.gradle.kts (module)
dependencies {
    implementation("androidx.compose.foundation:foundation:1.7.0-rc01")
}

4.3 核心 API 参数详解

参数类型说明默认值
columnsStaggeredGridCells列定义:Fixed(n) / Adaptive(minSize)— (必填)
modifierModifier布局修饰,如 .fillMaxSize()Modifier
contentPaddingPaddingValues整体内边距PaddingValues(0.dp)
horizontalArrangementArrangement.Horizontal列间水平排列(常用 SpaceEvenly)Arrangement.Start
verticalItemSpacingDp垂直方向上 item 间距0.dp
userScrollEnabledBoolean是否允许用户滚动true
flingBehaviorFlingBehavior滚动后惯性行为(可用于自定义减速)默认 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(),宽度默认为 wrapitem 根元素添加 Modifier.fillMaxWidth()
跨列 item 位置错乱在 Adaptive 模式使用了自定义 span改用 FullLine 或切换到 Fixed
滚动卡顿、布局跳动未提供 key 参数,Compose 无法稳定辨识 item为 items 添加 key = { it.id }
列间距不生效用了 horizontalArrangement 但缺少 padding确保 contentPadding 和 verticalItemSpacing 都设置

4.7 性能基线检查

✅ 验证 checklist:
  1. 确认每屏 item 数量 ≤ 20(瀑布流高度不同,测量成本更高)
  2. 图片使用 rememberAsyncImagePainter 或 Coil,避免同步加载
  3. 检查 Layout Inspector 中是否出现 Recompose 次数过多