【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(二十五):【深色模式】一键切换暗色主题——让 App 在深夜也温柔

【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(二十五):【深色模式】一键切换暗色主题——让 App 在深夜也温柔 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 的值。这极大减少了维护成本——你只需要定义“变化的部分”。LIGHTDARKbackgroundColor($r(app.color.bg_page))资源解析器当前 ColorMode?base/element/color.jsonbg_page: #FFF8F0dark/element/color.jsonbg_page: #121212图一解读资源解析器是 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}fromkit.AbilityKit;LocalisDark:booleanfalse;aboutToAppear():void{constctxthis.getUIContext().getHostContext()ascommon.UIAbilityContext;this.isDarkctx.config.colorModeConfigurationConstant.ColorMode.COLOR_MODE_DARK;}// UIRow(){Text(this.isDark? 深色模式:☀️ 浅色模式).fontSize(14).fontColor(#333)Blank()Toggle({type:ToggleType.Switch,isOn:this.isDark}).selectedColor(#FF6B35).onChange((on:boolean){this.isDarkon;constctxthis.getUIContext().getHostContext()ascommon.UIAbilityContext;constmodeon?ConfigurationConstant.ColorMode.COLOR_MODE_DARK:ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;ctx.getApplicationContext().setColorMode(mode);})}所有页面资源解析器ApplicationContextToggle 开关 用户所有页面资源解析器ApplicationContextToggle 开关 用户点击切换setColorMode(DARK)通知颜色模式变更$r(app.color.xxx)全部重新解析所有页面立即切换为深色图二解读setColorMode()是全局生效的——一旦调用所有使用了$r(app.color.xxx)的组件会立刻重新解析为 dark 目录的值。无需手动刷新任何页面无需遍历组件树。这是 HarmonyOS 声明式资源系统最大的优势状态变更 → 自动响应。Step 3渐进式迁移——只改两行演示当前代码中大量使用了硬编码颜色如#FF6B35、#333、#FFF。全部替换成本高且风险大。本篇采用「渐进式迁移」策略当前状态100% 硬编码颜色第25篇 14个颜色资源 Toggle 开关 2处演示替换后续篇章每次改文件顺带替换硬编码 → $r()目标状态核心路径100%资源化老旧代码逐步替换图三解读本篇文章只完成第一步——建立颜色基础设施并替换两处关键页面作为验证。后续每改一个文件就顺手把硬编码颜色替换为$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 后端如果你觉得这篇文章对您有所帮助麻烦您动动发财之手点赞 、收藏 ⭐ 和评论 。谢谢大家