鴻蒙學習實戰之路-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)
}
🥦 西蘭花警告:
- Grid 的子組件必須是 GridItem,不能直接放其他組件!
- GridItem 只能有一個子組件,要放多個內容記得用容器包起來!
- 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%')
}
}
三、實戰案例:淘寶二樓效果
現在咱們來整個實戰案例,實現淘寶二樓的功能入口布局!
注代碼中的圖片可以自行替換成 其他圖片,保證代碼邏輯正確即可
效果展示
需求分析
- 實現 5x5 的網格佈局
- 每個網格包含圖標和文字
- 整體使用漸變色背景
參考代碼
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 控制器了!
核心步驟
- 創建 Scroller 對象
- 設置給 Grid
- 調用 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 組件真的是鴻蒙佈局中的"瑞士軍刀",無論是規則的網格佈局還是複雜的不規則佈局,都能輕鬆應對!
核心知識點回顧
- 基礎用法:固定行列布局,使用 columnsTemplate 和 rowsTemplate 設置行列數和比例
- 合併行列:使用 rowStart/rowEnd 和 columnStart/columnEnd 實現不規則網格
- 滾動網格:設置 rowsTemplate 或 columnsTemplate 實現水平/垂直滾動≈
- 代碼控制:使用 Scroller 控制器實現代碼控制滾動
實戰技巧
- 使用@Builder 裝飾器拆分複雜佈局,提高代碼可讀性
- 利用 Array.from({ length: n })快速生成測試數據
- 合理設置 GridItem 的寬高,優化滾動性能
📚 推薦資料
- 官方文檔:Grid 組件開發指南
- 組件參考:Grid API 文檔
我是鹽焗西蘭花,≈ 不教理論,只給你能跑的代碼和避坑指南。≈ 下期見!🥦