摘要: 系统切深色模式,应用没跟着变;图片在深色模式下刺眼;自定义绘制颜色没适配——深色模式不是"换个背景色",是颜色/图片/材质/状态栏的全链路适配。本文用涟漪睡眠 App 适配深色模式为案例,给完整方案与 5 个踩坑。

适用版本: HarmonyOS NEXT 7.x / API 14+(2026 年稳定版)

开篇:深色模式一开,界面变得半明半暗

“系统切深色了,你的 App 怎么还是白的?”

2026 年 8 月,涟漪睡眠 App 联调。产品同学系统切深色模式,App 纹丝不动——刺眼。深色模式不是简单换个 backgroundColor,是颜色资源、图片资源、材质、状态栏的全链路适配。这篇把完整链路讲透。

图:深色模式适配效果对比 浅色 vs 深色 + base/dark 资源目录结构


一、深色模式全链路

深色模式适配全链路——从配置到自定义(HarmonyOS 7.x)|图 1


二、颜色资源适配

深色模式适配全链路——从配置到自定义(HarmonyOS 7.x)|图 2

资源目录结构

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:不是所有图片都需要深色版——照片类内容不用适配,图标/插画类需要。

四、监听模式切换

深色模式适配全链路——从配置到自定义(HarmonyOS 7.x)|图 3

// 统一在 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?
  • 真机深色模式实测?

避坑要点

  1. 颜色用资源引用:硬编码不切换
  2. 图标提供 dark 版:照片类不用
  3. 状态栏字色随模式:深色必须白色

边界与已知限制

限制项具体表现规避方式
模式优先级应用内设置与系统深色开关冲突,出现"系统深色 + 应用浅色"混搭明确二选一:跟随系统,或应用内独立并在启动时统一设置
硬编码颜色直接写 #ffffff 不随模式切换全部改用资源引用,dark 目录提供同名资源
位图资源图片不随模式切换,深色下刺眼提供 dark 版资源;照片类内容不做处理
WebViewH5 内容不跟随系统深色注入 prefers-color-scheme 或与前端约定主题参数
材质层深色下材质/模糊层对比度不足深色单独调 systemMaterial 参数
文字对比度深色背景上灰色文字对比度不足关键文本按 4.5:1 核查
版本差异深色相关能力随 API 版本变化标注适用版本,低版本走纯色回退

小结

深色模式的工作量不在"切换开关",而在把散落在代码里的颜色、图标、材质层全部收进资源体系。只要还有一处硬编码颜色,就会出现半明半暗的界面。

推荐顺序:先建资源目录(base/dark)→ 再清硬编码 → 再处理图片与 WebView → 最后调材质与对比度。按这个顺序,5 个坑里有 3 个能提前规避。

评论区交流: 你的深色模式适配遇到什么问题?评论区交流。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐