📌 前言

在开发鸿蒙应用时,遇到了一个常见问题:应用无法正常适配深色模式,不符合鸿蒙应用UX设计规范。具体表现为:

  • 在应用内打开深色模式开关后,界面颜色不变
  • 在设备切换为深色模式后,应用未正常跟随系统

本文记录了完整的解决过程,希望能帮助遇到同样问题的开发者。

开发环境:

  • DevEco Studio 5.0.4
  • HarmonyOS API 16
  • 15个页面需要适配

🔍 问题分析

核心问题

应用中大量使用了硬编码颜色,例如:

// ❌ 硬编码颜色
.fontColor('#000000')
.backgroundColor('#FFFFFF')
.fillColor('#1698E4')

这些硬编码颜色在深色模式下不会自动改变,导致:

  • 白色背景在深色模式下依然是白色(刺眼)
  • 黑色文字在深色背景上看不清
  • 品牌色在深色模式下对比度不足

错误尝试

最初尝试在 module.json5 中添加 configChanges: ["colorMode"],但遇到编译错误:

Value should be one of: "priority", "name", "srcEntry", "launchType", "description", "icon", "label", "permissions", "metadata", "visible", "exported", "skills", "backgroundModes", "continueType", "startupVisible"

原因: API 16 不需要手动配置 configChanges,系统会自动适配。


✅ 解决方案

第一步:创建颜色资源文件

1.1 创建浅色模式颜色资源

文件路径: entry/src/main/resources/base/element/color.json

{
  "color": [
    {
      "name": "page_background",
      "value": "#FFFFFF"
    },
    {
      "name": "page_background_secondary",
      "value": "#F5F5F5"
    },
    {
      "name": "text_primary",
      "value": "#000000"
    },
    {
      "name": "text_secondary",
      "value": "#666666"
    },
    {
      "name": "text_tertiary",
      "value": "#999999"
    },
    {
      "name": "brand_color",
      "value": "#007DFF"
    },
    {
      "name": "text_on_brand",
      "value": "#FFFFFF"
    },
    {
      "name": "icon_tertiary",
      "value": "#666666"
    },
    {
      "name": "divider_color",
      "value": "#E0E0E0"
    },
    {
      "name": "start_window_background",
      "value": "#FFFFFF"
    }
  ]
}
1.2 创建深色模式颜色资源

文件路径: entry/src/main/resources/dark/element/color.json

{
  "color": [
    {
      "name": "page_background",
      "value": "#000000"
    },
    {
      "name": "page_background_secondary",
      "value": "#1C1C1E"
    },
    {
      "name": "text_primary",
      "value": "#FFFFFF"
    },
    {
      "name": "text_secondary",
      "value": "#8E8E93"
    },
    {
      "name": "text_tertiary",
      "value": "#636366"
    },
    {
      "name": "brand_color",
      "value": "#0A84FF"
    },
    {
      "name": "text_on_brand",
      "value": "#FFFFFF"
    },
    {
      "name": "icon_tertiary",
      "value": "#8E8E93"
    },
    {
      "name": "divider_color",
      "value": "#38383A"
    },
    {
      "name": "start_window_background",
      "value": "#000000"
    }
  ]
}

说明:

  • 深色模式下,浅色变深,深色变浅
  • 品牌色在深色模式下略微调亮,保持识别度
  • 必须包含 start_window_background,否则编译报错

第二步:替换硬编码颜色

2.1 颜色替换对照表
硬编码颜色 替换为资源引用 说明
#FFFFFF, Color.White $r('app.color.page_background') 白色背景
#F5F5F5, #EEEEEE $r('app.color.page_background_secondary') 浅灰背景
#000000, Color.Black $r('app.color.text_primary') 黑色文字
#666666 $r('app.color.text_secondary') 灰色文字
#999999, Color.Gray $r('app.color.text_tertiary') 浅灰文字
#007DFF, #0A59F7 $r('app.color.brand_color') 品牌蓝色
Color.White(按钮文字) $r('app.color.text_on_brand') 按钮白字
2.2 替换示例

背景色:

// ❌ 修改前
Column() {
  Text("内容")
}
.backgroundColor('#FFFFFF')

// ✅ 修改后
Column() {
  Text("内容")
}
.backgroundColor($r('app.color.page_background'))

文字颜色:

// ❌ 修改前
Text("标题")
  .fontColor('#000000')

Text("副标题")
  .fontColor('#666666')

// ✅ 修改后
Text("标题")
  .fontColor($r('app.color.text_primary'))

Text("副标题")
  .fontColor($r('app.color.text_secondary'))

品牌色/强调色:

// ❌ 修改前
Button('确认')
  .backgroundColor('#007DFF')
  .fontColor(Color.White)

// ✅ 修改后
Button('确认')
  .backgroundColor($r('app.color.brand_color'))
  .fontColor($r('app.color.text_on_brand'))

条件表达式:

// ❌ 修改前
Image($r('app.media.icon'))
  .fillColor(this.selected ? '#1698E4' : '#666666')

Text(item.text)
  .fontColor(this.selected ? '#1698E4' : '#666666')

// ✅ 修改后
Image($r('app.media.icon'))
  .fillColor(this.selected ? $r('app.color.brand_color') : $r('app.color.icon_tertiary'))

Text(item.text)
  .fontColor(this.selected ? $r('app.color.brand_color') : $r('app.color.text_secondary'))

第三步:修改 EntryAbility.ets

关键问题: 如果代码中强制设置了颜色模式,深色模式将无法生效。

3.1 错误代码示例
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  // ❌ 强制浅色模式,导致深色模式失效
  this.context.getApplicationContext()
    .setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT);
}
3.2 正确做法

文件路径: entry/src/main/ets/entryability/EntryAbility.ets

import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

const DOMAIN = 0x0000;

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // ✅ 设置为跟随系统,自动适配深色模式
    this.context.getApplicationContext()
      .setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
  }

  // 可选:监听深色模式切换事件
  onConfigurationUpdate(newConfig: Configuration): void {
    hilog.info(DOMAIN, 'testTag', 'ColorMode changed: %{public}d', newConfig.colorMode);
    // UI 会自动刷新,无需手动处理
  }
  
  // ... 其他方法
}

ColorMode 说明:

  • COLOR_MODE_NOT_SET - 跟随系统(推荐)
  • COLOR_MODE_LIGHT - 强制浅色
  • COLOR_MODE_DARK - 强制深色

第四步:处理特殊颜色

某些颜色有特殊语义(如成功绿色、错误红色),需要单独定义:

在 base/element/color.json 中添加:

{
  "name": "status_success",
  "value": "#4CAF50"
},
{
  "name": "status_error",
  "value": "#E4080A"
},
{
  "name": "status_warning",
  "value": "#FFA000"
}

在 dark/element/color.json 中添加:

{
  "name": "status_success",
  "value": "#66BB6A"
},
{
  "name": "status_error",
  "value": "#EF5350"
},
{
  "name": "status_warning",
  "value": "#FFB84D"
}

使用方式:

Text(errorMessage)
  .fontColor($r('app.color.status_error'))

Button('成功')
  .backgroundColor($r('app.color.status_success'))

⚠️ 常见问题

1. 编译错误:ref '$color:xxx' don't be defined

原因: 颜色资源文件中缺少某个颜色定义。

解决: 检查 base/element/color.jsondark/element/color.json,确保所有用到的颜色都已定义。

2. 遮罩层在深色模式下显示异常

说明: 弹窗遮罩层通常使用半透明黑色,不需要适配。

// ✅ 遮罩层保持黑色
Column()
  .backgroundColor('#000000')
  .opacity(0.5)

3. 切换深色模式后应用没有变化

检查清单:

  • [ ] 颜色资源文件是否正确创建
  • [ ] 代码中是否替换了所有硬编码颜色
  • [ ] EntryAbility 中是否设置了 COLOR_MODE_NOT_SET
  • [ ] 是否重新编译(Clean Build)
  • [ ] 是否完全关闭应用重新打开

4. module.json5 需要配置吗?

答: API 16 不需要配置 configChanges

❌ 错误做法:

{
  "abilities": [
    {
      "configChanges": ["colorMode"]  // API 16 不支持
    }
  ]
}

✅ 正确做法:保持默认配置,系统会自动适配。


🎯 修改统计

以本项目为例,完成深色模式适配:

  • 修改页面数量: 15个
  • 替换硬编码颜色: 157+ 处
  • 创建颜色资源: 10+ 种基础色 + 5种语义色
  • 修改时间: 约2小时

📊 修改前后对比

浅色模式

元素 修改前 修改后
主背景 #FFFFFF $r('app.color.page_background')
主文字 #000000 $r('app.color.text_primary')
品牌色 #007DFF $r('app.color.brand_color')

深色模式(自动切换)

元素 颜色值 效果
主背景 #000000 黑色背景
主文字 #FFFFFF 白色文字
品牌色 #0A84FF 亮蓝色

🔍 验证方法

检查是否还有硬编码颜色

在项目根目录执行:

grep -r "#[0-9A-Fa-f]\{6\}" entry/src/main/ets/pages/

如果没有输出,说明所有硬编码颜色都已替换。

测试深色模式切换

  1. 编译运行应用
  2. 进入系统设置 → 显示与亮度 → 开启深色模式
  3. 返回应用,界面应自动切换为深色
  4. 关闭深色模式,界面应自动切换回浅色

✅ 最终效果

完成适配后:

  • 自动跟随系统:系统切换深色/浅色模式,应用自动跟随
  • 无需手动刷新:颜色实时更新,用户体验流畅
  • 符合设计规范:通过鸿蒙应用UX设计规范审核
  • 减少维护成本:使用系统资源,后续维护简单

💡 最佳实践建议

1. 颜色命名规范

  • 使用语义化命名:page_backgroundtext_primary
  • 避免颜色值命名:~~color_white~~、~~color_black~~

2. 颜色层级

主背景 (page_background)
  └─ 次级背景 (page_background_secondary)
      └─ 卡片背景

主文字 (text_primary)
  └─ 次要文字 (text_secondary)
      └─ 三级文字 (text_tertiary)

3. 品牌色使用

  • 交互元素:按钮、链接、选中状态
  • 强调信息:重要提示、数据高亮
  • 保持一致性:全局统一使用 brand_color

4. 对比度要求

  • 主文字对比度 > 4.5:1
  • 大号文字对比度 > 3:1
  • 确保深色模式下文字清晰可读

📚 参考资料


🎉 总结

深色模式适配的核心思路:

  1. 创建颜色资源:base 和 dark 两套配置
  2. 替换硬编码:所有颜色使用 $r('app.color.xxx')
  3. 跟随系统:EntryAbility 设置 COLOR_MODE_NOT_SET
  4. 测试验证:多场景测试,确保显示正常

虽然修改量较大(本项目157+处),但流程清晰,按照规范逐个替换即可。完成后不仅通过审核,还能提升用户体验,特别是夜间使用场景。

希望这篇文章能帮助到遇到同样问题的开发者!如有疑问,欢迎交流讨论。


作者: 全球通史
日期: 2025年11月
标签: HarmonyOS, 深色模式, DevEco Studio, API 16, 鸿蒙开发

Logo

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

更多推荐