填坑:深色模式适配遗漏?—— 查找硬编码颜色与资源引用实战

📅 2026/7/15 20:38:49
填坑:深色模式适配遗漏?—— 查找硬编码颜色与资源引用实战
文章目录前言一、HarmonyOS 深色模式如何工作1.1 资源限定目录1.2 引用方式1.3 应用级 ColorMode二、坑硬编码 Hex 导致深色穿帮2.1 典型现象2.2 Demo 错误示例HardcodedProfileCard2.3 为什么「页面变了、卡片没变」2.4 常见误区三、解法语义化颜色资源 $r() 引用3.1 命名原则语义化而非色值化3.2 base/color.json浅色3.3 dark/color.json深色3.4 Demo 正确示例ResourceProfileCard3.5 涉及颜色的属性一览四、DevEco Studio查找硬编码颜色的技巧4.1 Find in Files最实用4.2 更宽泛的 Hex 搜索4.3 Structural Search结构化搜索4.4 Inspect Code代码检查4.5 命令行补充可选五、批量替换实战流程5.1 推荐顺序不要反过来5.2 批量替换示例5.3 映射表模板六、两种方案对照七、选型决策树八、哪些颜色可以保留 Hex九、系统性排查流程十、Demo 体验指南十一、生产环境 Checklist十二、总结附录快速参考卡前言应用上架前QA 在深色模式下测了一轮反馈来了「页面背景已经变黑了中间卡片还是一块大白字也看不清。」「设置页跟着系统变深色了个人中心怎么还是浅色」开发者的第一反应往往是「我开了深色模式啊系统也切了为什么这块没跟上」排查下来大量案例的根因非常朴素——代码里写死了 Hex 颜色.backgroundColor(#FFFFFF).fontColor(#111827)这些值在浅色模式下没问题一旦系统进入深色模式硬编码色不会自动变化就会出现「背景黑了、卡片还白」的穿帮或者「深底深字、对比度不足」的可读性灾难。HarmonyOS 的正规解法是把颜色抽到资源文件用$r(app.color.xxx)引用在base与dark目录各维护一份color.json框架按当前主题自动选值。本文结合项目 Demo从现象、资源机制、DevEco 查找硬编码技巧、批量替换流程到验收清单做一次完整拆解。读完后你应该能回答为什么深色模式下只有部分页面「穿帮」DevEco Studio 如何批量找出.ets里的 Hex 颜色如何把#FFFFFF系统性地替换为$r(app.color.dm_card_bg)一、HarmonyOS 深色模式如何工作1.1 资源限定目录HarmonyOS 资源按**限定词Qualifier**分目录存放entry/src/main/resources/ ├── base/element/color.json ← 默认浅色 └── dark/element/color.json ← 深色限定同名资源项dm_card_bg在两份文件中各有一个值资源名base浅色dark深色dm_page_bg#F3F4F6#111827dm_card_bg#FFFFFF#1F2937dm_text_primary#111827#F9FAFBdm_text_secondary#6B7280#9CA3AFdm_accent#4F46E5#818CF8dm_border#E5E7EB#3741511.2 引用方式Text(标题).fontColor($r(app.color.dm_text_primary))Column().backgroundColor($r(app.color.dm_card_bg))$r(app.color.xxx)返回ResourceColor运行时由系统根据当前 ColorMode选择base或dark目录下的值。1.3 应用级 ColorModeEntryAbility中常见配置this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET)模式含义COLOR_MODE_NOT_SET跟随系统推荐默认COLOR_MODE_LIGHT强制浅色COLOR_MODE_DARK强制深色Demo 提供三个按钮分别强制浅色、强制深色、恢复跟随系统方便实机对比this.context.getApplicationContext().setColorMode(dark?ConfigurationConstant.ColorMode.COLOR_MODE_DARK:ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT)二、坑硬编码 Hex 导致深色穿帮2.1 典型现象现象原因页面背景变黑卡片仍白卡片backgroundColor(#FFFFFF)写死深色背景下黑字看不清fontColor(#111827)未随主题变亮边框几乎看不见#E5E7EB浅灰边框在深色底上对比度不足部分页面正常、部分穿帮只有部分组件做了资源引用2.2 Demo 错误示例HardcodedProfileCardText(this.nickname).fontColor(#111827)// 写死深灰字Column().backgroundColor(#FFFFFF)// 写死白底.border({width:1,color:#E5E7EB})浅色模式下观感正常。切到深色后外层dm_page_bg变成#111827深灰蓝——已适配卡片仍是#FFFFFF白底——穿帮文字#111827深灰写在白底上还行但若外层容器也深色对比度崩溃。2.3 为什么「页面变了、卡片没变」系统 ColorMode DARK ↓ 使用 $r() 的组件 → 读 dark/color.json → 正常变深 ↓ 使用 #FFFFFF 的组件 → 绕过资源系统 → 永远白色硬编码色不经过资源限定机制这是穿帮的本质。2.4 常见误区误区事实「设置了深色模式就全局生效」只对$r()/ 主题资源生效硬编码不受影响「只在 color.json 里加 dark 就行」代码仍写 Hex 则不会读 color.json「用 Color.Black / Color.White 就行」系统枚举色同样不随主题变「深色模式后面再适配」历史 Hex 散落全项目后期改造成本指数上升三、解法语义化颜色资源 $r()引用3.1 命名原则语义化而非色值化❌ 不好gray_100、gray_900只描述浅色下的色值✅ 推荐dm_text_primary、dm_card_bg、dm_border描述用途同一语义色在base/dark各写不同 Hex组件代码只引用资源名不写色值。3.2 base/color.json浅色{name:dm_card_bg,value:#FFFFFF},{name:dm_text_primary,value:#111827}3.3 dark/color.json深色{name:dm_card_bg,value:#1F2937},{name:dm_text_primary,value:#F9FAFB}两份文件的name必须一一对应缺了 dark 项会回退到 base 值造成「深色下仍显示浅色」。3.4 Demo 正确示例ResourceProfileCardText(this.nickname).fontColor($r(app.color.dm_text_primary))Column().backgroundColor($r(app.color.dm_card_bg)).border({width:1,color:$r(app.color.dm_border)})切深色后卡片背景自动变为#1F2937文字变为#F9FAFB无需改一行组件代码。3.5 涉及颜色的属性一览以下属性都应优先用资源引用属性示例fontColor.fontColor($r(app.color.dm_text_primary))backgroundColor.backgroundColor($r(app.color.dm_card_bg))border的color.border({ width: 1, color: $r(app.color.dm_border) })fillColor/strokeColor图形、Canvas 场景shadow的color阴影色也需适配四、DevEco Studio查找硬编码颜色的技巧4.1 Find in Files最实用路径Edit → Find → Find in FilesmacOSCmd Shift F勾选Regex正则表达式输入\.(fontColor|backgroundColor|borderColor|fillColor|strokeColor)\([^)]*[]#[0-9A-Fa-f]{3,8}[]作用匹配.ets文件中颜色相关属性里直接写 Hex 字面量的代码行。搜索范围建议entry/src/main/ets/业务代码排除oh_modules/、build/目录Demo 模拟扫描命中 5 处ProfileCard.ets属性Hex建议资源backgroundColor#FFFFFFapp.color.dm_card_bgfontColor#111827app.color.dm_text_primaryfontColor#6B7280app.color.dm_text_secondaryborder.color#E5E7EBapp.color.dm_borderbackgroundColor#4F46E5app.color.dm_accent4.2 更宽泛的 Hex 搜索若需找出所有Hex包括变量赋值、常量定义#[0-9A-Fa-f]{3,8}\b范围大、误报多适合第一轮摸底再结合上下文人工过滤。4.3 Structural Search结构化搜索DevEco 支持按代码结构搜索。示例模式$backgroundColor$ in classes Component where backgroundColor matches /#[0-9A-Fa-f]{3,8}/适合在大型项目中定位Component内 backgroundColor 硬编码的类比纯文本搜索更精准。4.4 Inspect Code代码检查Code → Inspect Code可运行 IDE 内置检查部分版本会提示资源使用不规范或硬编码建议。适合 CI 前做一次全项目扫描与 Find in Files 互补。4.5 命令行补充可选项目外可用rg快速统计rg#[0-9A-Fa-f]{3,8}entry/src/main/ets--glob*.ets适合脚本化统计「还有多少 Hex 未清理」。五、批量替换实战流程5.1 推荐顺序不要反过来① 梳理色板在 color.json 建语义色 ↓ ② Find in Files 列出所有硬编码命中 ↓ ③ 建立 Hex → 资源名 映射表 ↓ ④ Replace in Files 批量改为 $r() ↓ ⑤ 补全 dark/color.json 对应项 ↓ ⑥ 实机切换浅色/深色验收先建资源再替换。否则代码里写了$r(app.color.xxx)但资源不存在编译直接报错。5.2 批量替换示例路径Edit → Find → Replace in Files查找替换为.backgroundColor(#FFFFFF).backgroundColor($r(app.color.dm_card_bg)).fontColor(#111827).fontColor($r(app.color.dm_text_primary)).fontColor(#6B7280).fontColor($r(app.color.dm_text_secondary))color: #E5E7EBcolor: $r(app.color.dm_border)⚠️注意同一 Hex 在不同语义场景可能对应不同资源名如#FFFFFF可能是卡片底也可能是按钮字色dm_on_accent不能无脑全局替换需结合上下文。替换后逐文件 Review避免误改注释、日志字符串中的 Hex。5.3 映射表模板Hex出现场景资源名basedark#FFFFFF卡片背景dm_card_bg#FFFFFF#1F2937#111827主标题dm_text_primary#111827#F9FAFB#6B7280副标题dm_text_secondary#6B7280#9CA3AF#4F46E5品牌强调dm_accent#4F46E5#818CF8Demo 中formatReplaceCommand()即为映射输出的格式化backgroundColor: #FFFFFF → $r(app.color.dm_card_bg)六、两种方案对照维度❌ 硬编码 Hex✅$r(app.color.xxx)浅色表现正常正常深色表现穿帮 / 不可读自动切换维护成本改色需搜全项目只改 color.json设计改版高风险低风险多主题扩展几乎不可能可加更多限定目录代码可读性#6B7280无语义dm_text_secondary自解释Demo 并排对比左侧HardcodedProfileCard切深色后仍白底右侧ResourceProfileCard随主题同步变化。七、选型决策树组件颜色怎么写 │ ├─ 随浅色/深色主题变化 │ ├─ 是 → $r(app.color.语义名) │ │ base dark 各一份 color.json │ └─ 否 → 可用固定 Hex如品牌渐变中的精确色 │ ├─ 已有大量硬编码 │ └─ Find in Files 正则扫描 → 建资源 → 批量 Replace │ ├─ 图片/图标颜色 │ └─ 深色模式可能需要换图media 也支持 dark 目录 │ └─ 需要 App 内手动切主题 └─ setColorMode(LIGHT / DARK / NOT_SET)八、哪些颜色可以保留 Hex并非所有 Hex 都必须资源化。以下情况可保留硬编码但需深色模式单独验证场景说明品牌固定色 Logo 区公司 VI 要求精确色值半透明遮罩rgba(0,0,0,0.5)等深浅通用功能色成功/警告/错误建议仍资源化便于统一调对比度调试/日志 UIDemo 中扫描报告的#EF4444红字可保留原则用户可见的 UI 色默认走资源仅当语义不随主题变时才保留 Hex。九、系统性排查流程复现系统设置切深色或 Demo 点「深色」按钮强制COLOR_MODE_DARK定位穿帮组件白块、看不清的文字、消失的边框查源码该组件fontColor/backgroundColor是$r()还是#...全项目扫描Find in Files 正则统计未治理 Hit 数补资源base/dark成对添加缺失的name批量替换按映射表 Replace in Files回归浅色 / 深色 / 跟随系统三种模式各过一遍边缘页弹窗、Toast、自定义 Dialog 容易遗漏单独检查。十、Demo 体验指南路径entry/src/main/ets/darkmode/文件职责DarkModeTypes.ets方案枚举、扫描命中、替换预览、正则常量ColorHardcodeScanner.ets扫描汇总、替换命令格式化HardcodedProfileCard.ets❌ 硬编码卡片ResourceProfileCard.ets✅ 资源引用卡片DarkModePitfallDemo.ets主页面、主题切换、扫描报告建议操作打开 App保持「并排对比」点深色→ 左卡仍白底穿帮右卡同步变深点浅色→ 两卡均正常差异缩小展开DevEco 扫描报告对照正则与替换建议阅读批量替换预览理解 Replace in Files 写法。十一、生产环境 Checklistbase/element/color.json建立语义化颜色 tokendark/element/color.json同名项全部补齐业务.ets中fontColor/backgroundColor无硬编码 Hexborder/fillColor/strokeColor同步资源化Find in Files 正则扫描结果为零或仅剩白名单EntryAbility使用COLOR_MODE_NOT_SET跟随系统提供 App 内主题切换时调用setColorMode图片/图标检查resources/dark/media是否需要深色版弹窗、表单、Web 容器等二级页面单独验收对比度满足可读性深底浅字、浅底深字十二、总结深色模式适配遗漏多半不是「没开深色」而是颜色写死在代码里绕过了资源系统。核心结论说明穿帮根因backgroundColor(#FFFFFF)等硬编码不随主题变正确做法$r(app.color.xxx) base/dark 双份 color.json命名语义化dm_text_primary而非gray_900查找DevEco Find in Files 正则批量定位 Hex替换先建资源再 Replace in Files 改$r()验收setColorMode强制浅/深 跟随系统实机对比记住一句话颜色是资源不是常量主题切换靠$r()不靠运气。附录快速参考卡// ❌ 硬编码深色模式穿帮Column().backgroundColor(#FFFFFF).border({width:1,color:#E5E7EB})// ✅ 资源引用自动适配Column().backgroundColor($r(app.color.dm_card_bg)).border({width:1,color:$r(app.color.dm_border)})// base/element/color.json{name:dm_card_bg,value:#FFFFFF}// dark/element/color.json{name:dm_card_bg,value:#1F2937}// Find in Files 正则 \.(fontColor|backgroundColor|borderColor|fillColor|strokeColor)\([^)]*[]#[0-9A-Fa-f]{3,8}[]// 强制切换主题Democontext.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_DARK)