HarmonyOS ArkUI Grid 网格布局:固定列、自适应列与跨列 GridItem

系列:鸿蒙 HarmonyOS 6.1 新特性实战 · 第 48 篇

Grid 是 ArkUI 中专为二维网格场景设计的布局容器,配合 GridItem 使用,可以轻松实现商品列表、图片墙、仪表盘等经典界面。与 Column/Row 的一维排列不同,Grid 通过columnsTemplaterowsTemplate精确定义每列每行的尺寸比例,并支持 GridItem 跨列/跨行占据多个格子。本篇通过商品网格与横幅跨列两个实战示例讲透 Grid 的核心用法。

运行效果

初始状态(2 列等宽商品网格):

切换为 3 列自适应模式,横幅 GridItem 横跨两列:

columnsTemplate 与 rowsGap/columnsGap

columnsTemplate是 Grid 的核心属性,使用空格分隔的 fr 单位字符串定义每一列的宽度比例,与 CSS Grid 的grid-template-columns一脉相承:

// 2 列等宽商品网格 Grid() { GridItem() { Column({ space: 8 }) { Text('🎧').fontSize(48) Text('无线耳机').fontSize(13).fontColor('#333') Text('¥299') .fontSize(14).fontColor('#e74c3c') .fontWeight(FontWeight.Bold) } .width('100%').padding(12) .backgroundColor('#fff').borderRadius(8) .alignItems(HorizontalAlign.Center) } GridItem() { Column({ space: 8 }) { Text('📱').fontSize(48) Text('智能手表').fontSize(13).fontColor('#333') Text('¥599') .fontSize(14).fontColor('#e74c3c') .fontWeight(FontWeight.Bold) } .width('100%').padding(12) .backgroundColor('#fff').borderRadius(8) .alignItems(HorizontalAlign.Center) } GridItem() { Column({ space: 8 }) { Text('💻').fontSize(48) Text('平板电脑').fontSize(13).fontColor('#333') Text('¥1299') .fontSize(14).fontColor('#e74c3c') .fontWeight(FontWeight.Bold) } .width('100%').padding(12) .backgroundColor('#fff').borderRadius(8) .alignItems(HorizontalAlign.Center) } GridItem() { Column({ space: 8 }) { Text('🎮').fontSize(48) Text('游戏手柄').fontSize(13).fontColor('#333') Text('¥199') .fontSize(14).fontColor('#e74c3c') .fontWeight(FontWeight.Bold) } .width('100%').padding(12) .backgroundColor('#fff').borderRadius(8) .alignItems(HorizontalAlign.Center) } } .columnsTemplate('1fr 1fr') // 2 列等宽 .columnsGap(12) // 列间距 12vp .rowsGap(12) // 行间距 12vp .width('100%') .backgroundColor('#f5f5f5') .padding(12)

'1fr 1fr'表示 2 列等宽;'1fr 2fr 1fr'表示 3 列,中间列是两侧列宽度的 2 倍;'120vp 1fr'表示左列固定 120vp,右列占满剩余空间。rowsTemplate同理控制行高,若省略则行高由内容决定(自动扩展)。

GridItem 跨列:columnStart 与 columnEnd

横幅、广告位等需要横跨多列的场景,通过 GridItem 的columnStartcolumnEnd属性实现:

Grid() { // 横幅:跨越 0、1 两列(columnsTemplate 为 2 列时) GridItem() { Text('Banner 横幅——限时特惠') .width('100%').height(100) .backgroundColor('#0066ff').fontColor('#fff') .textAlign(TextAlign.Center).fontSize(16) .borderRadius(8) } .columnStart(0).columnEnd(1) // 0-indexed,inclusive,跨越第 0 和第 1 列 // 普通商品格子 GridItem() { Column({ space: 6 }) { Text('⌚').fontSize(36) Text('智能手表').fontSize(12).fontColor('#333') Text('¥399').fontSize(13).fontColor('#e74c3c').fontWeight(FontWeight.Bold) } .width('100%').padding(10).backgroundColor('#fff').borderRadius(8) .alignItems(HorizontalAlign.Center) } GridItem() { Column({ space: 6 }) { Text('🎵').fontSize(36) Text('蓝牙音箱').fontSize(12).fontColor('#333') Text('¥179').fontSize(13).fontColor('#e74c3c').fontWeight(FontWeight.Bold) } .width('100%').padding(10).backgroundColor('#fff').borderRadius(8) .alignItems(HorizontalAlign.Center) } } .columnsTemplate('1fr 1fr') .columnsGap(12).rowsGap(12) .width('100%').backgroundColor('#f5f5f5').padding(12)

columnStartcolumnEnd均为 0 起始索引,且columnEnd包含该列(inclusive)。若columnsTemplate'1fr 1fr 1fr'(3 列),则跨全行需设.columnStart(0).columnEnd(2)rowStartrowEnd可类比用于跨行。

Grid vs List:选型指南

两者在列表场景下经常被拿来比较:

// Grid:二维布局,适合商品网格、图片墙 Grid() { ForEach(products, (product: Product) => { GridItem() { ProductCard({ product: product }) } }) } .columnsTemplate('1fr 1fr') .columnsGap(12).rowsGap(12) .width('100%') // List:一维列表,适合消息列表、设置项 List({ space: 12 }) { ForEach(messages, (msg: Message) => { ListItem() { MessageRow({ msg: msg }) } }) } .width('100%')

Grid 适合二维场景:商品网格、图片墙、仪表盘卡片;List 适合一维场景:消息流、设置列表、时间轴。若需要高度不固定的瀑布流效果,应使用 WaterFlow(见第 49 篇)。

完整代码

class ProductItem { icon: string = '' name: string = '' price: string = '' } @Entry @Component struct GridDemo { @State colCount: number = 2 @State template: string = '1fr 1fr' private products: ProductItem[] = [ { icon: '🎧', name: '无线耳机', price: '¥299' }, { icon: '📱', name: '智能手表', price: '¥599' }, { icon: '💻', name: '平板电脑', price: '¥1299' }, { icon: '🎮', name: '游戏手柄', price: '¥199' }, { icon: '📷', name: '运动相机', price: '¥899' }, { icon: '🔊', name: '蓝牙音箱', price: '¥179' }, ] @Builder ProductCard(item: ProductItem) { Column({ space: 8 }) { Text(item.icon).fontSize(40) Text(item.name).fontSize(13).fontColor('#333') Text(item.price) .fontSize(14).fontColor('#e74c3c') .fontWeight(FontWeight.Bold) } .width('100%').padding(12) .backgroundColor('#fff').borderRadius(8) .alignItems(HorizontalAlign.Center) } build() { Column({ space: 16 }) { // 标题 Text('Grid 网格布局演示') .fontSize(16).fontWeight(FontWeight.Bold).fontColor('#1a1a1a') // 切换列数 Row({ space: 12 }) { Button('2 列').height(36) .backgroundColor(this.colCount === 2 ? '#0066ff' : '#e0e0e0') .fontColor(this.colCount === 2 ? '#fff' : '#333') .onClick(() => { this.colCount = 2 this.template = '1fr 1fr' }) Button('3 列').height(36) .backgroundColor(this.colCount === 3 ? '#0066ff' : '#e0e0e0') .fontColor(this.colCount === 3 ? '#fff' : '#333') .onClick(() => { this.colCount = 3 this.template = '1fr 1fr 1fr' }) } // Banner 跨列演示 Text('跨列 Banner + 商品网格') .fontSize(14).fontColor('#666') Grid() { // 横幅:跨全部列 GridItem() { Text('限时特惠 · 全场满 300 减 50') .width('100%').height(80) .backgroundColor('#0066ff').fontColor('#fff') .textAlign(TextAlign.Center).fontSize(15) .borderRadius(8) } .columnStart(0) .columnEnd(this.colCount - 1) // 商品列表 ForEach(this.products, (item: ProductItem) => { GridItem() { this.ProductCard(item) } }) } .columnsTemplate(this.template) .columnsGap(10).rowsGap(10) .width('100%') .backgroundColor('#f5f5f5').padding(10) } .width('100%').padding(16) .backgroundColor('#ffffff') } }

API 速查

属性/方法说明
.columnsTemplate(string)列宽模板,如'1fr 1fr''120vp 1fr',空格分隔
.rowsTemplate(string)行高模板,省略时行高由内容自动决定
.columnsGap(number)列间距,单位 vp
.rowsGap(number)行间距,单位 vp
GridItem.columnStart(number)跨列起始列索引(0 起始)
GridItem.columnEnd(number)跨列结束列索引(包含,0 起始)
GridItem.rowStart(number)跨行起始行索引
GridItem.rowEnd(number)跨行结束行索引(包含)

小结

  • columnsTemplate用 fr 单位字符串定义列数和列宽比例,是 Grid 布局的核心属性
  • columnsGaprowsGap分别控制列间距和行间距,比 padding/margin 更语义化
  • columnStart/columnEnd实现跨列,索引从 0 开始且 columnEnd 为包含值
  • 省略rowsTemplate时行高由 GridItem 内容决定,适合高度不一致的商品卡片
  • 不要将自定义类或结构体命名为GridItem,会与 ArkUI 内置组件名冲突
  • Grid 适合二维网格;List 适合一维列表;高度不一致的瀑布流用 WaterFlow(第 49 篇)

上一篇:Flex 弹性布局完全指南 | 下一篇:WaterFlow 瀑布流布局实战