欢迎光临
我们一直在努力

HarmonyOS SymbolGlyph 实战:从 Unicode 字符迁移到语义化系统图标

Unicode 图标为什么适合原型、不适合长期数据

项目早期为了快速验证功能,曾经用 Unicode 字符表示图标:

☀ ☾ ✓ ♥ ♫

优点很明显:

  • 不需要准备图片资源;
  • Text 组件就能显示;
  • 写几个字符即可覆盖原型。

但进入正式版本后,问题也会出现:

  • 不同字体的字形差异很大;
  • 某些字符在不同设备上基线不一致;
  • 字符可能被渲染为彩色 Emoji;
  • 图标粗细无法和系统风格统一;
  • 持久化后很难知道“这个字符的业务含义是什么”;
  • 更换图标体系时需要迁移旧数据。

因此 HarmonyOS 版本最终使用 SymbolGlyph 和稳定语义 Key。

一、磁盘里保存语义,不保存 Resource 对象

习惯模型中的图标字段是字符串:

export interface Habit {
id: string;
name: string;
icon: string;
color: string;
// 其他字段
}

但这里的字符串不再是“☾”,而是:

moon_z
figure_run
book
drop
checkmark_circle

这些值是应用自己的语义协议。

好处是:

  • 不依赖某个具体系统资源对象能否 JSON 序列化;
  • 业务数据可以跨版本保存;
  • 将来替换成自定义图标时不必重写 Habit;
  • 中英日切换不影响图标身份;
  • 导出文件仍然可读。

二、通过一层映射连接业务 Key 与系统 Symbol

页面集中维护:

private iconResource(icon: string): Resource {
switch (normalizedIconKey(icon)) {
case 'sun_max':
return $r('sys.symbol.sun_max');
case 'face_smiling':
return $r('sys.symbol.face_smiling');
case 'water_waves':
return $r('sys.symbol.water_waves');
case 'moon_z':
return $r('sys.symbol.moon_z');
case 'figure_run':
return $r('sys.symbol.figure_run');
case 'book':
return $r('sys.symbol.book');
default:
return $r('sys.symbol.circle');
}
}

这层映射看起来比直接在页面写 $r() 多了一步,却带来清晰边界:

业务数据:moon_z
→ 映射层:sys.symbol.moon_z
→ UI:SymbolGlyph

系统资源名变化或设计决定替换图标时,只需调整映射层。

三、封装统一的图标 Builder

项目用一个 Builder 统一尺寸和颜色入口:

@Builder
private AppIcon(
icon: string,
size: number,
color: string
) {
SymbolGlyph(this.iconResource(icon))
.fontSize(size)
.fontColor([color])
}

圆形底图也进一步封装:

@Builder
private IconBadge(
icon: string,
size: number,
color: string,
diameter: number,
background: string
) {
Stack({ alignContent: Alignment.Center }) {
this.AppIcon(icon, size, color)
}
.width(diameter)
.height(diameter)
.backgroundColor(background)
.borderRadius(diameter / 2)
}

这样心情、标签、习惯和设置页不会各自维护一套 Symbol 样式。

四、旧 Unicode 数据必须在读取路径中兼容

应用一旦发布,磁盘数据就是需要长期兼容的协议。

不能把编辑器中的 Unicode 替换成新 Key 后,就假设旧用户数据自动变化。

项目提供归一化函数:

export function normalizedIconKey(icon: string): string {
switch (icon) {
case '☀': return 'sun_max';
case '≈': return 'water_waves';
case '☾': return 'moon_z';
case '▣': return 'briefcase';
case '↗': return 'figure_run';
case '◇': return 'drop';
case '▤': return 'book';
case '✓': return 'checkmark_circle';
case '♥': return 'heart';
case '♫':
case '♬': return 'music';
default: return icon;
}
}

显示图标和打开习惯编辑器时都先归一化:

this.habitIconDraft = normalizedIconKey(
habit?.icon ?? HABIT_ICONS[0]
);

这样旧数据仍能显示,用户下次保存习惯时会自然迁移到新的语义 Key。

五、懒迁移和一次性迁移怎么选

当前项目使用“读取兼容、编辑时迁移”的懒迁移。

适合:

  • 旧格式数量很少;
  • 映射完全确定;
  • 不迁移也不影响显示;
  • 数据规模小。

另一种方式是在 Repository 加载后一次性改写:

parsed.habits = parsed.habits.map((habit) => ({
habit,
icon: normalizedIconKey(habit.icon)
}));

然后保存新版本。

一次性迁移适合:

  • 后续代码不希望长期携带兼容分支;
  • 新旧格式混用会造成错误;
  • 迁移结果可验证;
  • 失败时有回滚策略。

无论选择哪种,都不应该直接把未知值清空。

六、未知图标需要稳定回退

导入文件、测试版本或未来资源变化可能带来未知 Key。

映射层默认返回:

return $r('sys.symbol.circle');

回退图标的目标不是“看起来完美”,而是保证:

  • 页面不会因为单个坏值崩溃;
  • 用户仍能打开编辑器并重新选择;
  • 导出和其他数据不受影响;
  • 问题可以通过日志或测试定位。

七、图标颜色也应该是数据的一部分吗

当前习惯保存:

color: '#5E7CE2'

这让用户选择的颜色能够持久化,但也意味着设计系统调整时,旧数据仍保留旧色值。

可以有两种模型:

保存实际色值

适合允许用户自由选择颜色,导出结果直观。

保存语义色 Key

例如:

habit-blue
habit-mint
habit-gold

适合需要统一适配深色模式或未来更换主题的产品。

当前项目色板固定且规模较小,直接保存色值足够。如果后续增加深色模式和主题系统,语义色 Key 会更易维护。

八、Symbol 不能替代无障碍名称

一个“锁”图标对开发者很直观,但屏幕阅读器需要知道它代表“应用锁”还是“隐私政策”。

因此可点击 Symbol 应与明确文本、控件标签或无障碍描述结合,不能只依靠图形表达业务操作。

尤其要避免:

  • 多个只显示图标的按钮没有名称;
  • 颜色是区分状态的唯一方式;
  • 选中态只改变图标粗细;
  • 删除和归档使用过于相似的图形。

九、系统 Symbol 的测试清单

  • 所有配置 Key 都能映射到资源;
  • 未知 Key 显示回退圆形;
  • 旧 Unicode 数据能打开和编辑;
  • 不同字号下图标没有截断;
  • 选中和未选中颜色对比清楚;
  • 中文、英文、日文下布局一致;
  • 真机系统版本包含使用到的 Symbol;
  • 重要操作有文字或无障碍名称;
  • 导出再导入后语义 Key 不丢失。

总结

从 Unicode 字符迁移到 SymbolGlyph,真正重要的不是“图标更好看”,而是建立了一套稳定协议:

  • 磁盘保存业务语义 Key;
  • 映射层连接系统 Symbol;
  • Builder 统一视觉样式;
  • 旧数据通过归一化函数兼容;
  • 未知值有安全回退;
  • 图标仍需要无障碍语义。
  • 这套模式同样适用于系统图标、自定义 SVG 和图片资源之间的后续替换。

    本文案例来自“心晴手记(MoodMemoir)”HarmonyOS 版的心情、标签与习惯图标系统。

    参考资料

    • HarmonyOS 图标小符号 SymbolGlyph/SymbolSpan

    赞(0)
    未经允许不得转载:171主机测评 » HarmonyOS SymbolGlyph 实战:从 Unicode 字符迁移到语义化系统图标
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址