手机桌面是一个典型的网格布局——4 列或 5 列的图标矩阵,每行等宽排列,点击即可打开应用。这种"等宽多列、自动换行"的布局需求在移动端无处不在:应用抽屉、照片网格、商品列表、分类导航……都是网格布局的典型场景。

HarmonyOS NEXT ArkUI 提供了 Grid 组件——一个高性能的网格布局容器,配合 GridItem 组件灵活控制每列的内容和样式。本文将深入讲解 Grid/GridItem 的 API 和使用技巧,并构建一个"应用抽屉"——支持动态列数切换、分类筛选和名称搜索。

关键词:HarmonyOS、ArkUI、Grid、GridItem、网格布局、应用抽屉、列数切换、分类筛选

一、Grid 组件 API

1.1 基本用法

Grid() {
  ForEach(this.apps, (app: AppInfo) => {
    GridItem() {
      Column() {
        Text(app.icon).fontSize(32)
        Text(app.name).fontSize(12)
      }
    }
  }, (app: AppInfo) => app.name)
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsGap(10)
.columnsGap(10)

核心参数与属性:

参数/属性 类型 说明
.columnsTemplate() string 列模板,如 '1fr 1fr 1fr 1fr' 表示 4 等宽列
.rowsTemplate() string 行模板(可选,通常由内容撑开即可)
.rowsGap() Length 行间距
.columnsGap() Length 列间距
.width() / .height() Length 容器尺寸

1.2 columnsTemplate 详解

columnsTemplate 是 Grid 最核心的属性——它决定了网格有多少列以及每列的宽度。它使用 CSS Grid 的 fr 单位语法:

含义
'1fr 1fr 1fr' 3 列等宽
'1fr 1fr 1fr 1fr' 4 列等宽
'1fr 1fr 1fr 1fr 1fr' 5 列等宽
'1fr 2fr 1fr' 3 列,中间列宽度是两边的 2 倍
'100px 1fr 1fr' 3 列,第一列固定 100px,后两列等分剩余空间

fr(fraction)是弹性单位——所有 fr 值相加后按比例分配可用空间。Demo 中使用纯 1fr 模板实现等宽列:

getColumnsTemplate(): string {
  if (this.columnCount === 3) return '1fr 1fr 1fr';
  if (this.columnCount === 5) return '1fr 1fr 1fr 1fr 1fr';
  return '1fr 1fr 1fr 1fr';
}

1.3 动态列数切换

Demo 通过在页面右上角提供 3/4/5 三个列数按钮,让用户实时切换网格密度:

@State columnCount: number = 4;

// 切换按钮
Text('3').onClick(() => { this.columnCount = 3; })
Text('4').onClick(() => { this.columnCount = 4; })
Text('5').onClick(() => { this.columnCount = 5; })

选中按钮为蓝底白字(#1677FF 背景 + #FFFFFF 文字),未选中为灰底灰字(#F2F3F5 背景 + #888899 文字)。

Grid 的 .columnsTemplate(this.getColumnsTemplate()) 会随着 columnCount 变化自动重新计算列模板,Grid 中的内容即时重新排列——不需要手动刷新任何内容。

1.4 GridItem 样式

每个 GridItem 包含应用的图标和名称:

GridItem() {
  Column() {
    Text(app.icon)   // emoji 图标(32sp)
      .fontSize(32)
      .margin({ bottom: 6 })
    Text(app.name)   // 应用名称(12sp)
      .fontSize(12)
      .fontColor('#1a1a2e')
      .maxLines(1)
      .textOverflow({ overflow: TextOverflow.Ellipsis })
  }
  .width('100%')
  .padding({ top: 16, bottom: 12 })
  .borderRadius(12)
  .backgroundColor(app.bgColor.concat('18'))  // 半透明背景
}

图标使用 emoji 字符(如 💬🎵🛒),32sp 字号在网格单元中足够醒目。名称限制单行 + 省略号,防止长名称撑破布局。背景色使用应用的品牌色 + '18' 后缀(即 9% 透明度),形成半透明的淡色背景——比纯色背景更柔和,用户能感知到不同应用的"色调"但不会过于刺眼。

二、应用抽屉的整体设计

2.1 页面架构

AppDrawerPage
├── 标题栏 — "应用抽屉" + 当前应用数
├── 搜索栏(圆角 pill 风格)
├── 操作栏
│   ├── 分类筛选标签(横向滚动)
│   └── 列数切换按钮(3/4/5)
├── Grid 网格区域
│   └── GridItem(应用图标 + 名称)
├── 空状态提示
└── 底部信息栏(列数 + 总应用数)

2.2 数据类设计

class AppInfo {
  name: string;     // 应用名称
  icon: string;     // emoji 图标
  category: string; // 分类标签
  bgColor: string;  // 品牌色(用于半透明背景)

  constructor(name: string, icon: string, category: string, bgColor: string) {
    this.name = name;
    this.icon = icon;
    this.category = category;
    this.bgColor = bgColor;
  }
}

AppInfobgColor 字段被巧用——不仅是数据属性,也直接用于 UI 渲染(.backgroundColor(app.bgColor.concat('18')))。这种"数据驱动视觉"的模式避免了在 UI 层做颜色映射逻辑,数据本身就包含了视觉信息。

2.3 应用数据

Demo 包含 19 款应用,覆盖 5 个分类:

分类 应用 数量
社交 微信💬、QQ🐧、微博📢、钉钉📌 4
工具 计算器🔢、时钟⏰、日历📅、文件管理📁、翻译🌐 5
娱乐 音乐🎵、视频🎬、游戏中心🎮、直播📺 4
购物 商城🛒、优惠券🎫、二手交易♻️ 3
阅读 新闻📰、电子书📚、知识社区💡 3

每个应用的 emoji 图标尽量贴近实际用途——微信的 💬、音乐的 🎵、商城的 🛒——让用户在 Demo 中也能感受到真实的应用抽屉氛围。
在这里插入图片描述

三、搜索与筛选

3.1 搜索过滤

if (this.searchText.length > 0) {
  let keyword = this.searchText;
  let filtered: AppInfo[] = [];
  for (let i = 0; i < apps.length; i++) {
    if (apps[i].name.indexOf(keyword) >= 0) {
      filtered = filtered.slice().concat(apps[i]);
    }
  }
  apps = filtered;
}

使用 String.indexOf() 做简单的子串匹配——输入"音"可以搜到"音乐",输入"乐"也能搜到。不需要完整的拼音搜索或模糊匹配,但对于 19 款应用的 Demo 规模来说已经足够实用。

3.2 分类筛选

6 个标签(全部 + 5 个分类)使用横向 Scroll 容器,确保在小屏手机上也能完整显示:

Scroll() {
  Row() {
    ForEach(this.getCategories(), (cat: string) => {
      Text(this.getCategoryLabel(cat))
        .fontSize(12)
        .fontColor(cat === this.activeCategory ? '#FFFFFF' : '#666677')
        // ...
        .onClick(() => { this.activeCategory = cat; })
    })
  }
}
.scrollBar(BarState.Off)
.layoutWeight(1)

选中标签蓝底白字,未选中灰底灰字。横向滚动 + 隐藏滚动条,保持视觉整洁。

3.3 搜索+分类双重过滤

搜索和分类是叠加关系——先按分类过滤,再按搜索词过滤:

getFilteredApps(): AppInfo[] {
  let apps = this.getAllApps();
  // 第一层:分类过滤
  if (this.activeCategory !== 'all') { /* 按分类过滤 */ }
  // 第二层:搜索过滤
  if (this.searchText.length > 0) { /* 按关键词过滤 */ }
  return apps;
}

用户可以组合使用:先选择"工具"分类(看所有工具类应用),再搜"计"(只看计算器)——两个维度的过滤协同工作。

3.4 空状态

当筛选结果为空时,显示差异化提示:

if (this.getFilteredApps().length === 0) {
  Column() {
    Text('没有找到应用')
      .fontSize(14).fontColor('#BBBBCC')
    Text('试试切换分类或修改搜索关键词')
      .fontSize(12).fontColor('#CCCCDD').margin({ top: 4 })
  }
  .width('100%').padding({ top: 48, bottom: 48 })
}

提示引导用户"切换分类"或"修改搜索关键词"——两个可操作的下一步动作,而非简单的"无结果"。
在这里插入图片描述

四、列数切换与视觉密度

4.1 三种密度

列数 columnsTemplate 适用场景
3 '1fr 1fr 1fr' 大图标、老年模式、信息密集型卡片
4 '1fr 1fr 1fr 1fr' 标准手机桌面(默认)
5 '1fr 1fr 1fr 1fr 1fr' 小图标、高密度、大屏手机

每种密度下,GridItem 的图标大小(32sp)保持不变,但单元宽度不同——3 列时单元最宽,5 列时最窄。用户可以根据屏幕大小和个人偏好选择合适的密度。

4.2 切换按钮设计

列数切换使用三轮圆形按钮,位于分类标签右侧:

[3] [4] [5]

选中为蓝底白字(#1677FF),未选中为灰底灰字(#F2F3F5)。按钮尺寸 26×26dp,足够小不会占据太多空间,又足够大可以用手指点击。

4.3 底部信息

网格下方显示当前列数和总应用数:

当前 4 列网格 · 共 19 款应用

帮助用户感知当前视图参数,也让页面底部不显得空洞。

五、Grid vs WaterFlow vs List

三个组件的对比:

维度 Grid WaterFlow List
布局方式 等宽多列,行对齐 不等高瀑布流 单列垂直列表
列控制 columnsTemplate 精确控制 columnsTemplate + 自动排列 无需列控制
子项高度 通常一致 各不相同 通常一致
典型场景 应用抽屉、照片网格 商品发现、Pinterest 联系人、消息列表
滚动方向 垂直(默认) 垂直(默认) 垂直/水平
性能 高(虚拟化) 高(虚拟化) 高(虚拟化)

选择建议:

  • 每个子项尺寸相似、需要规整的行列对齐 → Grid
  • 每个子项高度不同、需要充分利用空间 → WaterFlow
  • 简单的线性排列、需要分组粘性头 → List

六、交互流程演示

6.1 默认视图

进入页面,19 款应用以 4 列网格排列。每行最多 4 个应用图标——微信、QQ、微博、钉钉在第一行。所有应用半透明彩色背景,视觉丰富而不杂乱。标题栏显示"19 个"。

6.2 搜索应用

在搜索栏输入"音"。Grid 立即刷新,只显示"音乐"一个匹配项。标题栏变为"1 个"。

清空搜索栏,Grid 恢复显示全部 19 款应用。

输入"不存在"。Grid 显示空状态:“没有找到应用” + “试试切换分类或修改搜索关键词”。

6.3 分类筛选

点击"工具"分类标签。Grid 只显示 5 款工具类应用:计算器、时钟、日历、文件管理、翻译。标题栏变为"5 个"。

再在搜索栏输入"计"。双重过滤生效,只显示"计算器"。

点击"全部"恢复所有应用。

6.4 列数切换

点击列数按钮"3"——网格变为 3 列,每行最多 3 个应用。图标间距变宽,视觉更松散。

点击"5"——网格变为 5 列,每行最多 5 个应用。图标间距变窄,信息密度更高。

点击"4"恢复默认。

底部信息栏同步更新:“当前 3 列网格” → “当前 5 列网格” → “当前 4 列网格”。

6.5 点击应用

点击任意应用图标。Toast 提示"打开 XXX"。背景色提供触摸反馈的视觉感知。

七、总结

本文通过"应用抽屉"这个实战案例,全面讲解了 ArkUI Grid 网格布局组件的使用方法。核心知识点包括:

  1. Grid 基础 APIcolumnsTemplate() 列模板 + rowsGap() / columnsGap() 间距控制
  2. columnsTemplate 语法'1fr 1fr 1fr 1fr' 表示 4 等宽列,fr 是弹性比例单位
  3. 动态列数切换@State columnCountgetColumnsTemplate() 动态生成模板字符串
  4. GridItem 样式:居中对齐的 icon + name + 半透明品牌色背景
  5. 双重过滤:分类筛选 + 关键词搜索的叠加过滤逻辑
  6. 空状态设计:筛选无结果时的引导性提示
  7. Grid vs WaterFlow vs List:三种布局组件的对比与场景选择

网格布局是移动端信息展示的核心模式之一。从手机桌面到相册网格,从电商首页到设置入口——Grid 组件提供的 columnsTemplate 机制让开发者可以精确控制每行的列数和每列的宽度。配合搜索和筛选,一个灵活的应用抽屉(或任意图标网格)就能轻松构建。


Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐