
回复
在鸿蒙应用开发体系中,网格布局作为处理多元素有序排列的核心方案,广泛应用于电商商品陈列、图片画廊、功能矩阵等场景。鸿蒙提供的 Grid 与 GridItem 组件通过声明式语法构建灵活的二维布局系统,支持行列比例分配、单元格合并、滚动交互等高级特性,相比传统线性布局可提升 30% 的复杂界面开发效率。本文将系统解析这组黄金搭档的核心机制与工程实践,帮助开发者掌握多维度数据可视化的鸿蒙解法。
典型层级结构示意图:
Grid (网格容器)
├─ GridItem (占据第1行第1列)
├─ GridItem (占据第1行第2-3列)
└─ GridItem (占据第2-3行第1列)
通过rowsTemplate/columnsTemplate
声明行列比例,支持 fr 弹性单位与固定值混合使用:
Grid()
.rowsTemplate('80vp 1fr') // 首行固定80vp,次行弹性填充
.columnsTemplate('1fr 2fr 1fr') // 三列按1:2:1比例分配
.rowsGap(12) // 行间距12vp
.columnsGap(16) // 列间距16vp
columnsTemplate('1fr 2fr')
表示两列宽度比为 1:2Grid()
.rowsTemplate('1fr') // 单行布局
.columnsTemplate('1fr 1fr 1fr') // 三列结构
.height(200) // 固定高度触发水平滚动
.scrollBar(BarState.Auto) // 自动显示滚动条
Grid()
.columnsTemplate('1fr') // 单列布局
.rowsTemplate('1fr 1fr 1fr') // 三行结构
.width(300) // 固定宽度触发垂直滚动
.edgeEffect(EdgeEffect.Spring) // 边缘弹性效果
未设置行列模板时,通过layoutDirection
控制排列方向:
Grid()
.layoutDirection(GridDirection.Row) // 水平排列(默认)
.maxCount(4) // 每行最多4个单元格
通过rowStart/rowEnd
和columnStart/columnEnd
定义单元格占据范围(行列号从 1 开始):
GridItem()
.columnStart(1) // 起始列1
.columnEnd(3) // 结束列3(占据2列)
.rowStart(2) // 起始行2
.rowEnd(4) // 结束行4(占据3行)
实战案例:计算器按键布局
GridItem() { Text('0') }
.columnStart(1)
.columnEnd(3) // 占据2列宽度
通过forceRebuild
控制组件重建时机:
GridItem()
.forceRebuild(false) // 禁止随父组件重建(适用于静态内容)
设置在触发组件build时是否重新创建此节点。GridItem会根据自身属性和子组件变化自行决定是否需要重新创建,无需设置。
@Entry
@Component
struct ImageGrid {
private images: string[] = Array(9).map((i:number) => `img_${i + 1}`);
build() {
Grid() {
ForEach(this.images, (img:string) => {
GridItem() {
Image(img)
.width('100%')
.height('100%')
.objectFit(ImageFit.Cover)
.borderRadius(8)
}
})
}
.rowsTemplate('1fr 1fr 1fr')
.columnsTemplate('1fr 1fr 1fr')
.rowsGap(10)
.columnsGap(10)
.width('100%')
.height(300)
}
}
interface Product {
id: number;
name: string;
price: number;
image: string;
}
@Entry
@Component
struct ProductGrid {
// 显式声明数组类型并初始化
private products: Product[] = [
{ id: 1, name: "商品1", price: 99, image: "img1" },
{ id: 2, name: "商品2", price: 199, image: "img2" }
// 实际项目应通过API获取数据
];
build() {
Grid() {
// 显式声明LazyForEach参数类型
LazyForEach(
this.products, //这里需要DataSource类型,而不是Product[] 我偷点懒直接用了
(item: Product) => {
GridItem() {
ProductCard({ productData: item }) // 传递显式类型参数
}
.width('25%')
},
(item: Product) => item.id.toString() // 确保key为string类型
)
}
.rowsTemplate('1fr')
.height(200)
.columnsGap(12)
.width('100%')
.cachedCount(5)
}
}
// 商品卡片组件需明确定义props类型
@Component
struct ProductCard {
@ObjectLink productData: Product; // 使用ObjectLink监听对象变化
build() {
Column() {
Image(this.productData.image)
.width(80)
.height(80)
Text(this.productData.name)
.fontSize(14)
Text(`¥${this.productData.price.toFixed(2)}`)
.fontColor(Color.Red)
}
.padding(10)
}
}
Grid() {
// 标题行(跨全列)
GridItem() { Text('2023年12月') }
.columnStart(1)
.columnEnd(8) // 7列+1边距
// 星期标题
ForEach(['日', '一', '二', '三', '四', '五', '六'], (title, idx) => {
GridItem() { Text(title) }
.width('14.28%') // 7列均分
})
// 日期单元格(周末跨2列)
ForEach(this.calendarData, (date:CalendarData) => {
GridItem() { Text(date.day) }
.width(date.isWeekend ? '28.56%' : '14.28%') // 周末占2列
})
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr') // 7列结构
Grid() {
LazyForEach(largeData, (item) => GridItem(), item => item.id)
}
.cachedCount(8) // 预加载当前显示项前后各8个
.useVirtualized(true) // 启用虚拟列表(API 10+)
.itemSize()
声明,提升布局计算效率#if (DeviceType == DeviceType.Phone)
Grid().columnsTemplate('1fr 1fr') // 手机端2列
#elif (DeviceType == DeviceType.Tablet)
Grid().columnsTemplate('1fr 1fr 1fr') // 平板端3列
#elif (DeviceType == DeviceType.Desktop)
Grid().columnsTemplate('1fr 1fr 1fr 1fr') // 桌面端4列
#endif
问题场景 解决方案
单元格溢出 检查 GridItem 是否设置width/height: 100%,并通过maxWidth/maxHeight限制
滚动失效 确认仅设置单行 / 单列模板,且内容超出容器尺寸,可添加.scrollBar(BarState.Always)强制显示
合并错乱 确保rowEnd/columnEnd不超过 Grid 的行列总数,行列号从 1 开始计数
性能卡顿 大数据量场景使用LazyForEach+cachedCount,避免使用ForEach直接渲染
鸿蒙 Grid 与 GridItem 组件通过声明式语法将复杂二维布局转化为结构化配置,核心能力包括:
在实际开发中,建议遵循 "先结构后细节" 的开发流程:先通过 Grid 定义行列骨架,再通过 GridItem 实现单元格个性化。随着鸿蒙生态向全场景设备拓展,网格布局在大屏设备(如平板、智慧屏)上的优势将更加显著,开发者可结合官方模拟器的多设备预览功能,打造适配全场景的网格界面。