写点什么

鸿蒙 Grid 与 GridItem 深度解析:二维网格布局解决方案

作者:谢道韫
  • 2025-06-28
    广东
  • 本文字数:3335 字

    阅读完需:约 11 分钟

一、引言:网格布局 —— 多维度数据展示的黄金方案

在鸿蒙应用开发体系中,网格布局作为处理多元素有序排列的核心方案,广泛应用于电商商品陈列、图片画廊、功能矩阵等场景。鸿蒙提供的 Grid 与 GridItem 组件通过声明式语法构建灵活的二维布局系统,支持行列比例分配、单元格合并、滚动交互等高级特性,相比传统线性布局可提升 30% 的复杂界面开发效率。本文将系统解析这组黄金搭档的核心机制与工程实践,帮助开发者掌握多维度数据可视化的鸿蒙解法。

二、核心架构:Grid 与 GridItem 的协作模型

2.1 组件定位与层级关系

  • Grid:网格容器组件,负责定义行列结构、间距规则、滚动方向等全局布局参数

  • GridItem:网格项组件,作为 Grid 的直接子组件,支持单元格跨行列合并与个性化配置

典型层级结构示意图:

Grid (网格容器)├─ GridItem (占据第1行第1列)├─ GridItem (占据第1行第2-3列)└─ GridItem (占据第2-3行第1列)
复制代码

2.2 核心技术优势

  • 响应式布局:通过 fr 弹性单位实现屏幕自适应,适配手机 / 平板 / 车机等多端设备

  • 灵活布局能力:支持单元格跨越多行多列,满足日历、计算器等复杂布局需求

  • 高性能渲染:结合 LazyForEach 实现大数据量懒加载,列表渲染性能提升 50%

三、Grid 核心属性:构建网格骨架

3.1 行列结构配置

行列模板定义

通过rowsTemplate/columnsTemplate声明行列比例,支持 fr 弹性单位与固定值混合使用:

Grid()  .rowsTemplate('80vp 1fr')      // 首行固定80vp,次行弹性填充  .columnsTemplate('1fr 2fr 1fr') // 三列按1:2:1比例分配  .rowsGap(12)                   // 行间距12vp  .columnsGap(16)                // 列间距16vp
复制代码


fr 单位计算逻辑
  • 1fr 表示 1 份弹性空间,总份数由声明的 fr 数量决定

  • 示例:columnsTemplate('1fr 2fr')表示两列宽度比为 1:2

3.2 滚动与布局控制

水平滚动网格
Grid()  .rowsTemplate('1fr')            // 单行布局  .columnsTemplate('1fr 1fr 1fr') // 三列结构  .height(200)                    // 固定高度触发水平滚动  .scrollBar(BarState.Auto)       // 自动显示滚动条
复制代码


垂直滚动网格
Grid()  .columnsTemplate('1fr')          // 单列布局  .rowsTemplate('1fr 1fr 1fr')     // 三行结构  .width(300)                     // 固定宽度触发垂直滚动  .edgeEffect(EdgeEffect.Spring)   // 边缘弹性效果
复制代码
流式布局模式

未设置行列模板时,通过layoutDirection控制排列方向:

Grid()  .layoutDirection(GridDirection.Row) // 水平排列(默认)  .maxCount(4)                       // 每行最多4个单元格
复制代码

四、GridItem 核心属性:单元格个性化定制

4.1 跨行列合并

通过rowStart/rowEndcolumnStart/columnEnd定义单元格占据范围(行列号从 1 开始):

GridItem()  .columnStart(1)    // 起始列1  .columnEnd(3)      // 结束列3(占据2列)  .rowStart(2)       // 起始行2  .rowEnd(4)         // 结束行4(占据3行)
复制代码

实战案例:计算器按键布局

GridItem() { Text('0') }  .columnStart(1)  .columnEnd(3)      // 占据2列宽度
复制代码

4.2 性能优化控制

通过forceRebuild控制组件重建时机:

GridItem()  .forceRebuild(false) // 禁止随父组件重建(适用于静态内容)
复制代码

设置在触发组件 build 时是否重新创建此节点。GridItem 会根据自身属性和子组件变化自行决定是否需要重新创建,无需设置。 


五、实战案例:从基础到复杂的网格开发

5.1 基础九宫格图片墙

@Entry@Componentstruct ImageGrid {  private images: string[] = Array(9).map((i:number) => `img_${i + 1}`);   build() {    Grid() {      ForEach(this.images, (img:string) => {        GridItem() {          Image(img)            .width('100%')            .height('100%')            .objectFit(ImageFit.Cover)            .borderRadius(8)        }      })    }    .rowsTemplate('1fr 1fr 1fr')    .columnsTemplate('1fr 1fr 1fr')    .rowsGap(10)    .columnsGap(10)    .width('100%')    .height(300)  }}
复制代码


5.2 水平滚动商品列表

interface Product {  id: number;  name: string;  price: number;  image: string;} @Entry@Componentstruct ProductGrid {  // 显式声明数组类型并初始化  private products: Product[] = [    { id: 1, name: "商品1", price: 99, image: "img1" },    { id: 2, name: "商品2", price: 199, image: "img2" }  // 实际项目应通过API获取数据  ];   build() {    Grid() {      // 显式声明LazyForEach参数类型      LazyForEach(        this.products, //这里需要DataSource类型,而不是Product[] 我偷点懒直接用了        (item: Product) => {          GridItem() {            ProductCard({ productData: item }) // 传递显式类型参数          }          .width('25%')        },        (item: Product) => item.id.toString() // 确保key为string类型      )    }    .rowsTemplate('1fr')    .height(200)    .columnsGap(12)    .width('100%')    .cachedCount(5)  }} // 商品卡片组件需明确定义props类型@Componentstruct ProductCard {  @ObjectLink productData: Product; // 使用ObjectLink监听对象变化   build() {    Column() {      Image(this.productData.image)        .width(80)        .height(80)      Text(this.productData.name)        .fontSize(14)      Text(`¥${this.productData.price.toFixed(2)}`)        .fontColor(Color.Red)    }    .padding(10)  }}
复制代码


5.3 复杂日历布局

    Grid() {      // 标题行(跨全列)      GridItem() { Text('2023年12月') }      .columnStart(1)      .columnEnd(8) // 7列+1边距       // 星期标题      ForEach(['日', '一', '二', '三', '四', '五', '六'], (title, idx) => {        GridItem() { Text(title) }        .width('14.28%') // 7列均分      })       // 日期单元格(周末跨2列)      ForEach(this.calendarData, (date:CalendarData) => {        GridItem() { Text(date.day) }        .width(date.isWeekend ? '28.56%' : '14.28%') // 周末占2列      })    }    .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr') // 7列结构
复制代码


六、工程实践最佳指南

6.1 性能优化策略

大数据量懒加载
Grid() {  LazyForEach(largeData, (item) => GridItem(), item => item.id)}.cachedCount(8) // 预加载当前显示项前后各8个.useVirtualized(true) // 启用虚拟列表(API 10+)
复制代码


复杂布局优化
  • 避免超过 3 层的网格嵌套

  • 固定高度 / 宽度的网格项使用.itemSize()声明,提升布局计算效率

6.2 多端适配方案

#if (DeviceType == DeviceType.Phone)  Grid().columnsTemplate('1fr 1fr') // 手机端2列#elif (DeviceType == DeviceType.Tablet)  Grid().columnsTemplate('1fr 1fr 1fr') // 平板端3列#elif (DeviceType == DeviceType.Desktop)  Grid().columnsTemplate('1fr 1fr 1fr 1fr') // 桌面端4列#endif
复制代码


6.3 常见问题解决方案

问题场景	解决方案单元格溢出	检查 GridItem 是否设置width/height: 100%,并通过maxWidth/maxHeight限制滚动失效	确认仅设置单行 / 单列模板,且内容超出容器尺寸,可添加.scrollBar(BarState.Always)强制显示合并错乱	确保rowEnd/columnEnd不超过 Grid 的行列总数,行列号从 1 开始计数性能卡顿	大数据量场景使用LazyForEach+cachedCount,避免使用ForEach直接渲染
复制代码

七、总结:网格布局的全场景应用

鸿蒙 Grid 与 GridItem 组件通过声明式语法将复杂二维布局转化为结构化配置,核心能力包括:

  1. Grid 容器:行列模板定义、间距控制、滚动管理

  2. GridItem 项:跨行列合并、性能优化控制

  3. 工程实践:懒加载、多端适配、性能调优

在实际开发中,建议遵循 "先结构后细节" 的开发流程:先通过 Grid 定义行列骨架,再通过 GridItem 实现单元格个性化。随着鸿蒙生态向全场景设备拓展,网格布局在大屏设备(如平板、智慧屏)上的优势将更加显著,开发者可结合官方模拟器的多设备预览功能,打造适配全场景的网格界面。

用户头像

谢道韫

关注

还未添加个人签名 2025-05-05 加入

还未添加个人简介

评论

发布
暂无评论
鸿蒙 Grid 与 GridItem 深度解析:二维网格布局解决方案_谢道韫_InfoQ写作社区