系列文章《鸿蒙开发从入门到专家》第 58 篇 · 高级
第 44 篇讲 UX 规范时提到过设计系统,但没展开颜色系统怎么落地。这篇从代码层面讲:颜色令牌怎么设计、暗黑模式怎么切、运行时换肤怎么做。
大部分应用的颜色管理方式是在代码里到处写 #FF0000。改一次主色调要全局搜索替换,暗黑模式更是地狱——每个颜色都要写两份硬编码值。颜色令牌系统就是解决这个问题的。
一、颜色令牌体系
令牌(Token)是设计系统里的术语,指的是把颜色抽象成语义化变量。不是说"这个按钮用 #007AFF",而是说"这个按钮用 primaryColor"。primaryColor 在浅色模式下是 #007AFF,在暗色模式下是 #4A90D9。
1.1 三层令牌架构
┌────────────────────────────────────────────┐
│ 组件层令牌(Component Token) │
│ buttonBg → primaryColor │
│ cardBg → surfaceColor │
│ errorText → errorColor │
├────────────────────────────────────────────┤
│ 语义层令牌(Semantic Token) │
│ primaryColor → brand_blue │
│ surfaceColor → bg_primary │
│ errorColor → semantic_red │
├────────────────────────────────────────────┤
│ 基础层令牌(Base Token) │
│ brand_blue → #007AFF(浅)/ #0A84FF(深) │
│ bg_primary → #FFFFFF(浅)/ #1C1C1E(深) │
│ semantic_red → #FF3B30(浅)/ #FF453A(深)│
└────────────────────────────────────────────┘
三层分离的好处:组件层只依赖语义,语义层映射到基础颜色。换皮肤时只改基础层的映射,组件层代码不动。
1.2 资源文件定义
鸿蒙用 resource 文件管理多模式资源。在 resources/ 下分别为浅色和深色定义颜色:
resources/base/element/color.json(浅色模式):
{
"color": [
{ "name": "brand_blue", "value": "#007AFF" },
{ "name": "bg_primary", "value": "#FFFFFF" },
{ "name": "bg_secondary", "value": "#F2F2F7" },
{ "name": "semantic_red", "value": "#FF3B30" },
{ "name": "text_primary", "value": "#000000" },
{ "name": "text_secondary", "value": "#3C3C43" }
]
}
resources/dark/element/color.json(暗色模式):
{
"color": [
{ "name": "brand_blue", "value": "#0A84FF" },
{ "name": "bg_primary", "value": "#1C1C1E" },
{ "name": "bg_secondary", "value": "#2C2C2E" },
{ "name": "semantic_red", "value": "#FF453A" },
{ "name": "text_primary", "value": "#FFFFFF" },
{ "name": "text_secondary", "value": "#EBEBF5" }
]
}
resources/dark/ 目录下的资源会在系统暗色模式启用时自动生效。
1.3 在组件中使用
@Entry
@Component
struct ThemedPage {
build() {
Column() {
Text('Hello World')
.fontColor($r('app.color.text_primary'))
.fontSize(16)
Button('Action')
.backgroundColor($r('app.color.brand_blue'))
.fontColor($r('app.color.bg_primary'))
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.bg_primary'))
}
}
$r('app.color.xxx') 会根据当前系统模式自动取浅色或深色值。不需要在代码里判断暗黑模式。
二、暗黑模式适配
2.1 配置支持
在 module.json5 中声明应用支持的颜色模式:
{
"module": {
"name": "entry",
"type": "entry",
"colorMode": "auto"
}
}
auto 表示跟随系统模式。如果设为 light 或 dark,应用锁定模式不随系统切换。
2.2 监听模式切换
有些逻辑需要在模式切换时做额外处理(比如更新状态栏颜色):
import { ConfigurationConstant, common } from '@kit.AbilityKit';
export default class EntryAbility extends UIAbility {
onConfigurationUpdate(newConfig: ConfigurationConstant.Configuration): void {
if (newConfig.colorMode !== undefined) {
const isDark = newConfig.colorMode === ConfigurationConstant.ColorMode.COLOR_DARK;
console.info(`Color mode changed: ${isDark ? 'dark' : 'light'}`);
// 更新状态栏
this.updateStatusBar(isDark);
}
}
private updateStatusBar(isDark: boolean): void {
const window = this.windowStage.getMainWindowSync();
window.setWindowSystemBarProperties({
statusBarContentColor: isDark ? '#FFFFFF' : '#000000'
});
}
}
2.3 图片资源适配
暗色模式下图片也需要适配。在 resources/dark/media/ 下放置暗色版本的图片,文件名和浅色版一致。系统会自动选择。
resources/
base/
media/
logo.png ← 浅色版
empty_state.png ← 浅色版
dark/
media/
logo.png ← 暗色版
empty_state.png ← 暗色版
SVG 图标不需要准备两份。用 fillColor 引用颜色令牌:
Image($r('app.media.ic_search'))
.fillColor($r('app.color.text_secondary'))
三、运行时动态换肤
暗黑模式是跟随系统的,但有些应用需要提供"主题选择"功能:用户在应用内选主题色(蓝色、绿色、紫色…),实时生效。
3.1 方案:AppStorage + ResourceStr
// theme/ThemeManager.ts
export enum ThemeType {
BLUE = 'blue',
GREEN = 'green',
PURPLE = 'purple'
}
interface ThemeColors {
primary: string;
primaryLight: string;
primaryDark: string;
}
const themes: Record<ThemeType, ThemeColors> = {
[ThemeType.BLUE]: {
primary: '#007AFF',
primaryLight: '#5AC8FA',
primaryDark: '#004E92'
},
[ThemeType.GREEN]: {
primary: '#34C759',
primaryLight: '#7ED957',
primaryDark: '#248A3D'
},
[ThemeType.PURPLE]: {
primary: '#AF52DE',
primaryLight: '#D0B3FF',
primaryDark: '#6B2CA0'
}
};
export class ThemeManager {
private static currentTheme: ThemeType = ThemeType.BLUE;
static getTheme(): ThemeType {
return this.currentTheme;
}
static setTheme(theme: ThemeType): void {
this.currentTheme = theme;
const colors = themes[theme];
// 写入 AppStorage,触发 UI 刷新
AppStorage.set('primaryColor', colors.primary);
AppStorage.set('primaryLightColor', colors.primaryLight);
AppStorage.set('primaryDarkColor', colors.primaryDark);
}
static init(): void {
// 初始化默认主题
this.setTheme(this.currentTheme);
}
}
3.2 在组件中使用
@Entry
@Component
struct ThemedPage {
@StorageLink('primaryColor') primaryColor: string = '#007AFF';
build() {
Column() {
Text('Dynamic Theme')
.fontColor(this.primaryColor)
Button('Blue')
.backgroundColor(this.primaryColor)
.onClick(() => ThemeManager.setTheme(ThemeType.BLUE))
Button('Green')
.backgroundColor(this.primaryColor)
.onClick(() => ThemeManager.setTheme(ThemeType.GREEN))
}
}
}
@StorageLink 会自动监听 AppStorage 中对应 key 的变化,主题切换后所有引用了该 key 的组件自动刷新。
3.3 持久化用户选择
import { preferences } from '@kit.ArkData';
export class ThemeManager {
private static readonly PREF_KEY = 'theme_type';
static async loadSavedTheme(): Promise<void> {
const pref = await preferences.getPreferences(getContext(), 'app_prefs');
const saved = await pref.get(ThemeManager.PREF_KEY, ThemeType.BLUE) as ThemeType;
this.setTheme(saved);
}
static async saveTheme(theme: ThemeType): Promise<void> {
const pref = await preferences.getPreferences(getContext(), 'app_prefs');
await pref.put(ThemeManager.PREF_KEY, theme);
await pref.flush();
this.setTheme(theme);
}
}
应用启动时调 loadSavedTheme() 恢复上次选择。
四、暗黑模式适配清单
4.1 半透明处理
暗色模式下,浅色的半透明遮罩会很突兀。用令牌管理:
// base/element/color.json
{ "name": "overlay_bg", "value": "#80000000" }
// dark/element/color.json
{ "name": "overlay_bg", "value": "#CC000000" }
五、常见问题
问题 1:颜色不切换。 确认 module.json5 的 colorMode 设为 auto,且 resources/dark/ 目录结构和 resources/base/ 完全一致(element/color.json 和 media/ 下的文件名要对应)。
问题 2:图片闪烁。 模式切换时图片资源重新加载可能闪烁。对纯色图标用 fillColor 而非多套位图,避免加载开销。
问题 3:第三方 SDK 颜色不适配。 第三方组件可能用硬编码颜色,无法通过令牌切换。需要在 onConfigurationUpdate 中主动通知第三方 SDK 刷新主题。
小结
颜色令牌三层架构(基础→语义→组件)是设计系统落地的标准模式。暗黑模式用鸿蒙的 resources/dark/ 目录自动切换,代码里用 $r() 引用。运行时动态换肤用 AppStorage + @StorageLink 做响应式更新。核心原则:代码里不写硬编码颜色值,一切走令牌。
下篇讲无障碍开发。Accessibility 属性怎么用、语义树怎么管理。
参考阅读:
第 44 篇:鸿蒙设计规范(UX 适配实战)
第 50 篇:设计模式在鸿蒙里的应用
官方文档:color 资源、ConfigurationConstant