HarmonyOS 6.1 深色模式与主题适配实战:三种实现方案的真实对比
基于 ThemeDemo 实战验证,覆盖 @State 条件函数、AppStorage 全局主题、资源限定词三种方案,附带完整可运行代码
写在前面
做深色模式这事儿,很多人觉得"不就是换个背景色嘛"。天真。你把背景色改成 #121212,结果文字还是 #333333——恭喜,深色背景配深色文字,用户什么都看不见。
深色模式的核心不是改颜色,是保证所有颜色都有对应的深色版本,并且切换时不能有遗漏。这篇文章把三种实现方案都走了一遍,坑多得你不敢信。

一、方案一:@State + 条件函数(Demo 采用)
最直觉的方案——用 @State isDark: boolean 驱动,所有颜色通过函数返回:
@State isDark: boolean = false
private bgColor(): string {
return this.isDark ? '#121212' : '#ffffff'
}
private cardBg(): string {
return this.isDark ? '#1e1e1e' : '#f8f8f8'
}
private textColor(): string {
return this.isDark ? '#e0e0e0' : '#333333'
}
UI 里所有硬编码颜色替换成函数调用:
Column() {
Text('标题')
.fontColor(this.textColor())
.backgroundColor(this.cardBg())
}
.backgroundColor(this.bgColor())
优点
- 实现简单,不需要额外架构
- 切换即时生效,@State 变了 UI 自动刷新
- 方便调试,isDark 一眼就知道当前主题
缺点
- 颜色散落在各组件的函数里,不好统一管理
- 新增颜色需要在每个组件里加函数
- 页面多了,函数重复率很高
适合场景
小项目、单页面 Demo、快速验证。颜色不多的时候这个方案够用。

二、方案二:AppStorage + @StorageLink 全局主题
项目稍微大一点,就需要全局管理主题色。把颜色存到 AppStorage,所有组件通过 @StorageLink 监听:
// 初始化(EntryAbility 或首组件 aboutToAppear)
AppStorage.setOrCreate('themeBgColor', '#ffffff')
AppStorage.setOrCreate('themeCardBg', '#f8f8f8')
AppStorage.setOrCreate('themeTextColor', '#333333')
AppStorage.setOrCreate('themePrimaryColor', '#1a73e8')
AppStorage.setOrCreate('isDarkMode', false)
// 各组件
@StorageLink('themeBgColor') bgColor: string = '#ffffff'
@StorageLink('themeTextColor') textColor: string = '#333333'
切换主题时,统一修改 AppStorage:
toggleDarkMode(): void {
const isDark: boolean = !(AppStorage.get<boolean>('isDarkMode') ?? false)
AppStorage.setOrCreate('isDarkMode', isDark)
AppStorage.setOrCreate('themeBgColor', isDark ? '#121212' : '#ffffff')
AppStorage.setOrCreate('themeCardBg', isDark ? '#1e1e1e' : '#f8f8f8')
AppStorage.setOrCreate('themeTextColor', isDark ? '#e0e0e0' : '#333333')
}
优点
- 全局统一,一处修改处处生效
- 跨页面共享主题状态
- 新页面只需声明 @StorageLink,不用重复定义函数
缺点
- @StorageLink 的 key 必须先 setOrCreate,否则崩溃
- 主题色多了,初始化代码很长
- 需要维护一个主题色常量表
适合场景
中大型项目、多页面共享主题。主题色在 20 个以内的项目用这个方案最合适。
三、方案三:资源限定词(resources/dark/)
HarmonyOS 支持通过资源目录自动适配深色模式:
resources/
base/
element/
color.json // 浅色颜色
media/
icon.png // 浅色图片
dark/
element/
color.json // 深色颜色
media/
icon.png // 深色图片
UI 代码里用 $r() 引用资源,系统自动根据当前模式选择:
.backgroundColor($r('app.color.background'))
.fontColor($r('app.color.text_primary'))
优点
- 系统原生支持,跟随系统深色模式自动切换
- UI 代码不需要任何 isDark 判断
- 图片资源也能自动适配
缺点
- 只能跟随系统,不能应用内独立切换
- 资源文件维护成本高,新增颜色要改两个 color.json
- 调试不直观,看不出当前用的是哪个资源
适合场景
跟随系统深色模式的项目,不需要应用内手动切换主题。
四、三种方案对比
| 方案 | 切换方式 | 作用域 | 跨页面 | 图片适配 | 维护成本 | 适合规模 |
|---|---|---|---|---|---|---|
| @State+函数 | 应用内切换 | 组件级 | 需手动传递 | 不支持 | 低 | 小项目 |
| AppStorage | 应用内切换 | 全局 | 自动 | 不支持 | 中 | 中项目 |
| 资源限定词 | 跟随系统 | 全局 | 自动 | 支持 | 高 | 大项目 |
我的建议:中小项目用 AppStorage 方案,大项目用资源限定词 + AppStorage 组合。资源限定词处理跟随系统的自动适配,AppStorage 处理应用内手动切换。
五、主题色切换的实现
Demo 里还实现了主题色切换,用户可以选择不同的主色调:
@State primaryColor: string = '#1a73e8'
Row({ space: 8 }) {
ForEach(['#1a73e8', '#e65100', '#34a853', '#9c27b0', '#d32f2f'], (color: string) => {
Row()
.width(36).height(36).borderRadius(18)
.backgroundColor(color)
.border({ width: this.primaryColor === color ? 3 : 0, color: '#333333' })
.onClick(() => { this.primaryColor = color })
}, (color: string, index: number) => index.toString())
}
深色模式下,主题色需要用更亮的变体,否则在深色背景上对比度不够:
private lightColors: string[] = ['#1a73e8', '#e65100', '#34a853', '#9c27b0', '#d32f2f']
private darkColors: string[] = ['#64b5f6', '#ff9800', '#81c784', '#ce93d8', '#ef5350']

六、踩坑合集
坑一:只改背景色不改文字色
这是最常见的低级错误。改了背景色忘了改文字色,深色背景上深色文字,用户什么都看不见。检查清单:背景色、文字色、副文字色、分割线色、卡片色,全都要改。
坑二:阴影在深色模式下不可见
浅色模式下 shadow({ radius: 8, color: '#1a000000' }) 看着挺好的,深色模式下透明度 10% 的黑色阴影在 #121212 背景上完全看不见。深色模式的阴影应该用更亮的颜色或者改用边框替代。
坑三:Web/Canvas 内容不跟随主题
WebView 加载的网页、Canvas 绘制的内容不会自动适配深色模式。网页需要自己实现深色主题(CSS prefers-color-scheme),Canvas 需要重新绘制。
坑四:系统弹窗不跟随应用主题
AlertDialog.show() 等系统弹窗默认跟随系统主题,不受应用内 isDark 控制。如果你的应用是浅色模式但系统是深色,弹窗会显示深色。目前没有好的解决方案,只能用 CustomDialog 替代。
坑五:PNG 图标不好适配
PNG 图标在深色模式下需要准备两套,维护成本翻倍。建议用 SVG 格式,通过 .fillColor() 动态改颜色,一套资源搞定两种模式。
七、透明度调整
深色模式下,相同的透明度值视觉效果不一样。浅色背景上 10% 透明度的叠加色很淡,深色背景上同样的值可能完全看不出。
通用规律:
- 深色背景上,主色的透明度需要提高(10%→20%)
- 浅色背景上,辅助色的透明度可以降低(20%→10%)
Demo 里的做法是浅色色板和深色色板分开定义,避免运行时算透明度。
八、状态栏颜色
深色模式切换后,状态栏颜色也要跟着改。通过窗口实例设置:
const mainWindow = AppStorage.get<window.Window>('mainWindow')
if (mainWindow) {
mainWindow.setWindowSystemBarProperties({
statusBarColor: this.isDark ? '#121212' : '#ffffff',
statusBarContentColor: this.isDark ? '#e0e0e0' : '#333333'
})
}
如果忘了改状态栏,深色页面顶着白色状态栏,割裂感极强。
写在最后
深色模式看着简单,做起来全是细节。颜色遗漏一个地方就穿帮,阴影不调就消失,图标不换就别扭。建议做深色模式的时候列一个颜色清单,改一个划一个,不然一定会漏。
另外,如果项目时间紧,优先保证核心页面的深色模式。设置页、关于页这些次要页面可以后做,但首页和主功能页面必须第一时间适配。
更多推荐



所有评论(0)