网格布局是移动端最经典的排版方式之一。从照片墙到应用抽屉,从商品列表到图标菜单,等分网格承载了大量信息展示任务。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{lett='1fr';for(leti=1;i<cols;i++){t=t.concat(' 1fr');}returnt;}// 使用: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 图标、背景色和文字色的对象表示:
interfacePhotoItem{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{constfound=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 开发的基础能力。