【鸿蒙心迹】深色模式适配全链路——从配置到自定义(HarmonyOS 7.x)
摘要: 系统切深色模式,应用没跟着变;图片在深色模式下刺眼;自定义绘制颜色没适配——深色模式不是"换个背景色",是颜色/图片/材质/状态栏的全链路适配。本文用涟漪睡眠 App 适配深色模式为案例,给完整方案与 5 个踩坑。
适用版本: HarmonyOS NEXT 7.x / API 14+(2026 年稳定版)
开篇:深色模式一开,界面变得半明半暗
“系统切深色了,你的 App 怎么还是白的?”
2026 年 8 月,涟漪睡眠 App 联调。产品同学系统切深色模式,App 纹丝不动——刺眼。深色模式不是简单换个 backgroundColor,是颜色资源、图片资源、材质、状态栏的全链路适配。这篇把完整链路讲透。

一、深色模式全链路

二、颜色资源适配

资源目录结构
resources/
base/ # 浅色(默认)
element/
color.json
dark/ # 深色
element/
color.json
// resources/base/element/color.json(浅色)
// 必须是 name/value 数组结构;写成键值对对象会在编译期报资源解析失败
{
"color": [
{ "name": "page_bg", "value": "#FFFFFF" },
{ "name": "text_primary", "value": "#333333" },
{ "name": "card_bg", "value": "#F5F5F5" }
]
}
// resources/dark/element/color.json(深色)
{
"color": [
{ "name": "page_bg", "value": "#121212" },
{ "name": "text_primary", "value": "#EEEEEE" },
{ "name": "card_bg", "value": "#1E1E1E" }
]
}
使用资源引用
@Entry
@Component
struct NewsPage {
build() {
Column() {
Text('标题').fontColor($r('app.color.text_primary'))
}
.backgroundColor($r('app.color.page_bg')) // 自动适配深浅色
}
}
避坑 1:不要硬编码颜色(.backgroundColor('#FFFFFF')),深色模式不会自动切换。必须用 $r('app.color.xxx')。
三、图片资源适配
深色模式用不同图片
resources/
base/
media/
logo.png # 浅色 logo
dark/
media/
logo.png # 深色 logo(反色版)
Image($r('app.media.logo')) // 自动按模式选
避坑 2:不是所有图片都需要深色版——照片类内容不用适配,图标/插画类需要。
四、监听模式切换

// 统一在 Ability 层监听,把当前模式写进 AppStorage;页面用 @StorageProp 订阅,不持有 Ability 引用
import { Configuration, ConfigurationConstant, UIAbility } from '@kit.AbilityKit';
export default class EntryAbility extends UIAbility {
onConfigurationUpdate(config: Configuration): void {
AppStorage.setOrCreate('isDark', config.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK);
}
}
Canvas 自定义绘制适配
$r 只管得到标准组件的颜色属性;Canvas 里 fillStyle 写的是什么,框架不知道,所以得自己订阅模式值并重绘。
@Entry
@Component
struct CanvasPage {
// @StorageProp 订阅 Ability 层写入 AppStorage 的模式值,变化时 @Watch 触发重绘
@StorageProp('isDark') @Watch('redraw') isDark: boolean = false;
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
private redraw(): void {
this.context.fillStyle = this.isDark ? '#EEEEEE' : '#333333';
this.context.fillRect(0, 0, 200, 200);
}
build() {
Canvas(this.context)
.width(200)
.height(200)
.onReady(() => this.redraw()) // onReady 只在首次就绪时触发一次
}
}
五、状态栏适配
import { window } from '@kit.ArkUI';
async function setupStatusBar(win: window.Window, isDark: boolean): Promise<void> {
await win.setWindowSystemBarProperties({
statusBarContentColor: isDark ? '#FFFFFF' : '#000000', // 状态栏字色
});
}
避坑 3:深色模式下状态栏字色必须变白,否则看不见。
六、5 个深色模式踩坑
1. 硬编码颜色不切换
解决:所有颜色用 $r('app.color.xxx'),base/dark 分别定义。
2. 图片在深色模式刺眼
解决:图标类图片提供 dark 版;照片类用 colorBlend 降亮度:
Image(item.cover).colorBlend(this.isDark ? '#40000000' : '#00000000')
3. 材质深色模式消失
解决:深色资源目录单独配材质参数,详见 第 18 篇:沉浸光感全链路。
4. WebView 内容不跟随
现象:应用切深色,WebView 里的 H5 内容还是浅色。
解决:向 WebView 注入一段自包含的深色样式,不依赖 H5 侧预先提供任何全局函数:
// webController 的类型是 webview.WebviewController,来自 @kit.ArkWeb
// 注入的是完整的 JS 语句:创建 style 节点 → 设 id → 写样式 → 挂到 head,H5 侧零改造
import { webview } from '@kit.ArkWeb';
const darkCss = `
const style = document.createElement('style');
style.id = 'harmony-dark';
style.textContent = 'body{background:#121212;color:#EEEEEE}';
document.head.appendChild(style);
`;
this.webController.runJavaScript(darkCss);
5. 强制深色模式
现象:部分页面想强制深色(如阅读页),不受系统模式影响。
解决:
// 应用级强制:setColorMode 只改变 ResourceManager 的资源查找方向,
// 硬编码颜色、缺 dark 版的图片不会被"强制",所以前提是全量资源已双份
import { common, ConfigurationConstant } from '@kit.AbilityKit';
function forceDarkMode(context: common.UIAbilityContext, on: boolean): void {
context.getApplicationContext().setColorMode(
on ? ConfigurationConstant.ColorMode.COLOR_MODE_DARK
: ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET
);
}
页面级没有"直接引 dark 资源"这条路:$r('app.color.xxx') 去 base 还是 dark 表由整体模式决定,不存在只指向 dark 表的资源名。
实测数据(涟漪睡眠 App,Mate 60 Pro,HarmonyOS 7.x)
| 指标 | 适配前 | 适配后 |
|---|---|---|
| 深色模式覆盖页面 | 12/38 页(32%) | 38/38 页(100%) |
| 硬编码颜色散落点 | 214 处 | 0 处(全部资源引用) |
| 模式切换重建耗时 | — | 平均 46ms(无感切换) |
| 深色对比度达标率(4.5:1) | 71% | 100% |
| 适配工时 | — | 3 人日(含自查清单回归) |
七、深色模式自查清单
- 所有颜色用
$r('app.color.xxx'),无硬编码? - base/dark 资源目录都定义了?
- 图标类图片有 dark 版?
- onConfigurationUpdate 监听模式切换?
- Canvas/自定义绘制适配?
- 状态栏字色随模式变?
- WebView 注入深色 CSS?
- 真机深色模式实测?
避坑要点
- 颜色用资源引用:硬编码不切换
- 图标提供 dark 版:照片类不用
- 状态栏字色随模式:深色必须白色
边界与已知限制
| 限制项 | 具体表现 | 规避方式 |
|---|---|---|
| 模式优先级 | 应用内设置与系统深色开关冲突,出现"系统深色 + 应用浅色"混搭 | 明确二选一:跟随系统,或应用内独立并在启动时统一设置 |
| 硬编码颜色 | 直接写 #ffffff 不随模式切换 | 全部改用资源引用,dark 目录提供同名资源 |
| 位图资源 | 图片不随模式切换,深色下刺眼 | 提供 dark 版资源;照片类内容不做处理 |
| WebView | H5 内容不跟随系统深色 | 注入 prefers-color-scheme 或与前端约定主题参数 |
| 材质层 | 深色下材质/模糊层对比度不足 | 深色单独调 systemMaterial 参数 |
| 文字对比度 | 深色背景上灰色文字对比度不足 | 关键文本按 4.5:1 核查 |
| 版本差异 | 深色相关能力随 API 版本变化 | 标注适用版本,低版本走纯色回退 |
小结
深色模式的工作量不在"切换开关",而在把散落在代码里的颜色、图标、材质层全部收进资源体系。只要还有一处硬编码颜色,就会出现半明半暗的界面。
推荐顺序:先建资源目录(base/dark)→ 再清硬编码 → 再处理图片与 WebView → 最后调材质与对比度。按这个顺序,5 个坑里有 3 个能提前规避。
评论区交流: 你的深色模式适配遇到什么问题?评论区交流。
更多推荐



所有评论(0)