鸿蒙新特性:Grid 网格布局组件详解——构建动态照片画廊
网格布局是移动端最经典的排版方式之一。从照片墙到应用抽屉,从商品列表到图标菜单,等分网格承载了大量信息展示任务。HarmonyOS NEXT ArkUI 提供了 Grid 组件,通过 columnsTemplate 属性灵活定义列数与列宽,配合 GridItem 渲染子元素,轻松实现各种网格效果。
本文将深入讲解 Grid 组件的核心 API,并通过一个"动态照片画廊"实战案例,展示列数切换、选中高亮和多选管理等功能。
关键词:HarmonyOS、ArkUI、Grid、网格布局、照片画廊、动态列数
一、Grid 组件概览
Grid 是 ArkUI 容器组件家族中专门用于网格布局的成员。与 List(线性列表)、WaterFlow(瀑布流)和 Scroll(自由滚动)相比,Grid 的核心特征在于等分列——所有子元素按固定的列数排列,行高一致,视觉整齐统一。
| 组件 | 排列方式 | 典型场景 |
|---|---|---|
| List | 单列线性 | 消息列表、设置选项 |
| Grid | 多列等分 | 照片墙、应用抽屉、图标菜单 |
| WaterFlow | 多列不等高 | 图片流、商品发现 |
| Scroll | 自由排布 | 内容页面 |
Grid 的优势在于"规整"——当你的内容需要以 N 列的方式整齐排列时,Grid 是最合适的选择。
二、Grid 核心 API
2.1 构造函数与列模板
Grid 的列定义通过 columnsTemplate 属性控制,使用类似 CSS Grid 的语法:
Grid() {
ForEach(this.items, (item) => {
GridItem() { /* 内容 */ }
})
}
.columnsTemplate('1fr 1fr 1fr') // 3 等分列
columnsTemplate 支持以下格式:
'1fr 1fr'— 2 等分列(每列占 50% 宽度)'1fr 1fr 1fr'— 3 等分列(每列占 33.3% 宽度)'1fr 1fr 1fr 1fr'— 4 等分列(每列占 25% 宽度)'120vp 1fr'— 混合模式(第一列固定 120vp,第二列占剩余宽度)
fr 是 fraction unit(分数单位)的缩写,1fr 表示"占 1 份可用空间"。如果有 3 个 1fr,则每列各占 1/3 宽度。
2.2 间距控制
Grid() { ... }
.columnsGap(8) // 列间距
.rowsGap(8) // 行间距
列间距和行间距独立控制,通常设为相同值以保持视觉统一。
2.3 动态构建列模板
在实际项目中,列数往往需要根据用户偏好动态调整。由于 columnsTemplate 接受字符串,我们可以动态构建:
buildTemplate(cols: number): string {
let t = '1fr';
for (let i = 1; i < cols; i++) {
t = t.concat(' 1fr');
}
return t;
}
// 使用:
Grid() { ... }
.columnsTemplate(this.buildTemplate(this.colCount))
当 colCount 变化时,columnsTemplate 字符串更新,Grid 自动重新排版。
2.4 GridItem
每个网格单元必须包裹在 GridItem() 中:
ForEach(this.photos, (photo: PhotoItem) => {
GridItem() {
Column() {
Text(photo.emoji).fontSize(32)
Text(photo.label).fontSize(13)
}
.width('100%').height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor(photo.color)
.borderRadius(12)
}
})
GridItem 提供了网格布局所需的尺寸测量能力,确保每列的子元素对齐。
三、实战:动态照片画廊
我们构建一个照片画廊,包含 18 张主题卡片(日出、海洋、森林、极光…),支持三种列数切换(2/3/4 列)和多选管理。
3.1 数据模型
每张"照片"用一个包含主题名、emoji 图标、背景色和文字色的对象表示:
interface PhotoItem {
id: number;
label: string; // 主题名
emoji: string; // emoji 图标
color: string; // 背景色
textColor: string; // 文字颜色(深色背景用白,浅色背景用黑)
}
aboutToAppear(): void {
this.photos = [
{ id: 0, label: '日出', emoji: '🌅', color: '#FF6B6B', textColor: '#FFFFFF' },
{ id: 1, label: '海洋', emoji: '🌊', color: '#4ECDC4', textColor: '#FFFFFF' },
{ id: 2, label: '森林', emoji: '🌲', color: '#2ECC71', textColor: '#FFFFFF' },
{ id: 3, label: '晚霞', emoji: '🌇', color: '#FF8C42', textColor: '#FFFFFF' },
{ id: 4, label: '星空', emoji: '✨', color: '#6C5CE7', textColor: '#FFFFFF' },
// ... 共 18 张
];
}
每张卡片使用独立的颜色方案(18 色),确保网格在任何列数下都呈现出丰富的色彩对比。
3.2 列数切换
三个触摸按钮——2 列、3 列、4 列——点击时切换 colCount 并清除当前选择:
ForEach([2, 3, 4], (n: number) => {
Text(n.toString().concat('列'))
.fontSize(13)
.fontColor(this.colCount === n ? '#FFFFFF' : '#666677')
.fontWeight(this.colCount === n ? FontWeight.Bold : FontWeight.Normal)
.padding({ top: 6, bottom: 6, left: 12, right: 12 })
.borderRadius(12)
.backgroundColor(this.colCount === n ? '#1677FF' : '#F2F3F5')
.margin({ left: 6 })
.onClick(() => {
this.colCount = n;
this.clearSelection(); // 切换列数时重置选择
})
}, (n: number) => n.toString())
切换列数时清除选中状态是一个重要的 UX 细节——因为列数变化会导致卡片位置重新排列,保留旧选中状态会让用户困惑。
3.3 选中交互
点击卡片触发选中/取消,选中的卡片显示白色边框和扩展阴影:
toggleSelect(id: number): void {
const found = this.selectedIds.indexOf(id);
if (found === -1) {
this.selectedIds.push(id);
} else {
this.selectedIds.splice(found, 1);
}
}
每张卡片根据是否被选中动态调整样式:
.border({
width: this.selectedIds.indexOf(photo.id) !== -1 ? 3 : 0,
color: '#FFFFFF',
style: BorderStyle.Solid
})
.shadow(
this.selectedIds.indexOf(photo.id) !== -1
? { radius: 12, color: photo.color.concat('88'), offsetX: 0, offsetY: 4 }
: { radius: 4, color: '#00000012', offsetX: 0, offsetY: 2 }
)
选中状态的双重视觉反馈:
- 白色边框(3vp 宽):清晰标识哪些卡片被选中
- 彩色扩展阴影(12vp 半径):使用卡片自身的颜色 + 半透明(
88),产生"发光"效果
非选中状态的阴影则较轻(4vp 半径、浅黑半透明),保持卡片的层次感但不抢夺注意力。
3.4 选择计数与清除
在列数切换器右侧显示已选数量和清除入口:
if (this.selectedIds.length > 0) {
Row() {
Text('已选 '.concat(this.selectedIds.length.toString()))
.fontSize(12).fontColor('#1677FF').fontWeight(FontWeight.Medium)
Text(' | 清除')
.fontSize(12).fontColor('#FF4D4F')
.onClick(() => { this.clearSelection(); })
}
}
这个信息区域仅在选中至少 1 张卡片后才显示,保持界面的简洁。清除按钮使用红色(#FF4D4F)暗示其"清空"的操作性质。
四、Grid vs List vs WaterFlow
选择哪个布局容器取决于内容特征:
| 特征 | Grid | List | WaterFlow |
|---|---|---|---|
| 内容高度 | 统一 | 可变 | 可变 |
| 列数 | 自定义 | 1 | 自定义 |
| 列对齐 | 强制对齐 | 无 | 紧凑排列 |
| 适用 | 图标菜单、相册 | 消息、设置 | 图片流 |
如果你的内容卡片的宽高比一致(如本例中的 18 张主题卡片),Grid 是天然的选择。如果内容高度差异大(如不同长度的文字卡片),WaterFlow 更合适。如果只需要单列,List 最简单。
五、完整交互流程
- 初始状态:进入页面,3 列网格展示 18 张彩色主题卡片,无任何选中
- 切换列数:点击"2 列"按钮,卡片重新排版为 2 列;点击"4 列",变为 4 列
- 选中卡片:点击"星空"卡片,出现白色边框 + 紫色发光阴影,右上角显示"已选 1 | 清除"
- 多选:继续点击"海洋"和"日出",已选计数变为 3,三张卡片同时高亮
- 取消选中:再次点击"星空",计数变为 2,该卡片恢复默认样式
- 清除选择:点击"清除",所有选中状态归零,计数信息消失
六、总结
Grid 是 ArkUI 中设计最简洁的布局组件之一。它的核心就是一条属性:columnsTemplate。掌握了这个属性的用法,就能在 2 列、3 列、4 列甚至混合列宽之间自由切换。
本文通过"照片画廊"这个实战案例,覆盖了:
columnsTemplate的动态构建与列数切换- GridItem 的嵌套使用
- 选中状态的边框 + 阴影双重反馈
- 多选管理与计数清除
- Grid / List / WaterFlow 的选择原则
Grid 配合 List(列表)、WaterFlow(瀑布流)和 Scroll(滚动),构成了 ArkUI 布局体系的四大容器。根据内容特征选择合适的容器,是鸿蒙应用 UI 开发的基础能力。
更多推荐



所有评论(0)