鸿蒙原生 ArkTS 布局实战:Grid + WaterFlow 混合布局(顶部网格 + 底部瀑布流)

一、引言

在移动端应用开发中,混合布局是首页设计的常见模式。典型的「顶部网格分类 + 底部瀑布流内容推荐」布局,既能高效展示多入口(Grid),又能以错落有致的卡片流(WaterFlow)承载大量内容,兼顾信息密度视觉层次

HarmonyOS NEXT 的 ArkUI 声明式框架提供了 GridWaterFlow 两个高性能布局容器,配合 Column 纵向容器,可以非常优雅地实现这一经典布局。

本文从零开始构建一个完整的示例应用,深入剖析每个组件的配置要点及混合布局的实现细节。


二、项目环境

项目 版本
操作系统 HarmonyOS NEXT
API 版本 24(对应 SDK 7.0.0)
开发工具 DevEco Studio NEXT
构建系统 hvigor 6.23+
语言 ArkTS(声明式 UI + 装饰器语法)

build-profile.json5 中确认 SDK 配置:

{
  "app": {
    "products": [
      {
        "name": "default",
        "targetSdkVersion": "7.0.0(24)",
        "compatibleSdkVersion": "7.0.0(24)",
        "runtimeOS": "HarmonyOS"
      }
    ]
  }
}

三、整体布局架构

我们使用三层嵌套实现完整的混合布局:

Column(根容器,撑满全屏)
├── Column(顶部网格区域)
│   ├── Row          ← "🔥 热门分类" 标题栏
│   └── Grid         ← 2×2 固定网格(不滚动)
│       ├── GridItem → 新品推荐(圆角卡片)
│       ├── GridItem → 热门榜单
│       ├── GridItem → 限时特惠
│       └── GridItem → 每日签到
│
└── Column(底部瀑布流区域,layoutWeight 占满剩余空间)
    ├── Row          ← "✨ 为你推荐" 标题栏
    └── WaterFlow    ← 2 列瀑布流(自管理滚动)
        ├── FlowItem → 卡片 1(200vp)
        ├── FlowItem → 卡片 2(280vp)
        ├── FlowItem → 卡片 3(160vp)
        └── ...      → 共 12 项,高度 160~310vp 不等

关键设计原则

  • Grid:固定高度,不参与页面滚动——作为「顶部导航」区域,始终可见或随页面整体滚动
  • WaterFlowlayoutWeight(1) 填满剩余空间,完全接管其内容区域的纵向滚动
  • Column:作为串联容器,将两个布局区域纵向排列

四、数据模型层(@Observed 装饰器)

ArkTS 中使用 @Observed 装饰类,使其属性变化可被 UI 观察。这是 @State 数组深度观测的前提。

@Observed
class GridCategory {
  name: string;
  icon: string;
  color: number;

  constructor(name: string, icon: string, color: number) {
    this.name = name;
    this.icon = icon;
    this.color = color;
  }
}

@Observed
class WaterfallItem {
  title: string;
  description: string;
  height: number;  // 卡片高度,单位 vp —— 控制瀑布流错落效果
  color: number;   // 卡片色调

  constructor(title: string, description: string, height: number, color: number) {
    this.title = title;
    this.description = description;
    this.height = height;
    this.color = color;
  }
}

为什么用 @Observed
在 ArkTS 中,@State 装饰的数组只观测引用变化(如 pushsplice、重新赋值)。当数组中对象的属性发生变化时,需要 @Observed 装饰该类,才能触发 UI 刷新。虽然本示例不涉及动态属性修改,但这是生产环境最佳实践。


五、顶部 Grid 网格布局详解

5.1 布局配置

Grid() {
  ForEach(this.gridList, (item: GridCategory) => {
    GridItem() {
      this.GridCell(item);
    }
  }, (item: GridCategory, index?: number) => index!.toString());
}
.columnsTemplate('1fr 1fr')  // 2 列等宽
.rowsTemplate('1fr 1fr')     // 2 行等高
.columnsGap(12)              // 列间距 12vp
.rowsGap(12)                 // 行间距 12vp
.height(180)                 // 固定高度,不滚动
.clip(true)                  // 超限裁剪
.padding({ left: 16, right: 16 });

5.2 关键属性解析

属性 说明
columnsTemplate '1fr 1fr' 两列等宽,fr 是网格的弹性单位,1fr 表示等分剩余空间
rowsTemplate '1fr 1fr' 两行等高
columnsGap 12 列与列之间 12vp 间距
rowsGap 12 行与行之间 12vp 间距
height 180 固定高度——与 rowsTemplate 配合,确保 Grid 不自带滚动
clip true 超出高度部分裁剪,避免与下方 WaterFlow 重叠

5.3 「固定高度不滚动」的意义

Grid 组件默认拥有自己的滚动行为——当内容超出设定高度时会内部滚动。但在混合布局中,我们不希望 Grid 独立滚动,而是让整体由 WaterFlow 统一管理滚动。因此设置 height(180) + clip(true),将 Grid 限制为 2×2 静态网格。

5.4 Grid 单元格(@Builder 子组件)

@Builder
GridCell(item: GridCategory) {
  Column() {
    // 圆形图标背景
    Text(item.icon)
      .fontSize(32)
      .width(48).height(48)
      .backgroundColor(Color.White)
      .borderRadius(24);
    // 分类名称
    Text(item.name)
      .fontSize(14)
      .fontWeight(FontWeight.Medium)
      .fontColor('#333333')
      .margin({ top: 8 });
  }
  .width('100%').height('100%')
  .justifyContent(FlexAlign.Center)
  .alignItems(HorizontalAlign.Center)
  .backgroundColor(item.color)
  .borderRadius(14)
  .shadow({ radius: 6, offsetX: 0, offsetY: 3, color: 0x22000000 });
}

设计要点

  • @Builder 将 UI 抽取为独立子组件,避免 build() 方法膨胀
  • 白色圆形背景托起 emoji 图标,形成「图标在卡片中央」的视觉效果
  • 半透明阴影(0x22 为 alpha 通道,对应约 13% 不透明度)增加层次感
  • .width('100%').height('100%') 使单元格撑满 GridItem 的区域

六、底部 WaterFlow 瀑布流布局详解

6.1 布局配置

WaterFlow() {
  ForEach(this.waterfallList, (item: WaterfallItem) => {
    FlowItem() {
      this.WaterfallCell(item);
    }
  }, (item: WaterfallItem, index?: number) => index!.toString());
}
.columnsTemplate('1fr 1fr')  // 2 列等宽
.columnsGap(10)              // 列间距 10vp
.rowsGap(10)                 // 行间距 10vp
.layoutWeight(1)             // 填满 Column 剩余空间
.width('100%')
.padding({ left: 12, right: 12 })
.backgroundColor('#F5F5F5')
.onReachEnd(() => {          // 触底加载更多
  console.info('[WaterFlow] 触底,可在此加载更多数据');
})
.onReachStart(() => {        // 到顶
  console.info('[WaterFlow] 到达顶部');
})
.onScrollIndex((firstIndex: number, lastIndex: number) => {
  console.info(`[WaterFlow] 可见区间: [${firstIndex}, ${lastIndex}]`);
});

6.2 核心属性解析

属性 说明
columnsTemplate '1fr 1fr' 两列等宽,和 Grid 语法一致
columnsGap 10 列间距
rowsGap 10 行间距(WaterFlow 中表现为垂直间距)
layoutWeight 1 在 Column / Row / Flex 中占据剩余比例空间
onReachEnd 回调 上拉加载更多的入口,适合数据分页
onReachStart 回调 下拉刷新判断点
onScrollIndex 回调 获取当前可见区间,可用于埋点或懒加载

6.3 WaterFlow 的工作原理

WaterFlow 是 ArkUI 提供的高性能瀑布流容器,其核心机制:

  1. 自动布局:按 columnsTemplate 分列,每个 FlowItem 从上到下依次排入当前最短列
  2. 按需渲染:只渲染可见区域的 FlowItem,内存与性能优于手动计算位置
  3. 自管理滚动:无需外层 Scroll 包裹,自身处理手势滚动
  4. 高度自适应:每个 FlowItem 的高度独立计算,形成错落有致的「瀑布」效果

6.4 瀑布流卡片(@Builder 子组件)

@Builder
WaterfallCell(item: WaterfallItem) {
  Column() {
    // 模拟图片区域
    Column()
      .width('100%')
      .height(item.height - 80)  // 根据 item.height 动态计算图片区
      .borderRadius({ topLeft: 12, topRight: 12 })
      .backgroundColor(item.color);

    // 文字内容区域
    Column() {
      Text(item.title)
        .fontSize(15).fontWeight(FontWeight.Bold)
        .maxLines(1)
        .textOverflow({ overflow: TextOverflow.Ellipsis });
      Text(item.description)
        .fontSize(12).lineHeight(18)
        .maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .margin({ top: 4 });
    }
    .padding({ left: 10, right: 10, top: 8, bottom: 10 })
    .width('100%')
    .layoutWeight(1);
  }
  .width('100%')
  .height(item.height)          // 每个卡片独立高度
  .backgroundColor(Color.White)
  .borderRadius(12)
  .shadow({ radius: 4, offsetX: 0, offsetY: 2, color: 0x15000000 });
}

错落感来源:12 张卡片的高度从 160vp 到 310vp 不等,WaterFlow 自动将每个卡片排入当前最短的列,产生自然的「瀑布」错落效果。

数据示例:
晨曦山峦  200vp │ 城市夜景  280vp
海洋日记  160vp │ 林间小径  240vp
星空物语  300vp │ 咖啡时光  180vp
花海漫游  260vp │ 古建筑韵  220vp
美食探索  190vp │ 极光之舞  310vp
沙漠驼铃  170vp │ 湖畔垂钓  250vp

七、混合布局的尺寸管理

7.1 layoutWeight 的妙用

本示例中最关键的尺寸管理手段是 layoutWeight

Column() {
  // ... 顶部 Grid 区域(自然高度)
}
// ...(没有 layoutWeight,取自身高度)

Column() {
  // ... 标题
  WaterFlow()
    .layoutWeight(1)  // WaterFlow 填满此 Column
}
.layoutWeight(1)       // 此 Column 填满根 Column 的剩余空间

工作流程

  1. Column 高度为 100%(全屏)
  2. 顶部 Grid 区域按内容自然高度渲染(约 240vp)
  3. 底部 WaterFlow 区域的 Column 设置了 .layoutWeight(1),自动占据剩余全部空间
  4. 该 Column 内部的 WaterFlow 同样设置了 .layoutWeight(1),填满 Column 内剩余高度
  5. WaterFlow 在自己的有效区域内管理滚动,超出的卡片内容通过内部滚动查看

7.2 为什么用两层 Column?

有人可能会问:为什么不在根 Column 直接放 WaterFlow?

// ❌ 不推荐的简化写法
Column() {
  Grid()     // 顶部网格
    .height(180);     // 固定高度
  WaterFlow()         // 瀑布流
    .layoutWeight(1); // 填满剩余空间
}

这种写法理论上可行,但实际使用中存在两个问题:

  1. 标题缺失:Grid 和 WaterFlow 各自需要标题栏(“热门分类”、“为你推荐”),需要额外的布局容器
  2. 样式隔离:顶部 Grid 区域有白色背景 + 圆角,底部 WaterFlow 区域有灰色背景,用两层 Column 可以天然隔离样式作用域

因此两层 Column 的结构在可维护性和视觉还原度上更优。


八、API 24 新增特性与最佳实践

HarmonyOS NEXT API 24(SDK 7.0.0)在布局方面带来以下改进:

8.1 性能优化

  • WaterFlow 的懒加载性能提升:配合 LazyForEach 实现真正按需渲染,千级数据量也不卡顿
  • Grid 的复用机制GridItem 支持节点复用,减少频繁建销毁的开销

8.2 推荐使用 @ObservedV2 + @Trace

虽然本示例使用 @Observed(它在 API 24 中仍然兼容),但 API 24 推荐使用新的装饰器体系:

@ObservedV2
class WaterfallItem {
  @Trace title: string = '';
  @Trace description: string = '';
  @Trace height: number = 0;
  @Trace color: number = 0;
}

@ObservedV2 + @Trace 的粒度更细,仅标记需要观测的属性,性能开销更低。

8.3 WaterFlow onReachEnd 的分页实践

在实际项目中,onReachEnd 可以配合分页加载:

@State page: number = 1;
@State isLoading: boolean = false;

WaterFlow() {
  // ...
}
.onReachEnd(() => {
  if (this.isLoading) return;
  this.isLoading = true;
  this.page++;
  loadMoreData(this.page).then((newItems) => {
    this.waterfallList.push(...newItems);
    this.isLoading = false;
  });
});

九、运行效果

在 DevEco Studio 中连接模拟器或真机运行后,可以看到:

  1. 顶部:4 个彩色圆角卡片排成 2×2 网格,每个卡片中央显示图标与分类名称,底部有柔和阴影
  2. 底部:双列瀑布流卡片依次排布,卡片高度从 160vp 到 310vp 不等,左列和右列的卡片起始位置不同,形成自然的「瀑布」落差视觉效果
  3. 滚动交互:上滑时 WaterFlow 区域流畅滚动,下滑到顶触发 onReachStart 回调,滑到底触发 onReachEnd 回调
  4. 视觉层次:网格区白色背景 + 大圆角,瀑布流区浅灰背景,两者之间通过 margin 隔离,层次分明

十、总结

本文通过一个完整的示例,详细讲解了如何在 HarmonyOS NEXT 中使用 Grid + WaterFlow + Column 实现「顶部网格 + 底部瀑布流」的混合布局。

核心收获:

知识点 要点
Grid 固定高度 配合 .clip(true) 关闭内部滚动,作为静态网格区域
WaterFlow 自管理滚动 layoutWeight(1) 填满剩余空间,内部滚动不冲突
Column 串联 两个 Column 分别承载 Grid 和 WaterFlow,样式隔离
@Builder 抽离子组件 提升代码可读性与复用性
@Observed 数据模型 支持深度属性变化的 UI 观测
onReachEnd 分页回调 真正的上拉加载更多入口

这套布局模式适用于绝大多数移动端首页——淘宝、京东、小红书等主流应用均采用类似结构。掌握了 Grid + WaterFlow 混合布局,就掌握了鸿蒙原生开发中最常用的首页架构。


十一、参考资料


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐