【学习目标】

  • 掌握Grid/GridItem组件的核心架构、父子组件约束规则,理解二维网格布局的核心逻辑
  • 吃透Grid组件六大核心配置:行列模板、排列方向、行列间距、滚动控制、不规则布局、性能优化的完整用法
  • 掌握不规则网格的实现方案,搞定跨行跨列布局的核心配置与开发技巧
  • 通过示例实现固定功能九宫格、计算器不规则布局、可滚动商品网格

一、本节工程目录

GridLayoutDemo/
├── entry/
│   └── src/
│       └── main/
│           ├── ets/
│           │   ├── entryability/
│           │   │   └── EntryAbility.ets       // 应用入口(工程自动生成)
│           │   ├── pages/
│           │   │   ├── Index.ets              // 导航首页:3个示例入口
│           │   │   ├── GridBaseDemo.ets       // 示例1:基础九宫格布局
│           │   │   ├── GridIrregularDemo.ets  // 示例2:不规则网格布局
│           │   │   └── GridGoodsDemo.ets      // 示例3:可滚动商品网格实战
│           │   └── utils/                      // 工具类目录
│           ├── resources/                      // 应用资源目录
│           │   ├── base/element/
│           │   └── base/media/
│           └── module.json5                    // 模块配置文件
└── build-profile.json5                          // 工程配置文件

二、网格布局核心基础

2.1 什么是网格布局

网格布局是鸿蒙官方提供的二维自适应布局组件,以「Grid容器+GridItem子项」为核心架构:

  • Grid 作为父容器,定义整体的行、列规则,将页面切分成若干个规整的单元格
  • GridItem 作为子容器,必须作为Grid的直接子组件使用,用于承载每个单元格的具体内容

它可以同时控制水平、垂直两个方向的布局规则,既能实现等分的规整宫格,也能实现跨行跨列的不规则布局,天然具备自适应能力。

核心规则:Grid的直接子组件只能是GridItem,GridItem必须嵌套在Grid容器内,两者单独使用均无布局效果。

2.2 网格布局核心适配逻辑

  1. Grid容器通过行列规则,确定整体网格的单元格数量、尺寸占比
  2. GridItem按规则依次填充单元格,可自定义跨行跨列的占用范围
  3. 容器尺寸发生变化时,所有单元格与间距会按占比等比例缩放,无需手动适配
  4. 仅设置行/列其中一项规则时,超出容器显示区域的内容,Grid自动支持对应方向的滚动

三、Grid容器配置详解

Grid是网格布局的核心,所有全局规则均在Grid容器上配置,下面我们逐一拆解每个核心配置的用法、规则。

3.1 核心构造函数语法

Grid(scroller?: Scroller, layoutOptions?: GridLayoutOptions)
  • scroller:可选项,绑定滚动控制器,用于控制网格的滚动位置、翻页、回到顶部等操作
  • layoutOptions:可选项,用于配置不规则网格的跨行跨列规则

3.2 核心配置一:行列模板

通过rowsTemplatecolumnsTemplate两个属性,定义网格的行列数量、每行每列的尺寸占比,是网格布局最核心的配置。

3.2.1 属性语法规则

属性值为**「数字+fr」通过空格分隔的字符串**,核心规则如下:

  1. fr是网格布局的专属占比单位,fr的个数 = 对应方向的行列数量
    • 例:rowsTemplate('1fr 1fr 1fr') 代表垂直方向分为3行
    • 例:columnsTemplate('1fr 1fr 1fr 1fr') 代表水平方向分为4列
  2. fr前的数字为占比权重,数字越大,对应行/列在父容器中的占比越高
    • 例:columnsTemplate('1fr 2fr 1fr') 代表3列,宽度占比为1:2:1
  3. 相同权重下,所有行/列会均分父容器对应方向的尺寸
    • 例:rowsTemplate('1fr 1fr 1fr') 代表3行,每行高度均分容器总高度
3.2.2 布局约束与滚动规则

这是开发中最容易踩坑的点,必须严格遵守:

配置场景 布局效果 滚动能力
同时设置rowsTemplate + columnsTemplate 仅展示固定行列数的内容,超出部分不展示 不可滚动
仅设置rowsTemplate 行数固定,列数自适应,内容水平排列 超出容器宽度可横向滚动
仅设置columnsTemplate 列数固定,行数自适应,内容垂直排列 超出容器高度可纵向滚动
两者都不设置 行列数由布局方向、单元格尺寸共同决定,超出内容不展示 不可滚动

3.3 核心配置二:主轴排列方向

通过layoutDirection属性设置网格的主轴排列方向,仅在未同时设置rowsTemplate和columnsTemplate时生效

枚举值 布局效果
GridDirection.Row 默认值,主轴为水平方向,先从左到右排满一行,再换行从上到下排列
GridDirection.Column 主轴为垂直方向,先从上到下排满一列,再换列从左到右排列
GridDirection.RowReverse 水平反向排列,从右到左、从上到下
GridDirection.ColumnReverse 垂直反向排列,从下到上、从左到右
配套约束属性
  • maxCount:主轴方向上最大的单元格数量
  • minCount:主轴方向上最小的单元格数量
  • cellLength:主轴方向上单个单元格的固定尺寸

3.4 核心配置三:行列间距

通过rowsGapcolumnsGap分别设置网格的行间距与列间距,避免单元格紧贴在一起,提升布局美观度。

  • rowsGap:设置行与行之间的垂直间距,单位vp
  • columnsGap:设置列与列之间的水平间距,单位vp

3.5 核心配置四:滚动控制

Grid组件可通过绑定Scroller控制器,实现滚动位置控制、翻页、回到顶部等操作,仅在Grid具备滚动能力时生效(仅设置单个行列模板)

核心使用步骤
  1. 初始化Scroller滚动控制器
  2. 将控制器绑定到Grid构造函数
  3. 通过控制器的API实现各类滚动操作
核心滚动API
API 功能说明
scrollPage({next: boolean}) 滚动到上一页/下一页,next为true向下/向右翻页,false向上/向左翻页
scrollEdge(Edge.Top/Edge.Bottom) 滚动到容器顶部/底部
scrollEdge(Edge.Start/Edge.End) 滚动到容器最左侧/最右侧
currentOffset() 获取当前滚动的偏移量

3.6 核心配置五:不规则网格布局

通过layoutOptions配置,实现单个GridItem跨行跨列的不规则布局,适用于计算器、日历、个性化宫格等场景。

核心配置语法
// 不规则布局配置
private layoutOptions: GridLayoutOptions = {
  regularSize: [1, 1], // 常规单元格默认占用[行数,列数]
  // 自定义每个单元格的位置与占用范围
  onGetRectByIndex: (index: number) => {
    // 返回值格式:[起始行号, 起始列号, 占用行数, 占用列数]
    // 注意:行列号从0开始编号
  }
}

// 绑定到Grid构造函数
Grid(undefined, this.layoutOptions) {
  // GridItem子项
}

四、GridItem子组件详解

GridItem是网格布局的子项容器,负责承载每个单元格的具体内容,核心使用规则如下:

  1. 强制父子约束:Grid的直接子组件只能是GridItem,GridItem内部可嵌套任意布局组件(Row/Column/Image/Text等)
  2. 尺寸自适应:GridItem的宽高默认跟随Grid的行列模板自动适配,无需手动设置固定宽高,避免破坏网格规则
  3. 跨行跨列配置:单个GridItem的跨行跨列规则,通过Grid的layoutOptions统一配置,无需在GridItem上单独设置
  4. 交互支持:GridItem支持点击、触摸、长按等通用事件,可实现单元格的各类交互逻辑
  5. 样式自定义:GridItem内部可自由设置背景、圆角、阴影等样式,不影响网格整体布局规则

五、演示示例

5.1 实战一:基础功能九宫格

对应知识点:行列模板、行列间距、基础GridItem用法,实现APP首页常见的功能入口九宫格,可直接复制运行。

interface IMenu {
  name: string,
  icon: string
}

@Entry
@Component
struct GridBaseDemo {
  // 功能入口数据
  private menuList: IMenu[] = [
    { name: '首页', icon: '🏠' },
    { name: '分类', icon: '📋' },
    { name: '发现', icon: '🔍' },
    { name: '购物车', icon: '🛒' },
    { name: '我的', icon: '👤' },
    { name: '设置', icon: '⚙️' },
    { name: '消息', icon: '💬' },
    { name: '客服', icon: '📞' },
    { name: '关于', icon: 'ℹ️' }
  ]

  build() {
    Column() {
      Text('功能入口九宫格')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      Grid() {
        ForEach(this.menuList, (item: IMenu) => {
          GridItem() {
            Column({ space: 10 }) {
              Text(item.icon)
                .fontSize(32)
              Text(item.name)
                .fontSize(14)
                .fontColor('#333333')
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .backgroundColor($r('sys.color.comp_background_list_card'))
            .borderRadius(12)
            .shadow({ radius: 4, color: '#00000010' })
          }
        }, (item: IMenu) => item.name)
      }
      .rowsTemplate('1fr 1fr 1fr')
      .columnsTemplate('1fr 1fr 1fr')
      .rowsGap(16)
      .columnsGap(16)
      .width('90%')
      .layoutWeight(1)
    }
    .width('100%')
    .height(360)
    .backgroundColor('#F5F5F5')
    .padding(20)
  }
}

运行效果

Grid-九宫格布局

5.2 实战二:计算器不规则布局

对应知识点:不规则网格配置、跨行跨列实现,完整还原计算器按键布局,可直接复制运行。

@Entry
@Component
struct GridIrregularDemo {
  @State  currentKey:string = '0'

  // 计算器按键数据
  private keyList: string[] = ['AC', '+/-', '%', '÷', '7', '8', '9', '×', '4', '5', '6', '-', '1', '2', '3', '+', '0', '.', '='];
  private scroller:Scroller = new Scroller()
  // 不规则布局配置
  private layoutOptions: GridLayoutOptions = {
    regularSize: [1, 1],
    onGetRectByIndex: (index: number) => {
      // 按键"0":第5行第0列,占1行2列
      if (index === 16) {
        return [4, 0, 1, 2];
      }
      // 其余按键默认1行1列
      return [Math.floor(index / 4), index % 4, 1, 1];
    }
  }

  build() {
    Column() {
      // 计算器显示区域
      Column(){
        Text(this.currentKey)
          .fontSize(48)
          .fontColor($r('sys.color.comp_background_list_card'))
          .fontWeight(FontWeight.Regular)
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .width('90%')
          .textAlign(TextAlign.End)

      }
      .justifyContent(FlexAlign.End)
      .alignItems(HorizontalAlign.End)
      .width('90%')
      .height('35%')
      .padding(20)

      // 计算器按键网格
      Grid(this.scroller, this.layoutOptions) {
        ForEach(this.keyList, (key: string, index: number) => {
          GridItem() {
            Button({buttonStyle:ButtonStyleMode.NORMAL,type:index === 16 ? ButtonType.Capsule: ButtonType.Circle}){
               Text(key)
                 .fontSize(28)
                 .fontColor(index < 4 ? '#000000' : '#FFFFFF')
                 .fontWeight(index % 4 === 3 || index === 18 ? FontWeight.Bold : FontWeight.Medium)
                 .textAlign(TextAlign.Center)
            }.backgroundColor(index < 4 ? '#A5A5A5' : index % 4 === 3 || index === 18 ? '#FF9F0A' : '#333333')
            .width('100%')
            .height('100%')
          }.onClick(()=>{
            this.currentKey = key
          })

        }, (key: string) => key)
      }
      .rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr')
      .columnsTemplate('1fr 1fr 1fr 1fr')
      .columnsGap(12)
      .rowsGap(12)
      .width('90%')
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#121212')
  }
}

运行效果

Grid-计算器布局

5.3 实战三:可滚动商品网格列表

对应知识点:垂直滚动网格、懒加载性能优化、滚动控制,集成Search搜索组件,实现电商APP常见的商品网格列表,可直接复制运行。

// 商品数据模型
interface GoodsItem {
  id: string;
  name: string;
  price: number;
  cover: string;
}

@Entry
@Component
struct GridGoodsDemo {
  @State goodsList: GoodsItem[] = [];
  private scroller: Scroller = new Scroller();

  // 初始化商品数据
  aboutToAppear() {
    for (let i = 0; i < 60; i++) {
      this.goodsList.push({
        id: i.toString(),
        name: `鸿蒙旗舰商品${i + 1}`,
        price: 99 + i * 10,
        cover: `📦`
      });
    }
  }

  build() {
    Stack() {
      Column() {
        this.navgationBarBuilder()
        
        // 商品网格列表
        Grid(this.scroller) {
          ForEach( // 可以使用LazyForEach
            this.goodsList,
            (item: GoodsItem) => {
              GridItem() {
                this.GoodsItemCard(item);
              }
              .onClick(() => {
                console.info(`点击商品:${item.name}`);
              })
            },
            (item: GoodsItem) => item.id // 唯一标识
          )
        }
        .columnsTemplate('1fr 1fr')
        .columnsGap(12)
        .rowsGap(12)
        .padding(12)
        .width('100%')
        .layoutWeight(1)
        .backgroundColor('#F5F5F5')
      }
      .width('100%')
      .height('100%')

      // 回到顶部悬浮按钮
      Button('回到顶部')
        .width(100)
        .height(40)
        .fontSize(14)
        .backgroundColor(Color.White)
        .fontColor(Color.Black)
        .borderRadius(20)
        .shadow({ radius: 4, color: '#00000010' })
        .position({ right: 20, bottom: 30 })
        .onClick(() => {
          this.scroller.scrollTo({ xOffset: 0, yOffset: 0 });
        })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5');
  }
  @Builder navgationBarBuilder(){

    // 顶部导航栏=
    Row({ space: 10 }) {
      Text('商品列表')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor(Color.Black)

      Search({ placeholder: '搜索商品' })
        .height(36)
        .layoutWeight(1)
        .padding({ left: 15 })
        .backgroundColor('#F5F5F5')
        .borderRadius(18)
        .placeholderColor('#999999')
        .textAlign(TextAlign.Start)
        .border({ width: 0.5, color: '#EEEEEE', radius: 18 });
    }
    .width('100%')
    .height(50)
    .justifyContent(FlexAlign.Center)
    .backgroundColor(Color.White)
    .zIndex(10)
    .padding({ left: 15, right: 15 });
  }
  // 商品卡片组件
  @Builder
  GoodsItemCard(item: GoodsItem) {
    Column({ space: 8 }) {
      // 商品封面
      Row() {
        Text(item.cover)
          .fontSize(40)
      }
      .width('100%')
      .aspectRatio(1)
      .backgroundColor('#E0E0E0')
      .borderRadius(12)
      .justifyContent(FlexAlign.Center)
      .alignItems(VerticalAlign.Center);

      // 商品名称
      Text(item.name)
        .fontSize(14)
        .fontColor('#333333')
        .maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .width('100%');

      // 商品价格
      Text(`¥${item.price.toFixed(2)}`)
        .fontSize(16)
        .fontColor('#FF3B30')
        .fontWeight(FontWeight.Bold)
        .width('100%');
    }
    .width('100%')
    .padding(10)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .shadow({ radius: 2, color: '#00000008' });
  }
}

运行效果

Grid网格-电商

六、最佳实践与高频避坑指南

  1. 行列模板规范:固定宫格场景同时设置rowsTemplate和columnsTemplate,保证布局可控;可滚动列表场景仅设置其中一个,开启对应方向滚动
  2. 间距规范:行列间距推荐使用8-16vp,保证视觉一致性,避免间距过大或过小导致布局失衡
  3. 性能规范:数据量超过50条的可滚动网格,推荐LazyForEach懒加载,不推荐使用ForEach全量渲染
  4. 预加载规范:cachedCount推荐设置3-5,平衡滚动体验与内存开销,避免设置过大导致内存占用过高
  5. 布局规范:GridItem内部使用百分比宽高或自适应布局,禁止设置固定宽高破坏网格的自适应能力

七、内容总结

  1. 核心架构:网格布局采用「Grid容器+GridItem子项」的父子架构,Grid负责全局行列规则,GridItem负责单个单元格内容承载
  2. 核心配置:通过rowsTemplate/columnsTemplate定义行列规则,通过layoutOptions实现不规则布局,通过Scroller实现滚动控制
  3. 核心能力:同时管控水平+垂直两个方向的布局,天然具备自适应能力,支持规整宫格与不规则跨行跨列布局
  4. 核心场景:固定功能九宫格、计算器、日历、商品网格、视频宫格等二维布局场景
  5. 性能优化:大数据场景必须使用LazyForEach懒加载,配合cachedCount预加载提升滚动体验

八、代码仓库

  • 示例工程:GridLayoutDemo
  • 仓库地址:https://gitee.com/HarmonyOS-UI-Basics/harmony-os-ui-basics.git

九、下节预告

下一节我们将正式学习瀑布流布局核心组件 WaterFlow,彻底掌握不等高自适应滚动布局开发:

  • 掌握 WaterFlow/FlowItem 核心架构与自动填充规则
  • 实现双列不等高瀑布流、无限滚动、下拉刷新、触底预加载
  • 掌握滑动窗口模式、组件复用、懒加载等瀑布流专属性能优化方案
Logo

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

更多推荐