鴻蒙學習實戰之路-Grid 網格佈局組件全攻略

最近在寫鴻蒙頁面時,發現好多佈局用傳統的 Flex 和 Column/Row 組合起來特別麻煩,尤其是那種網格狀的佈局,比如淘寶首頁的功能入口、小米有品的分類導航,簡直想摳腦殼 o(╯□╰)o 今天這篇,我就手把手帶你搞定 Grid&GridItem 網格佈局組件,從基礎到進階,保證你看完就能上手!

一、Grid 是什麼?

Grid 組件就像是一個佈局神器,專門用來創建網格狀的 UI 界面。簡單來説,就是把屏幕分成若干行和列,然後把內容放在這些格子裏。

舉個例子,你看淘寶首頁的這些功能入口,是不是整整齊齊的網格?用 Grid 實現簡直不要太輕鬆!

鴻蒙學習實戰之路-Grid 網格佈局組件全攻略_Text

🥦 西蘭花小貼士

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%')
  }
}

三、實戰案例:淘寶二樓效果

現在咱們來整個實戰案例,實現淘寶二樓的功能入口布局!

注代碼中的圖片可以自行替換成 其他圖片,保證代碼邏輯正確即可

效果展示

鴻蒙學習實戰之路-Grid 網格佈局組件全攻略_搜索_02

需求分析

  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%')
  }
}

六、實戰案例:小米有品橫向滾動導航

咱們來實現小米有品的橫向滾動導航效果:

注代碼中的圖片可以自行替換成 其他圖片,保證代碼邏輯正確即可

效果展示

鴻蒙學習實戰之路-Grid 網格佈局組件全攻略_Text_03

參考代碼

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 的寬高,優化滾動性能

📚 推薦資料

  • 官方文檔:Grid 組件開發指南
  • 組件參考:Grid API 文檔

我是鹽焗西蘭花,≈ 不教理論,只給你能跑的代碼和避坑指南。≈ 下期見!🥦