图标与颜色系统------构建可主题化的视觉体系

一、引言
在大型应用中,一致且可主题化的视觉体系是保证产品品质的基础。颜色和图标作为最基础的视觉元素,如果随意使用散落的色值和图标引用,将导致维护成本急剧上升,主题切换更无从谈起。
HarmonyOS 提供了 $r() 资源引用语法和 color.json / float.json 等资源文件,让我们能够以工程化的方式管理视觉体系。本文从颜色系统、图标选择和暗色模式三个维度,结合英语学习 App 的实践,详细讲解如何构建可主题化的视觉体系。
二、颜色系统:从散落色值到集中管理
2.1 资源文件结构
HarmonyOS 使用 color.json 文件集中管理颜色变量。同时,系统预置了丰富的 sys.color.* 资源可供直接使用:
resources/
├── base/
│ └── element/
│ ├── color.json # 基础颜色定义
│ └── float.json # 间距、字号等浮点数值
├── dark/
│ └── element/
│ └── color.json # 暗色模式颜色覆盖
2.2 系统级颜色资源
项目中大量使用系统预置的 sys.color.* 资源,这些资源会根据系统主题(浅色/深色)自动切换:
typescript
// 字体颜色层级------系统资源
.fontColor($r('sys.color.font_primary')) // 主要文字(浅色模式为 #000000,深色模式为 #FFFFFF)
.fontColor($r('sys.color.font_secondary')) // 次要文字(用于副文本)
.fontColor($r('sys.color.font_tertiary')) // 三级文字(用于提示文本)
// 背景颜色层级------系统资源
.backgroundColor($r('sys.color.background_primary')) // 白色背景
.backgroundColor($r('sys.color.background_secondary')) // 浅灰背景
.backgroundColor($r('sys.color.comp_background_tertiary')) // 三级容器背景
字体颜色层级的最佳实践:
| 层级 | 资源名 | 使用场景 | 示例 |
|---|---|---|---|
| 一级 | font_primary |
标题、正文核心内容 | 单词名、课程名称 |
| 二级 | font_secondary |
副标题、辅助信息 | 音标、学习进度 |
| 三级 | font_tertiary |
提示、占位符 | "点击卡片翻转" |
2.3 应用级颜色定义
除了系统资源,项目在不同模块的 color.json 中定义了业务相关的颜色:
json
// AppScope/resources/base/element/color.json
{
"color": [
{
"name": "system_color_background_white",
"value": "#ffffff"
},
{
"name": "system_color_background_gray",
"value": "#F1F3F5"
}
]
}
// product/entry/src/main/resources/base/element/color.json
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
},
{
"name": "icon_bg_blue",
"value": "#4B5CC4"
}
]
}
// features/minePage/src/main/resources/base/element/color.json
{
"color": [
{
"name": "time_btn",
"value": "#4B5CC4"
}
]
}
2.4 主色与业务色彩
项目中直接使用的特定色值(非 sys.color 资源)主要分为几类:
主题蓝色系(主品牌色):
typescript
// #165DFF ------ 主品牌色,用于标题和关键操作
Text(this.currentWord().word)
.fontSize(34).fontWeight(FontWeight.Bold)
.fontColor('#165DFF'); // 单词使用主题蓝突出显示
Button('右滑 已掌握')
.backgroundColor('#165DFF'); // 按钮主色
// #EAF2FF ------ 蓝色浅背景,用于标签/徽章
Text(this.currentWord().partOfSpeech)
.fontSize(12).fontColor('#165DFF')
.backgroundColor('#EAF2FF') // 浅蓝色背景标签
.borderRadius(12);
功能色彩:
typescript
const COLORS = {
favorite: '#FFB020', // 收藏金黄色 ------ ★ 收藏星标
success: '#64BB5C', // 绿色 ------ 答题正确
error: '#E84026', // 红色 ------ 答题错误
primary: '#4B5CC4', // 蓝紫色 ------ icon 背景、特殊按钮
warning: '#FFc000', // 橙黄 ------ 待复习按钮
};
2.5 颜色命名规范
定义应用级颜色时,建议遵循以下命名约定:
| 命名模式 | 示例 | 说明 |
|---|---|---|
{module}_{purpose} |
time_btn |
模块前缀 + 用途 |
{area}_{role} |
topic_page_background_gray |
区域 + 角色 |
{component}_{state} |
icon_bg_blue |
组件 + 状态 |
三、图标系统:SVG vs PNG 的选择策略
3.1 统一的图标引用方式
在 HarmonyOS 中,图标通过 $r('app.media.*') 引用媒体资源:
typescript
// 首页核心功能图标
new CourseCoreBar($r('app.media.ic_search'), '搜单词')
new CourseCoreBar($r('app.media.ic_practice_camera'), '搜单词')
new CourseCoreBar($r('app.media.ic__practice_wrong_question'), '易错词')
new CourseCoreBar($r('app.media.ic_practice_star'), '生词本')
new CourseCoreBar($r('app.media.ic_practice_notes'), '笔记')
new CourseCoreBar($r('app.media.ic_chart_report'), '学习报告')
// 练习模式图标
new PracticeView($r('app.media.ic_sequence'), '单词记忆', '每日单词打卡')
new PracticeView($r('app.media.ic_practice_simulations'), '听力训练', '沉浸式听力练习')
new PracticeView($r('app.media.ic_practice_test_paper'), '阅读训练', '英文原著阅读')
new PracticeView($r('app.media.ic_wrong_question'), '语法练习', '语法专项突破')
3.2 SVG 与 PNG 的选取标准
| 维度 | SVG | PNG |
|---|---|---|
| 缩放质量 | 无损,任意尺寸清晰 | 有损,放大有锯齿 |
| 文件大小 | 较小(复杂图形除外) | 取决于分辨率 |
| 支持颜色修改 | 可通过 CSS 改变 | 不能直接修改 |
| 兼容性 | 部分旧设备可能不支持 | 全兼容 |
| 适用场景 | 简单图标、Logo | 复杂插图、照片 |
项目实践建议:
- 功能图标使用 SVG (如
ic_search、ic_practice_star):尺寸小、颜色统一、支持主题色 - Banner 使用 PNG/JPG (如
banner1):包含渐变和复杂视觉效果,SVG 难以表现 - 准备双份资源 :在
media/目录存放 SVG/PNG,在media/提供同名的 PNG 备选
3.3 图标尺寸规范
项目中图标尺寸的引用分两种方式:
固定尺寸(直接写数值):
typescript
// 小图标 24×24
Image($r('app.media.ic_search')).width(28).height(28);
// 中图标 32×32
Image(this.item.imageSrc).width('32vp').height('32vp');
// 大图标 40×40
Image(this.item.imageUri).width(40).height(40);
资源变量引用(使用 float.json 中的变量):
typescript
// 通过 $r('app.float.*') 引用
.width($r('app.float.QuestionModel_ICON_SIZE'))
.height($r('app.float.QuestionModel_ICON_SIZE'))
// 间距也通过 float 资源管理
.padding($r('app.float.vp_12'))
.margin($r('app.float.vp_16'))
使用 app.float 资源管理间距和尺寸的好处是:当需要全局调整间距时,只需修改一处 float.json 即可生效。
四、暗色模式:自动与手动切换
4.1 颜色资源覆盖机制
HarmonyOS 的暗色模式通过 dark/element/color.json 目录实现。当系统切换到深色模式时,同名颜色会自动覆盖:
json
// product/entry/src/main/resources/base/element/color.json(浅色)
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}
// product/entry/src/main/resources/dark/element/color.json(深色)
{
"color": [
{
"name": "start_window_background",
"value": "#000000"
}
]
}
// components/login_info/dark/element/color.json
{
"color": [
{
"name": "login_page_bg",
"value": "#202224" // 深色模式登录页背景
}
]
}
// components/answer_questions/dark/element/color.json
{
"color": [
{
"name": "answer_sheet_bg",
"value": "#202224" // 深色模式答题卡背景
}
]
}
4.2 sys.color 的自动适配
使用 $r('sys.color.*') 引用系统颜色资源时,暗色模式适配由系统自动完成:
typescript
// 无需任何修改,自动适配暗色模式
Text('Hello')
.fontColor($r('sys.color.font_primary')) // 浅色=#000000, 深色=#FFFFFF
backgroundColor($r('sys.color.background_primary')) // 浅色=#FFFFFF, 深色=#1C1C1E
使用 sys.color 的三个原则:
- 文字颜色 :始终使用
font_primary/secondary/tertiary,不用硬编码 - 背景颜色 :始终使用
background_primary/secondary,不用硬编码 - 分隔线/边框 :使用
comp_divider等系统资源
4.3 主题切换的手动控制
项目提供了"跟随系统 / 普通模式 / 夜间模式"三种选项,通过 ConfigurationConstant 实现:
typescript
// features/minePage/src/main/ets/views/SetupPage.ets
private switchColorMode(item: string): void {
let colorMode: number;
if (item === '跟随系统') {
colorMode = ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET; // -1
} else if (item === '普通模式') {
colorMode = ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT; // 1
} else {
colorMode = ConfigurationConstant.ColorMode.COLOR_MODE_DARK; // 0
}
this.getUIContext()?.getHostContext()?.getApplicationContext()
.setColorMode(colorMode);
PreferenceUtil.getInstance().put(PreferConstant.COLOR_MODE, colorMode);
}
实现要点:
COLOR_MODE_NOT_SET(-1):跟随系统,这是推荐默认值COLOR_MODE_LIGHT(1):强制浅色模式COLOR_MODE_DARK(0):强制深色模式- 设置后需要持久化到 Preferences,确保下次启动时恢复用户选择
4.4 sys 资源 vs app 资源使用场景
| 资源引用 | 适用场景 | 示例 |
|---|---|---|
$r('sys.color.font_primary') |
文字颜色 | 标题、正文 |
$r('sys.color.background_primary') |
页面/卡片背景 | 内容区、卡片 |
$r('sys.float.Body_S') |
系统字号 | 正文文字 |
$r('sys.float.Caption_M') |
辅助字号 | 说明文字 |
$r('app.color.system_color_background_white') |
应用自定义颜色 | 业务专用色 |
$r('app.float.vp_12') |
应用间距 | padding、margin |
五、最佳实践总结
5.1 编码规范
- 绝不硬编码颜色值 :除主题品牌色(
#165DFF)外,全部使用$r()资源引用 - 主题色使用
sys.color:优先使用系统资源,确保自动适配暗色模式 - 业务色定义在
color.json:应用级颜色按模块分文件管理 - 图标统一
$r('app.media.*'):不使用网络图片图标
5.2 颜色检查清单
- 所有文字颜色使用
sys.color.font_*层级 - 卡片/页面背景使用
sys.color.background_* - 业务特定色值在
color.json中定义并引用 - 暗色模式资源在
dark/element/color.json中覆盖 - 主题切换逻辑持久化用户偏好
六、总结
可主题化的视觉体系是现代应用开发的基石。通过 color.json 集中管理颜色变量、$r() 统一引用资源、dark/ 目录提供暗色覆盖,我们构建了一个支持浅色/深色无缝切换的视觉体系。图标方面,根据场景选择 SVG 或 PNG,配合 app.media 资源引用,保证了图标的统一管理。
颜色和图标的管理看似是"小事",但正是这些细节决定了应用的主题化能力和后期维护成本。合理的资源规划,让英语学习 App 在功能迭代的同时,始终保持着一致的视觉品质。