鸿蒙学习实战之路-Grid 网格布局组件全攻略

最近在写鸿蒙页面时,发现好多布局用传统的 Flex 和 Column/Row 组合起来特别麻烦,尤其是那种网格状的布局,比如淘宝首页的功能入口、小米有品的分类导航,简直想抠脑壳 o(╯□╰)o
今天这篇,我就手把手带你搞定 Grid&GridItem 网格布局组件,从基础到进阶,保证你看完就能上手!

一、Grid 是什么?

Grid 组件就像是一个布局神器,专门用来创建网格状的 UI 界面。简单来说,就是把屏幕分成若干行和列,然后把内容放在这些格子里。

举个例子,你看淘宝首页的这些功能入口,是不是整整齐齐的网格?用 Grid 实现简直不要太轻松!

🥦 西兰花小贴士

Grid 和 Flex 的区别:Flex 是一维布局(要么行要么列),Grid 是二维布局(同时控制行和列)。就像搭积木,Flex 是一列一列摆,Grid 是一块一块铺!

二、Grid 基础用法

2.1 固定行列布局

最基础的 Grid 用法就是创建固定行列数的网格,比如 3 行 2 列的布局。

组件结构
Grid() {
  GridItem() {
    // 展示的内容放在这里
    Text('1')
      .fontColor(Color.White)
      .fontSize(30)
  }
  .backgroundColor(Color.Blue)

  GridItem() {
    Text('2')
      .fontColor(Color.White)
      .fontSize(30)
  }
  .backgroundColor(Color.Blue)
}

🥦 西兰花警告

  1. Grid 的子组件必须是 GridItem,不能直接放其他组件!
  2. GridItem 只能有一个子组件,要放多个内容记得用容器包起来!
  3. Grid 如果不设置宽高,会默认继承父组件的尺寸。
基础属性
名称 参数类型 描述
columnsTemplate string 设置列数和宽度比例,如’1fr 1fr 2fr’表示 3 列,宽度比例 1:1:2
rowsTemplate string 设置行数和高度比例,如’1fr 1fr’表示 2 行,高度各占一半
columnsGap Length 设置列间距,默认 0
rowsGap Length 设置行间距,默认 0
实战练习

我们来创建一个 3 列 2 行的网格,列宽比例 1:2:1,行高各占一半,带 10px 间距:

@Entry
@Component
struct GridBasicExample {
  build() {
    Column() {
      Text('固定行列布局')
        .fontSize(20)
        .fontWeight(900)
        .padding(10)

      Grid() {
        // 第1行
        GridItem() {
          Text('1')
            .fontColor(Color.White)
            .fontSize(30)
        }
        .backgroundColor(Color.Blue)

        GridItem() {
          Text('2')
            .fontColor(Color.White)
            .fontSize(30)
        }
        .backgroundColor(Color.Blue)

        GridItem() {
          Text('3')
            .fontColor(Color.White)
            .fontSize(30)
        }
        .backgroundColor(Color.Blue)

        // 第2行
        GridItem() {
          Text('4')
            .fontColor(Color.White)
            .fontSize(30)
        }
        .backgroundColor(Color.Blue)

        GridItem() {
          Text('5')
            .fontColor(Color.White)
            .fontSize(30)
        }
        .backgroundColor(Color.Blue)

        GridItem() {
          Text('6')
            .fontColor(Color.White)
            .fontSize(30)
        }
        .backgroundColor(Color.Blue)
      }
      .border({ width: 1 })
      .columnsTemplate('1fr 2fr 1fr')
      .rowsTemplate('1fr 1fr')
      .width('100%')
      .height(360)
      .columnsGap(10)
      .rowsGap(10)
    }
    .width('100%')
    .height('100%')
  }
}

三、实战案例:淘宝二楼效果

现在咱们来整个实战案例,实现淘宝二楼的功能入口布局!

注代码中的图片可以自行替换成 其他图片,保证代码逻辑正确即可

效果展示

需求分析

  1. 实现 5x5 的网格布局
  2. 每个网格包含图标和文字
  3. 整体使用渐变色背景

参考代码

interface TaoBaoItemContent {
  title: string
  icon: ResourceStr // $r('图片名')返回的是Resource类型,ResourceStr是联合类型Resource|string
}

@Entry
@Component
struct TaoBaoSecondFloor {
  contentList: TaoBaoItemContent[] = [
    { title: '淘金币', icon: $r('app.media.ic_taobao_01') },
    { title: '一起摇现金', icon: $r('app.media.ic_taobao_02') },
    { title: '闲鱼', icon: $r('app.media.ic_taobao_03') },
    { title: '中通快递', icon: $r('app.media.ic_taobao_04') },
    { title: '芭芭农场', icon: $r('app.media.ic_taobao_05') },
    { title: '淘宝珍库', icon: $r('app.media.ic_taobao_06') },
    { title: '阿里拍卖', icon: $r('app.media.ic_taobao_07') },
    { title: '阿里药房', icon: $r('app.media.ic_taobao_08') },
    { title: '小黑盒', icon: $r('app.media.ic_taobao_09') },
    { title: '菜鸟', icon: $r('app.media.ic_taobao_10') },
    { title: 'U先试用', icon: $r('app.media.ic_taobao_11') },
    { title: '有好价', icon: $r('app.media.ic_taobao_12') },
    { title: '极有家', icon: $r('app.media.ic_taobao_13') },
    { title: '天猫榜单', icon: $r('app.media.ic_taobao_14') },
    { title: '天天特卖', icon: $r('app.media.ic_taobao_15') },
    { title: '每日好店', icon: $r('app.media.ic_taobao_16') },
    { title: '全球购', icon: $r('app.media.ic_taobao_17') },
    { title: '我的爱车', icon: $r('app.media.ic_taobao_18') },
    { title: '造点新货', icon: $r('app.media.ic_taobao_19') },
    { title: '首单优惠', icon: $r('app.media.ic_taobao_20') },
    { title: '潮Woo', icon: $r('app.media.ic_taobao_21') },
    { title: '亲宝贝', icon: $r('app.media.ic_taobao_22') },
    { title: '领券中心', icon: $r('app.media.ic_taobao_23') },
    { title: '天猫奢品', icon: $r('app.media.ic_taobao_24') },
    { title: 'iFashion', icon: $r('app.media.ic_taobao_25') }
  ]

  build() {
    Column() {
      Column() {
        // 顶部返回区域
        this.backBuilder()

        // 搜索框区域
        this.searchBuilder()

        // Grid区域
        this.gridBuilder()
      }
    }
    .width('100%')
    .height('100%')
    .linearGradient({
      colors: [
        ['#271b41', 0],
        ['#481736', 1],
      ]
    })
  }

  @Builder
  backBuilder() {
    // 顶部返回区域
    Row() {
      Image($r('app.media.ic_taobao_back'))
        .fillColor(Color.White)
        .width(30)
      Text('最近使用')
        .fontSize(20)
        .fontColor(Color.White)
    }
    .width('100%')
    .padding({ top: 40 })
  }

  @Builder
  searchBuilder() {
    // 搜索框区域
    Stack() {
      Text()
        .width('100%')
        .height(40)
        .backgroundColor(Color.White)
        .opacity(.3)
        .borderRadius(20)
      Row({ space: 10 }) {
        Image($r('app.media.ic_taobao_search'))
          .width(25)
          .fillColor(Color.White)
        Text('搜索')
          .fontSize(15)
          .fontColor(Color.White)
      }
      .padding({ left: 10 })
      .width('100%')
    }
    .padding(10)
  }

  @Builder
  gridBuilder() {
    // Grid区域
    Grid() {
      ForEach(this.contentList, (item: TaoBaoItemContent, index: number) => {
        GridItem() {
          Column({ space: 10 }) {
            Image(item.icon)
              .width(40)
            Text(item.title)
              .fontColor(Color.White)
              .fontSize(14)
          }
        }
      })
    }
    .columnsTemplate('1fr 1fr 1fr 1fr 1fr')
    .rowsTemplate('1fr 1fr 1fr 1fr 1fr')
    .width('100%')
    .height(360)
  }
}

🥦 西兰花小贴士

这里用了@Builder 装饰器把布局拆分成几个小模块,代码看起来更清晰!这就像是把一个大蛋糕切成小块,吃起来更方便~

四、合并行列(不规则网格)

有时候咱们需要实现不规则的网格布局,比如有的格子跨两行,有的跨两列。这时候就需要用到 GridItem 的合并属性了!

合并属性

名称 参数类型 描述
rowStart number 指定当前元素起始行号
rowEnd number 指定当前元素终点行号
columnStart number 指定当前元素起始列号
columnEnd number 指定当前元素终点列号

实战练习:实现不规则网格

我们来把一个 4 列 3 行的规则网格改造成不规则布局:

@Entry
@Component
struct GridMergeExample {
  // 快速生成12个元素的数组
  nums: number[] = Array.from({ length: 12 })

  build() {
    Column() {
      Text('合并行列')
        .fontSize(20)
        .fontWeight(900)
        .padding(10)
      Grid() {
        ForEach(this.nums, (item: number, index: number) => {
          if (index === 2) {
            GridItem() {
              Text(index + '')
                .fontColor(Color.White)
                .fontSize(30)
            }
            .backgroundColor('#9dc3e6')
            .columnStart(3)
            .columnEnd(4)
          } else if (index === 3) {
            GridItem() {
              Text(index + '')
                .fontColor(Color.White)
                .fontSize(30)
            }
            .backgroundColor('#9dc3e6')
            .rowStart(2)
            .rowEnd(3)
          } else {
            GridItem() {
              Text(index + '')
                .fontColor(Color.White)
                .fontSize(30)
            }
            .backgroundColor('#9dc3e6')
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr')
      .width('100%')
      .height(260)
      .rowsGap(10)
      .columnsGap(10)
      .padding(10)
    }
    .width('100%')
    .height('100%')
  }
}

🥦 西兰花小贴士

快速生成指定长度的数组小技巧:Array.from({ length: 12 }),想要多长就把 length 设为多少!

五、滚动网格

在实际开发中,经常会遇到内容超出屏幕的情况,这时候就需要用到滚动网格了。

设置滚动方向

  • 水平滚动:设置rowsTemplate,Grid 的滚动方向为水平方向
  • 垂直滚动:设置columnsTemplate,Grid 的滚动方向为垂直方向

实战练习:垂直滚动网格

// 为Text扩展属性newExtend
@Extend(Text)
function newExtend() {
  .width('100%')
  .height('100%')
  .fontSize(30)
  .fontColor(Color.White)
  .textAlign(TextAlign.Center)
}

@Entry
@Component
struct GridScrollExample {
  // 生成30个元素的数组
  list: string[] = Array.from({ length: 30 })

  build() {
    Column() {
      Text('垂直滚动网格')
        .fontSize(20)
        .fontWeight(900)
        .padding(10)
      Grid() {
        ForEach(this.list, (item: string, index) => {
          GridItem() {
            Text((index + 1).toString())
              .newExtend()
          }
          .padding(5)
          .backgroundColor('#0094ff')
          .height('30%') // 竖向滚动-通过height设置高度
        })
      }
      .columnsTemplate('1fr 1fr 1fr') // 竖向滚动 固定列数
      .rowsGap(10)
      .columnsGap(10)
      .width('100%')
      .height(300)
      .border({ width: 1 })
      .padding(5)
    }
    .width('100%')
    .height('100%')
  }
}

六、实战案例:小米有品横向滚动导航

咱们来实现小米有品的横向滚动导航效果:

注代码中的图片可以自行替换成 其他图片,保证代码逻辑正确即可

效果展示

参考代码

interface NavItem {
  title: string
  icon: ResourceStr // 联合属性 Resource | string
}

@Entry
@Component
struct XiaomiYoupinNav {
  // 数据
  navList: NavItem[] = [
    { title: '上新精选', icon: $r('app.media.ic_xiaomi_nav_01') },
    { title: '智能家电', icon: $r('app.media.ic_xiaomi_nav_02') },
    { title: '小米众筹', icon: $r('app.media.ic_xiaomi_nav_03') },
    { title: '有品会员', icon: $r('app.media.ic_xiaomi_nav_04') },
    { title: '有品秒杀', icon: $r('app.media.ic_xiaomi_nav_05') },
    { title: '原产地', icon: $r('app.media.ic_xiaomi_nav_06') },
    { title: '生活优选', icon: $r('app.media.ic_xiaomi_nav_07') },
    { title: '手机', icon: $r('app.media.ic_xiaomi_nav_08') },
    { title: '小米自营', icon: $r('app.media.ic_xiaomi_nav_09') },
    { title: '茅台酒饮', icon: $r('app.media.ic_xiaomi_nav_10') },
    { title: '鞋服饰品', icon: $r('app.media.ic_xiaomi_nav_11') },
    { title: '家纺餐厨', icon: $r('app.media.ic_xiaomi_nav_12') },
    { title: '食品生鲜', icon: $r('app.media.ic_xiaomi_nav_13') },
    { title: '好惠买', icon: $r('app.media.ic_xiaomi_nav_14') },
    { title: '家具家装', icon: $r('app.media.ic_xiaomi_nav_15') },
    { title: '健康养生', icon: $r('app.media.ic_xiaomi_nav_16') },
    { title: '有品海购', icon: $r('app.media.ic_xiaomi_nav_17') },
    { title: '个护清洁', icon: $r('app.media.ic_xiaomi_nav_18') },
    { title: '户外运动', icon: $r('app.media.ic_xiaomi_nav_19') },
    { title: '3C数码', icon: $r('app.media.ic_xiaomi_nav_20') }
  ]

  build() {
    Column() {
      Text('小米有品')
        .fontSize(20)
        .fontWeight(900)
        .padding(10)
      Grid() {
        ForEach(this.navList, (item: NavItem) => {
          GridItem() {
            Column() {
              Image(item.icon)
                .width('80%')
              Text(item.title)
                .fontSize(12)
            }
            .height('100%')
          }
          .width('20%')
        })
      }
      .rowsTemplate('1fr 1fr')
      .height(160)
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(5)
      .padding({ bottom: 10 })
      .scrollBar(BarState.Off) // 关闭滚动条
    }
    .width('100%')
    .height('100%')
    .padding(10)
    .backgroundColor('#f5f5f5')
  }
}

七、代码控制滚动

有时候我们需要通过代码来控制 Grid 的滚动,比如实现上一页/下一页的按钮。这时候就需要用到 Scroller 控制器了!

核心步骤

  1. 创建 Scroller 对象
  2. 设置给 Grid
  3. 调用 Scroller 对象的 scrollPage 方法

参考代码

@Entry
@Component
struct GridControllerExample {
  nums: number[] = Array.from({ length: 200 })
  // 控制器对象,不是状态属性,不需要添加任何修饰符
  scroller: Scroller = new Scroller()

  build() {
    Column() {
      Text('控制器-代码控制滚动')
        .fontSize(20)
        .fontWeight(900)
        .padding(10)
      Grid(this.scroller) {
        ForEach(this.nums, (item: number, index: number) => {
          GridItem() {
            Text(index + 1 + '')
              .fontColor(Color.White)
              .fontSize(20)
              .width('100%')
              .height('100%')
              .textAlign(TextAlign.Center)
          }
          .backgroundColor('#0094ff')
          .width('25%')
        })
      }
      .padding(10)
      .height(450)
      .rowsGap(10)
      .columnsGap(10)
      .rowsTemplate('1fr 1fr 1fr 1fr')

      Row() {
        Button('上一页')
          .width(100)
          .onClick(() => {
            // 上一页
            this.scroller.scrollPage({ next: false })
          })
        Button('下一页')
          .width(100)
          .onClick(() => {
            // 下一页
            this.scroller.scrollPage({ next: true })
          })
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceAround)
    }
  }
}

八、总结

Grid&GridItem 组件真的是鸿蒙布局中的"瑞士军刀",无论是规则的网格布局还是复杂的不规则布局,都能轻松应对!

核心知识点回顾

  1. 基础用法:固定行列布局,使用 columnsTemplate 和 rowsTemplate 设置行列数和比例
  2. 合并行列:使用 rowStart/rowEnd 和 columnStart/columnEnd 实现不规则网格
  3. 滚动网格:设置 rowsTemplate 或 columnsTemplate 实现水平/垂直滚动≈
  4. 代码控制:使用 Scroller 控制器实现代码控制滚动

实战技巧

  • 使用@Builder 装饰器拆分复杂布局,提高代码可读性
  • 利用 Array.from({ length: n })快速生成测试数据
  • 合理设置 GridItem 的宽高,优化滚动性能

📚 推荐资料


我是盐焗西兰花,≈
不教理论,只给你能跑的代码和避坑指南。≈
下期见!🥦

Logo

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

更多推荐