前言

在 Flutter 应用里,图标(Icons) 是使用频率最高的 UI 元素之一:工具栏、Tab 栏、空状态、状态指示……几乎每个界面都离不开它。过去开发者习惯使用 Material Icons、Cupertino Icons 或 Font Awesome,但当应用要运行在 HarmonyOS(鸿蒙) 上时,一个新的诉求出现了:能否直接使用鸿蒙官方设计语言 HarmonyOS Symbol 的图标?

鸿蒙官方提供了 HM Symbol 图标字体(HMSymbol.ttf)与一套结构化的图标元数据,覆盖系统 UI、状态栏、通信、人物、办公等 17 个分类,并支持 可变字重(wght 40–900) 与 RTL 镜像字形。然而这些资产原本只面向原生 ArkUI 开发,Flutter 侧一直没有一个"官方同款"的图标库。

harmony_icons 就是为填补这个空白而生的:它把鸿蒙官方 HM Symbol 字体与 534 个图标的元数据打包成一个零依赖的 Flutter 包,用法与 cupertino_icons 完全一致——IconData 常量、开箱即用,同时提供中文名、分类、最低系统版本等富元数据与动态查询 API。本文是"Flutter 三方库适配 OpenHarmony"系列的图标库篇,将带你从背景、安装、API、核心用法一路讲到鸿蒙模拟器实测运行与图标字体的工程化生成全过程。

本文所有代码与截图均来自开源仓库 oh-flutter/harmony_icons,可直接复现。

一、背景:为什么鸿蒙上的 Flutter 需要原生图标库

1.1 鸿蒙社区 Flutter 分叉(CPF-Flutter)

Flutter 要运行在 HarmonyOS 上,依赖的是鸿蒙社区维护的 Flutter 分叉 oh-Flutter/flutter_flutter。它在官方 Flutter 引擎之上补齐了 OpenHarmony 平台层(ohos host、平台通道、构建集成),目前社区已发布 3.47.7-ohos-1.0.0 等版本,配套 DevEco Studio 工具链即可构建并运行 HAP 包。

在 OpenHarmony 官网 生态快速扩展的当下,跨平台开发者的关注点已经从"能不能跑起来"转移到"能不能以原生体验跑起来"。而视觉一致性是体验的第一印象——用 Material 风格图标做一个鸿蒙应用,就像在西装口袋里塞了一双运动鞋:功能没毛病,但总差一口气。

1.2 HarmonyOS Symbol:鸿蒙官方图标体系

HarmonyOS Symbol 是华为开发者官网提供的官方图标资源,其设计要点包括:

  • 图标字体交付:官方以 HMSymbol.ttf 字体形式分发,每个图标对应一个私有区码点(PUA),天然适配 Flutter 的 IconData 渲染机制;
  • 结构化元数据:官网同时提供 name_map_new.json 等元数据文件,包含每个图标的英文名、中文名、分类、所属模块、最低支持系统版本(如 HarmonyOS 5.0+);
  • 可变字重:字体保留 wght 轴(40–900),同一图标可渲染从细到粗的多种笔画宽度;
  • RTL 镜像:方向性图标(箭头、返回键等)提供镜像码点,适配从右向左的阅读环境;
  • 多分类复用:同一图标可出现在多个分类下,例如 wifi 同时属于"系统UI"与"连接"。

数据源地址:华为开发者官网 HarmonyOS Symbol(当前内置 HarmonyOS Symbol 2.3 版本数据)。

关键洞察:HM Symbol 的交付形态(TTF 字体 + JSON 元数据)与 Flutter 的图标渲染机制(字体 + 码点)天然同构,这意味着适配成本远低于"平台通道转发"类库——这正是 harmony_icons 能做到零平台相关代码的根本原因。

二、harmony_icons 是什么

2.1 项目定位与目录结构

harmony_icons 的核心定位一句话:HarmonyOS Symbol 图标库 for Flutter,用法与 cupertino_icons 一致。包内预置官方 HM Symbol 字体子集,每个图标对应一个 IconData 常量,同时携带完整元数据。仓库结构如下:

路径作用
lib/harmony_icons.dart库入口,导出实现
lib/src/harmony_icons.dart生成代码:OhosIcons 常量 + OhosIconMeta 元数据(勿手改)
assets/fonts/HMSymbol.ttf子集化后的官方图标字体(约 360KB,保留 wght 轴)
example/gallery 示例应用(搜索、分类筛选、点击查看详情)
example/ohos/鸿蒙工程壳(DevEco Studio 可打开)
tool/官网数据下载、字体子集化、代码生成、SVG 导出、模拟器运行脚本
test/常量与元数据一致性测试
doc/文档与运行截图

2.2 核心特性一览

特性说明
534 个唯一图标源自 HarmonyOS Symbol 2.3,按名称去重,覆盖 17 个分类
IconData 常量OhosIcons.airplane_fill 等,与 Material/Cupertino Icons 用法一致
富元数据中文名、分类、模块、最低系统版本、码点、RTL 镜像码点
动态查询fromName / values / names / categories / info,支持数据驱动场景
字体精简4.2MB 官方字体子集化为约 360KB,保留 wght 可变字重轴
零依赖仅依赖 Flutter SDK,无任何平台相关代码
双端兼容鸿蒙社区分叉 CPF-Flutter 与官方 Flutter SDK 均可运行

主要优势:

  1. 视觉原生:图标与鸿蒙系统界面同源,跨平台应用在鸿蒙上不再有"异类感";
  2. 零成本迁移:已经会用 cupertino_icons 的开发者无需学习新范式;
  3. 元数据完整:中文名与版本信息直接可用于国际化与兼容性提示;
  4. 可持续更新:官网数据与字体均可通过脚本一键重建,跟随官方版本迭代。

三、快速上手:安装与第一个图标

3.1 添加依赖

harmony_icons 已发布到 pub.dev,在 pubspec.yaml 中声明:

dependencies:
  harmony_icons: ^0.1.0

或者使用命令行一键添加:

flutter pub add harmony_icons

字体资产在包的 pubspec.yaml 中已声明完毕,无需在项目里做任何字体配置:

flutter:
  fonts:
    - family: HMSymbol
      fonts:
        - asset: assets/fonts/HMSymbol.ttf

3.2 渲染第一个图标

导入并直接使用 OhosIcons 常量,与使用 CupertinoIcons 没有任何区别:

import 'package:flutter/material.dart';
import 'package:harmony_icons/harmony_icons.dart';

class Demo extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Icon(
          OhosIcons.airplane_fill, // 鸿蒙官方"飞行模式"图标
          size: 48,
          color: Colors.blue,
        ),
      ),
    );
  }
}

OhosIcons.<icon_name> 中的 icon_name 使用官网的英文标识,例如 OhosIcons.battery_100percent、OhosIcons.person_2_fill、OhosIcons.wifi_6_badge_lock。534 个常量的完整清单可以在仓库的 gallery 示例中按名称搜索,也可以查阅生成文件 lib/src/harmony_icons.dart(每个常量上方都带有"中文名 · 英文名 · 最低版本"的文档注释)。

提示:图标名不确定时,优先使用第五节的 OhosIcons.fromName(name) 动态查询 API,配合 OhosIcons.names 做代码补全与校验。

四、API 参考:常量与元数据

4.1 常量与查询入口

成员说明
OhosIcons.<name>IconData 常量,直接给 Icon / IconButton 使用
OhosIcons.fontFamily字体名,固定为 HMSymbol
OhosIcons.fontPackage包名 harmony_icons,跨包引用字体时自动生效
OhosIcons.infoMap<String, OhosIconMeta>,按图标名查元数据
OhosIcons.values全部 OhosIconMeta 的可迭代集合
OhosIcons.names全部图标英文名(有序、唯一)
OhosIcons.count图标总数(534)
OhosIcons.version官方数据版本(2.3)
OhosIcons.categories全部分类(排序去重)
OhosIcons.fromName(name)按英文名动态获取 IconData

4.2 OhosIconMeta 元数据字段

每个图标的元数据由 OhosIconMeta 承载,源码(节选)如下:

/// Metadata for a single HarmonyOS Symbol.
class OhosIconMeta {
  const OhosIconMeta({
    required this.name,
    required this.label,
    required this.module,
    required this.categories,
    required this.supportVersion,
    required this.codepoint,
    this.mirrorCodepoint,
  });

  /// The [IconData] that renders this symbol.
  IconData get icon => IconData(
        codepoint,
        fontFamily: OhosIcons.fontFamily,
        fontPackage: OhosIcons.fontPackage,
      );

  /// The [IconData] of the RTL mirror variant, when [mirrorCodepoint] is set.
  IconData? get mirrorIcon => mirrorCodepoint == null
      ? null
      : IconData(
          mirrorCodepoint!,
          fontFamily: OhosIcons.fontFamily,
          fontPackage: OhosIcons.fontPackage,
        );
}

各字段含义:

字段说明示例
name英文标识airplane_fill
label中文名飞行模式
module所属模块状态栏
categories所属分类(可多个)[系统UI]
supportVersion最低系统版本HarmonyOS 5.0+
codepoint字体码点0xF0113
mirrorCodepointRTL 镜像码点(可为 null)0xF0A02
icon该图标的 IconData-
mirrorIcon镜像变体的 IconData(可为 null)-

注意:lib/src/harmony_icons.dart 是由 tool/generate_icons.py 从官网元数据自动生成的文件,头部标注 GENERATED CODE - DO NOT MODIFY BY HAND。如需新增/更新图标,应走第七节的工程化流程,而不是手改生成文件。

五、核心用法详解

5.1 按名称动态获取图标(fromName)

服务端下发图标名、路由表配置图标、低代码平台等数据驱动场景,使用 fromName:

Icon(
  OhosIcons.fromName(name),
  size: 32,
);

其实现非常直白:

static IconData fromName(String name) {
  return info[name]?.icon ??
      (throw ArgumentError.value(name, 'name', 'unknown symbol'));
}

名称不存在时会抛出 ArgumentError,在不确定名称来源时,先用 OhosIcons.info.containsKey(name) 判断更稳妥:

IconData icon;
if (OhosIcons.info.containsKey(name)) {
  icon = OhosIcons.fromName(name);
} else {
  icon = OhosIcons.xmark_circle; // 兜底图标
}

5.2 遍历与分类筛选(图标市场 / 选择器)

OhosIcons.values 暴露全部元数据,配合 categories 即可实现"图标市场"式的筛选浏览:

// 全部图标
for (final meta in OhosIcons.values) {
  debugPrint('${meta.name}: ${meta.label}');
}

// 按分类筛选:所有"通信"类图标
final communication = OhosIcons.values
    .where((m) => m.categories.contains('通信'))
    .toList();

// 全部分类(17 个,已排序)
final categories = OhosIcons.categories;

仓库自带的 gallery 示例正是这一模式的完整实现:顶部搜索框支持英文名/中文名双路模糊匹配,横向 ChoiceChip 分类栏由 OhosIcons.categories 驱动,下方 GridView 渲染全部图标。

5.3 展示中文名与版本信息

元数据里的 label(中文名)与 supportVersion(最低版本)可以直接用于 UI 提示:

final meta = OhosIcons.info['wifi']!;
Tooltip(
  message: '${meta.label} · ${meta.supportVersion}',
  child: Icon(meta.icon, size: 32),
);

对于需要版本兼容提示的场景(例如图标需要 HarmonyOS 5.0+,而目标设备可能是更低版本),supportVersion 字段提供了判断依据,可据此做降级替换。

5.4 RTL 镜像变体

方向性图标(箭头、播放键等)在从右向左的阅读方向下需要镜像字形。OhosIconMeta 提供 mirrorIcon 访问器,在 TextDirection.rtl 环境下切换:

final meta = OhosIcons.info['airplane_fill']!;
final icon = meta.mirrorIcon ?? meta.icon;
Icon(icon);

5.5 可变字重(wght 轴)

打包的字体保留了 wght 可变字重轴(40–900)。Icon / IconData 渲染时默认 Regular(400);需要更粗或更细的笔画时,用 Text 直接渲染码点字符并指定 fontVariations:

Text(
  String.fromCharCode(OhosIcons.info['wifi']!.codepoint),
  style: const TextStyle(
    fontFamily: 'HMSymbol',
    fontSize: 48,
    color: Colors.black,
    fontVariations: [FontVariation('wght', 700)], // 渲染 Bold
  ),
)

提示:Icon 组件不支持 fontVariations,这是 Flutter 的限制。需要非默认字重时,走 Text + 码点的方案即可,视觉效果与 Icon 等价。

六、鸿蒙平台适配实践(重点)

本章回答本文最核心的问题:这个图标库在鸿蒙(OpenHarmony)上到底怎么跑? 以下流程已在 OpenHarmony 模拟器上完整验证。

6.1 安装鸿蒙社区 Flutter 分叉

以 fvm(Flutter 版本管理工具)管理分叉为例:

# 添加鸿蒙社区分叉源
fvm fork add ohos https://atomgit.com/CPF-Flutter/flutter_flutter

# 安装并切换社区版本
fvm install ohos/oh-3.35.7-release
fvm use ohos/oh-3.35.7-release

配置完成后,项目内所有 fvm flutter xxx 命令都会走鸿蒙分叉的 Flutter,flutter doctor 应能看到 OHOS 设备条目。

6.2 配置 DevEco Studio 工具链环境变量

鸿蒙分叉的构建依赖 DevEco Studio 自带的工具链(ohpm、node、hvigor、hdc 等)。以 macOS 默认安装路径为例:

export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export PATH=/Applications/DevEco-Studio.app/Contents/tools/ohpm/bin:$PATH
export PATH=/Applications/DevEco-Studio.app/Contents/tools/node/bin:$PATH
export PATH=/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin:$PATH
环境变量 / 工具作用
DEVECO_SDK_HOMEOpenHarmony SDK 根目录,构建期读取
JAVA_HOMEhvigor 构建工具所需的 JDK(DevEco 内置 JBR)
ohpmOpenHarmony 包管理器,拉取 ohos/ 工程的依赖
nodehvigor 脚本运行时
hvigorOpenHarmony 构建工具(生成 HAP)
hdc设备/模拟器调试通道(类似 adb),安装与启动应用

6.3 一键运行到模拟器:run_ohos.sh

仓库提供了 tool/run_ohos.sh 脚本,一条命令完成 构建 → 安装 → 启动 全流程。核心逻辑如下:

# 1. 构建未签名的 debug HAP
fvm flutter build hap --debug --no-codesign

# 2. 通过 hdc 安装到设备/模拟器
hdc install -r build/ohos/hap/entry-default-unsigned.hap

# 3. 启动 entry  Ability
hdc shell aa start -a EntryAbility -b com.example.harmony_icons_example

脚本内部按顺序执行四步:

  1. 设置 DevEco 工具链环境变量(支持 DEVECO_APP、OHOS_SDK、HDC 等覆盖);
  2. flutter build hap --debug --no-codesign 产出未签名 debug HAP;
  3. hdc install -r 覆盖安装到已连接设备或模拟器;
  4. hdc shell aa start 拉起 EntryAbility,应用即刻可见。

为什么用"未签名 HAP + hdc"而不是 flutter run?
在 ohos 分叉上,flutter run 要求 DevEco 的自动签名(需要华为账号登录 Signing Configs)。而 OpenHarmony 模拟器接受未签名的 debug HAP,因此脚本绕开签名直接 hdc install,无需任何账号配置。如需热重载/断点调试,可在 DevEco Studio 中打开 example/ohos 工程,勾选 Signing Configs 自动签名后再用 flutter run。

使用方式:

./tool/run_ohos.sh            # 默认设备
./tool/run_ohos.sh <device-id>  # 多设备时指定

6.4 真机/模拟器运行截图

下面是 gallery 示例应用在 OpenHarmony 模拟器 上的实际运行效果(截图右上角为模拟器 DEBUG 标记):

请添加图片描述

图 1:ohos_icons gallery 运行于 OpenHarmony 模拟器,534 个 HM Symbol 图标全部正常渲染

截图中可以看到完整的界面能力:

  • 顶栏:ohos_icons gallery 标题 + 系统状态栏(时间、信号、电量);
  • 搜索框:支持按英文名 / 中文名模糊搜索,输入即过滤;
  • 分类 Chips:全部 / 交通出行 / 人物 / 办公文件……由 OhosIcons.categories 动态生成,横向滚动;
  • 统计行:共 534 个图标 (534 总, v2.3),实时反映筛选结果与数据版本;
  • 图标网格:每个格子渲染 HM Symbol 字形 + 英文标识,点击弹出详情弹窗(大图标 + 中文名 + 模块 + 最低版本)。

534 个图标在鸿蒙平台上无一缺字形、无一显示方块,说明字体子集化与 fontPackage 跨包引用机制在 ohos 平台上工作正常——这是本库适配鸿蒙最关键的验证点。

七、工程化:图标与字体的脚本化生成

harmony_icons 的所有"数据"——图标常量、元数据、字体、SVG——全部由脚本从官网源头生成,不依赖任何人工整理。这意味着官方发布 HarmonyOS Symbol 3.0 时,跑一遍脚本即可升级整个库。

7.1 从官网下载图标元数据与字体

python3 tool/download_source.py

脚本从华为开发者官网拉取最新的 name_map_new.json(图标名/中文名/分类/版本元数据)与官方完整字体 HMSymbol.ttf(约 4.2MB),落盘到 tool/data/,并记录 data_versions.json 用于版本追踪。

7.2 字体子集化:4.2MB → 360KB

官方完整字体除了 534 个图标码点外还包含其他字形,直接打包进 Flutter 应用会白白增加体积。tool/subset_font.py 基于 fonttools 做子集化:

pip install fonttools   # 仅子集化/导出需要
python3 tool/subset_font.py

子集化只保留 534 个图标码点对应的字形,产物约 360KB(压缩前),体积缩小约 91%。关键细节:wght 可变字重轴在子集化后被完整保留,因此第五节的可变字重用法依然可用。

提示:子集化时务必保留 wght 轴与 mirrorCodepoint 对应的镜像字形,否则可变字重与 RTL 能力会丢失。这是 fonttools pyftsubset 参数配置中最容易踩的坑。

7.3 生成 Dart 常量与元数据

python3 tool/generate_icons.py

generate_icons.py 读取 tool/data/name_map_new.json,生成 lib/src/harmony_icons.dart:

  • 534 个 IconData 常量(OhosIcons.<name>),每个常量上方带文档注释(/// 飞行模式 · airplane_fill · HarmonyOS 5.0+);
  • OhosIcons.info 元数据 Map(Map<String, OhosIconMeta>,与常量一一对应);
  • 文件头部标注生成来源与数据版本,防止误改。

生成后跑一遍测试即可校验一致性(见 8.2),CI 中已配置自动执行。

7.4 高保真 SVG 导出(可选)

python3 tool/export_svg.py

export_svg.py 利用 fonttools 把每个图标字形渲染导出为矢量 SVG(tool/export/svg/),适用于需要 SVG 场景(Web 展示、设计稿交付、图标对比评审)。

工具链总览:

工具作用
tool/download_source.py从华为官网下载图标元数据与字体
tool/subset_font.py将 4.2MB 官方字体裁剪为约 360KB 图标子集
tool/generate_icons.py生成 lib/src/harmony_icons.dart(常量 + 元数据)
tool/export_svg.py导出每个图标的高保真矢量 SVG
tool/run_ohos.sh构建 example 并运行到鸿蒙模拟器

八、设计哲学与质量保障

8.1 零依赖、纯 Dart 的设计

harmony_icons 刻意保持极简:

  • 依赖只有一项:flutter SDK 本身,无任何第三方包;
  • 零平台相关代码:没有 MethodChannel、没有平台插件、没有 ohos/ 目录下的 Dart 插件层——图标库的全部能力都建立在"字体 + 码点"这一 Flutter 通用机制上;
  • 双端兼容的必然结果:正因为没有平台代码,同一份包在鸿蒙社区分叉与官方 Flutter SDK 上表现一致,适配工作全部沉淀在 example/ohos 工程壳与构建脚本中。

这是"三方库适配 OpenHarmony"的一个范式:优先寻找与目标平台同构的资产形态(字体/资源/协议),把平台差异留在构建层,而不是往 Dart 层塞平台通道。 图标、字体、图片、协议这类库大多适用该思路。

8.2 一致性测试

生成代码最大的风险是"常量与元数据漂移"——常量表、元数据 Map、字体码点三者不一致时,图标会静默显示为空白。仓库用测试锁住这条约束:

test('constants are backed by metadata with the same codepoint', () {
  for (final entry in OhosIcons.info.entries) {
    final meta = entry.value;
    expect(meta.name, entry.key);
    expect(meta.icon.fontFamily, 'HMSymbol');
    expect(meta.icon.fontPackage, 'harmony_icons');
    expect(meta.icon.codePoint, meta.codepoint);
  }
  expect(OhosIcons.count, OhosIcons.info.length);
  expect(OhosIcons.names.toSet().length, OhosIcons.count); // 无重名
});

配合 GitHub Actions CI(flutter analyze + flutter test),每次生成后都能自动拦截数据不一致问题。

8.3 为什么图标数量是 534 而不是官网的 579

官网页面展示的图标总数(约 579)是按分类条目统计的:同一图标会出现在多个分类下(例如 wifi 同时属于"系统UI"和"连接")。本包按 name 去重后得到 534 个唯一图标。对应用而言,"唯一图标数"才是有意义的指标——多出来的 45 个只是重复归类,并非独立字形。

九、常见问题 FAQ

9.1 图标显示为方块/问号?

按以下顺序排查:

  1. 确认 IconData 的 fontPackage 是 harmony_icons(直接使用 OhosIcons 常量时无需关心,自定义 IconData 时注意别覆盖);
  2. 确认依赖已正确解析(flutter pub get 后检查 pubspec.lock);
  3. 鸿蒙工程场景下,确认 ohos 壳工程执行过 ohpm install 并重新构建。

本包 pubspec 已内置 HMSymbol 字体声明,正常接入无需额外配置。

9.2 能按官方 17 个分类浏览吗?

可以。OhosIcons.categories 返回排序去重后的全部分类列表,配合 OhosIconMeta.categories(注意是复数,一个图标可属于多个分类)做筛选即可,gallery 示例(第六节截图)就是现成实现。

9.3 官方 Flutter SDK 上能用吗?

能。本库不含任何平台相关代码,在官方 Flutter 上运行与在鸿蒙分叉上行为一致——图标渲染走的是 Flutter 通用字体机制。差异只在构建/运行链路:官方 SDK 跑不了 example/ohos 工程,但库本身与 example 的 Dart 部分完全通用。

总结

harmony_icons 证明了"三方库适配 OpenHarmony"可以做得非常轻量:当目标平台的资产形态(HM Symbol 字体 + JSON 元数据)与 Flutter 的渲染机制(字体 + 码点)天然同构时,一个零依赖、纯 Dart 的包就能同时跑在鸿蒙与官方 Flutter 上,平台差异被收敛到构建脚本与工程壳中。本文从背景、安装、API、五种核心用法,到鸿蒙模拟器实测运行、官网数据工程化生成,完整走了一遍这条路径;534 个图标在 OpenHarmony 模拟器上全量渲染成功的截图,就是这套方案可行性的直接证据。

下一篇,我们将继续"Flutter 三方库适配 OpenHarmony"系列,带来网络库/平台通道类的适配实战——当"同构"不再成立时,平台通道该怎么桥接,我们下期见。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

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

更多推荐