【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(二十五):【深色模式】一键切换暗色主题——让 App 在深夜也温柔
摘要:本文介绍如何在HarmonyOS 6.1中为《灵犀厨房》App实现深色模式,解决夜间使用时白色背景刺眼的问题。通过HarmonyOS的资源限定符机制,只需在resources/dark/目录下定义13个颜色覆盖值(如背景色改为#121212、文字色调整为浅色系),系统即可自动匹配当前模式。同时利用语义化命名(如primary、bg_page)提升代码可维护性,并通过setColorMode(
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十五):【深色模式】一键切换暗色主题——让 App 在深夜也温柔
摘要:从第1篇到现在,我们写了上万行代码,但有一个细节一直被忽略——用户可能在深夜打开 App,白色背景的刺眼光线会让体验大打折扣。本篇将利用 HarmonyOS 6.1.0 的 资源限定符(Resource Qualifier) 和
setColorMode()API,为《灵犀厨房》构建深色模式——只需在resources/dark/目录添加一组颜色覆盖值,配合一个 Toggle 开关,App 就能在浅色和深色之间无感切换。
一、引言:被忽略的“夜间访客”
你是否有过这样的体验:深夜关灯准备刷一下手机,打开某个 App,瞬间被白色背景刺得眯起眼睛。《灵犀厨房》也存在同样的问题——从第1篇到现在,我们所有的代码都默认运行在浅色模式下,硬编码了 #FFF8F0、#FFFFFF、#333333 等颜色。这在白天没问题,但在深夜,这些颜色就是“光污染”。
深色模式不只是“把白底换成黑底”。它需要解决三个核心问题:
| 问题 | 浅色模式 | 深色模式需求 |
|---|---|---|
| 背景亮度 | 暖米色/纯白,柔和舒适 | 深灰/纯黑,减少眩光 |
| 文字对比度 | 深色文字在浅色背景上 | 浅色文字在深色背景上,但仍需保持足够对比度 |
| 主题色一致性 | #FF6B35 橙红色醒目 |
需要调亮为 #FF8C5A,否则在深色背景上辨识度下降 |
🎯 本篇目标:用最少的工作量(28个颜色定义 + 1个Toggle开关 + 2处演示替换),为《灵犀厨房》装上深色模式,并为后续的全面迁移建立基础设施。
二、核心原理:HarmonyOS 的资源限定符机制
HarmonyOS 的资源系统支持限定符目录。当你写下:
.backgroundColor($r('app.color.bg_page'))
ArkUI 会根据当前系统的颜色模式,自动去 resources/base/ 或 resources/dark/ 目录中查找同名资源。
resources/
├── base/element/color.json ← 默认值(浅色模式)
│ { "name": "bg_page", "value": "#FFF8F0" }
│
└── dark/element/color.json ← 深色模式覆盖值
{ "name": "bg_page", "value": "#121212" }
关键机制:同名覆盖。dark 目录中只放需要改变的颜色。不需要覆盖的颜色(如
text_white: #FFFFFF)不出现在 dark 目录中,系统自动沿用 base 的值。这极大减少了维护成本——你只需要定义“变化的部分”。
图一解读:资源解析器是 HarmonyOS 的资源查找引擎。它根据系统当前的 ColorMode 自动选择对应限定符目录下的资源。开发者只需在代码中写 $r('app.color.bg_page'),不需要写任何 if (isDark) 判断。这种声明式的资源管理是 HarmonyOS 相比传统 Android 开发的一大优势。
三、颜色语义化设计:从“颜色值”到“设计意图”
直接写 #FF6B35 有两个问题:一是无法被深色模式覆盖(硬编码颜色不受资源系统管理),二是含义模糊——三个月后你自己都忘了这个颜色是干嘛用的。我们需要定义一套语义化颜色名,让颜色名表达用途而非色值:
| 语义名 | 浅色值 | 暗色值 | 用途 |
|---|---|---|---|
primary |
#FF6B35 |
#FF8C5A |
主题色(按钮、标签、强调文字) |
primary_light |
#FFF0E6 |
#3D2010 |
主题色浅底(卡片内强调区域) |
bg_page |
#FFF8F0 |
#121212 |
页面背景 |
bg_card |
#FFFFFF |
#1E1E1E |
卡片背景 |
bg_smart_screen |
#1A1A2E |
#0D0D0D |
智慧屏深色背景 |
bg_secondary |
#F8F9FA |
#1A1A1A |
次级背景(列表、分组头) |
text_primary |
#333333 |
#E0E0E0 |
主文字 |
text_secondary |
#666666 |
#B0B0B0 |
次要文字 |
text_hint |
#999999 |
#808080 |
提示文字 |
text_white |
#FFFFFF |
#FFFFFF |
白字(深色底上使用,无需覆盖) |
divider |
#F0F0F0 |
#2A2A2A |
分割线 |
success |
#4CAF50 |
#66BB6A |
成功/在线 |
warn |
#FF9800 |
#FFB74D |
警告/待机 |
error |
#F44336 |
#EF5350 |
错误/离线 |
命名原则:
- 按用途命名(
bg_page),而非颜色值(orange)——方便后续换主题色 - 暗色值调亮主色(
#FF6B35→#FF8C5A),保证深色背景上的对比度 - 暗色值降低背景亮度(
#FFFFFF→#1E1E1E),减少眩光 text_white不覆盖——白色在深色背景上不变,缺省即自动沿用 base
四、实战步骤
Step 1:定义颜色资源文件
resources/base/element/color.json(新增 14 个颜色):
{
"color": [
{ "name": "primary", "value": "#FF6B35" },
{ "name": "primary_light", "value": "#FFF0E6" },
{ "name": "bg_page", "value": "#FFF8F0" },
{ "name": "bg_card", "value": "#FFFFFF" },
{ "name": "bg_smart_screen", "value": "#1A1A2E" },
{ "name": "bg_secondary", "value": "#F8F9FA" },
{ "name": "text_primary", "value": "#333333" },
{ "name": "text_secondary", "value": "#666666" },
{ "name": "text_hint", "value": "#999999" },
{ "name": "text_white", "value": "#FFFFFF" },
{ "name": "divider", "value": "#F0F0F0" },
{ "name": "success", "value": "#4CAF50" },
{ "name": "warn", "value": "#FF9800" },
{ "name": "error", "value": "#F44336" }
]
}
resources/dark/element/color.json(新增 13 个暗色覆盖,不含 text_white):
{
"color": [
{ "name": "primary", "value": "#FF8C5A" },
{ "name": "primary_light", "value": "#3D2010" },
{ "name": "bg_page", "value": "#121212" },
{ "name": "bg_card", "value": "#1E1E1E" },
{ "name": "bg_smart_screen", "value": "#0D0D0D" },
{ "name": "bg_secondary", "value": "#1A1A1A" },
{ "name": "text_primary", "value": "#E0E0E0" },
{ "name": "text_secondary", "value": "#B0B0B0" },
{ "name": "text_hint", "value": "#808080" },
{ "name": "divider", "value": "#2A2A2A" },
{ "name": "success", "value": "#66BB6A" },
{ "name": "warn", "value": "#FFB74D" },
{ "name": "error", "value": "#EF5350" }
]
}
注意:
text_white不出现在 dark 目录中——白色在深色背景上不需要变化,系统自动沿用 base 的值。
Step 2:添加 Toggle 切换开关
在 ProfilePage 中添加一个 Switch 开关:
import { common, ConfigurationConstant } from '@kit.AbilityKit';
@Local isDark: boolean = false;
aboutToAppear(): void {
const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
this.isDark = ctx.config.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK;
}
// UI
Row() {
Text(this.isDark ? '🌙 深色模式' : '☀️ 浅色模式').fontSize(14).fontColor('#333')
Blank()
Toggle({ type: ToggleType.Switch, isOn: this.isDark })
.selectedColor('#FF6B35')
.onChange((on: boolean) => {
this.isDark = on;
const ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
const mode = on ? ConfigurationConstant.ColorMode.COLOR_MODE_DARK
: ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
ctx.getApplicationContext().setColorMode(mode);
})
}
图二解读:setColorMode() 是全局生效的——一旦调用,所有使用了 $r('app.color.xxx') 的组件会立刻重新解析为 dark 目录的值。无需手动刷新任何页面,无需遍历组件树。这是 HarmonyOS 声明式资源系统最大的优势:状态变更 → 自动响应。
Step 3:渐进式迁移——只改两行演示
当前代码中大量使用了硬编码颜色(如 '#FF6B35'、'#333'、'#FFF')。全部替换成本高且风险大。本篇采用「渐进式迁移」策略:
图三解读:本篇文章只完成第一步——建立颜色基础设施,并替换两处关键页面作为验证。后续每改一个文件就顺手把硬编码颜色替换为 $r(),最终实现全量迁移。这种策略避免了“一次性大重构”的风险,也与开发节奏自然融合。
本篇只替换了两处作为演示:
// Index.ets —— 页面背景
.backgroundColor($r('app.color.bg_page')) // 原 '#FFF8F0'
// DeviceControlCard.ets —— 卡片背景
.backgroundColor($r('app.color.bg_card')) // 原 Color.White
验证效果:
- ✅ 浅色模式:Index 背景
#FFF8F0(暖米色),设备卡片#FFFFFF(纯白) - ✅ 深色模式:Index 背景
#121212(深灰),设备卡片#1E1E1E(浅黑) - ✅ 切换 Toggle 后,两处颜色立即变化,其余硬编码颜色保持不变
运行截图:

五、代码增删改清单
| 文件 | 新增/修改 | 说明 |
|---|---|---|
resources/base/element/color.json |
修改 | 新增 14 个语义化颜色值(浅色默认) |
resources/dark/element/color.json |
修改 | 新增 13 个暗色覆盖值 |
pages/ProfilePage.ets |
修改 | 新增 Toggle 深色模式开关(~25行) |
pages/Index.ets |
修改 | 页面背景改为 $r('app.color.bg_page')(2行) |
components/DeviceControlCard.ets |
修改 | 卡片背景改为 $r('app.color.bg_card')(1行) |
六、设计决策
| 决策 | 选择 | 理由 |
|---|---|---|
| 颜色命名方式 | 语义化(bg_page)而非色值(orange) |
换主题色时只需改资源文件,不碰代码 |
| 暗色主色 | #FF8C5A(比浅色 #FF6B35 更亮) |
深色背景上需要更高对比度的颜色才醒目 |
| 迁移策略 | 渐进式(只替换2处演示,其余逐步) | 全量替换 300+ 处硬编码风险大,后续每改一个文件顺带替换 |
text_white 是否覆盖 |
否(dark 目录不含此色) | 白色在深色背景上不需要变化,缺省即沿用 base |
| 切换入口位置 | ProfilePage(设置页) | 符合用户预期——"设置"中切换主题 |
aboutToAppear 读取当前模式 |
从 ctx.config.colorMode 读取 |
保证 Toggle 初始状态与系统当前模式一致 |
七、本阶段总结与下篇预告
本篇是《灵犀厨房》系列中“代码量最少、视觉冲击最大”的一篇:
- 14 个颜色资源:覆盖主题色、背景、文字、分割线、状态色五大类
- 1 个 Toggle 开关:浅色/深色一键切换,全局即时生效
- 渐进式迁移:不追求一次性全替换,2 处演示 + 后续逐文件迁移
- 零破坏:硬编码颜色继续工作,与新资源引用共存
深色模式不是“把白底换成黑底”——主色要调亮以保持对比度,分割线要降低到仅可见,背景层级要用不同深度的灰色区分。一个好的深色模式,用户切换后感觉“本该如此”。
下篇预告:第 26 篇《响应式布局:折叠屏与平板完美适配》。让《灵犀厨房》在手机、折叠屏、平板上都展现最佳布局,让同一个 App 在不同屏幕尺寸上都有量身定做的体验。我们下一篇见!
📚 本系列持续更新中:下一篇将实现跨设备响应式布局,让 App 自由穿梭于手机、平板、折叠屏。
🔗 专栏入口:[《HarmonyOS6.1全场景实战》合集]
📦 获取基线版本源码包:包括第1-15篇所有代码 + 架构文档 + Flask 后端
如果你觉得这篇文章对您有所帮助,麻烦您动动发财之手点赞 👍、收藏 ⭐ 和评论 💬。谢谢大家!!
更多推荐
所有评论(0)