一个项目拥有Theme.ets,不等于它已经拥有完整主题系统。真正可维护的主题,需要回答四个问题:颜色表达的是业务语义还是某个十六进制值;浅色与深色是否使用同一组资源名称;系统状态栏、普通 ArkUI 组件和 Canvas 是否能同步变化;选中、按下、禁用和错误状态是否都经过对比度验证。
本文基于化学实验室 1.0.5的真实 HarmonyOS ArkTS 工程展开。源码中已经有AppColors与AppFonts,Constants.ets也集中保存了页面间距、卡片间距和圆角。然而,EntryAbility.ets在创建阶段强制设置COLOR_MODE_LIGHT;base/element/color.json定义了多种颜色,dark/element/color.json却只有启动窗口背景;部分页面、弹窗、Canvas 和系统栏仍直接写入#F0F0F0、#FFFFFF、#CCCCCC等色值。
这不是简单的“把白色改成黑色”问题。直接替换可能造成正文变暗、图表坐标线消失、未选中标签与卡片背景混在一起,甚至状态栏图标和背景使用同一明暗方向。正确做法是先建立语义令牌契约,再让资源限定词自动提供浅色和深色值。
主题系统的核心不是集中保存十六进制颜色,而是让页面、组件、系统栏和绘制层共同消费稳定的语义资源。
CHEM-LAB-ONE12-SEMANTIC-THEME-TOKENS-20260730
本文将完成以下工作:
- 核对工程当前颜色模式、主题常量和资源目录。
- 区分颜色常量、语义令牌与资源限定词的职责。
- 盘点
AppColors已覆盖的区域和仍然散落的硬编码颜色。 - 设计 base 与 dark 使用相同名称的颜色资源。
- 让 ArkUI 页面从字符串色值迁移到
$r('app.color.xxx')。 - 统一间距、圆角、字号与交互状态。
- 处理状态栏、导航栏、弹窗、阴影和 Canvas 的主题同步。
- 给出分阶段迁移、构建检查、对比度计算与双模式回归表。
一、先确认工程当前不是“跟随系统”
EntryAbility.ets的onCreate()中存在以下真实代码:
this.context.getApplicationContext().setColorMode( ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT )这行代码把应用锁在浅色模式。它解释了为什么当前页面即使在系统开启深色模式时仍保持浅色,也解释了为什么dark/element/color.json长期没有被补齐:运行时通常不会进入深色资源匹配。
因此本文的第一条结论是:在迁移完成前,锁定浅色可以避免半成品主题进入用户界面;在 base、dark、系统栏和绘制层全部验证后,才删除这条强制浅色设置,让应用重新跟随系统。不能先解除锁定,再边运行边寻找白底白字问题。
二、Theme.ets 已经提供了一个起点
当前Theme.ets把颜色分成品牌、状态、背景、文字、Tab 和实验分类几组:
export class AppColors { static readonly PRIMARY: string = '#0052D9' static readonly PRIMARY_BG: string = '#E8F1FF' static readonly PAGE_BG: string = '#F5F7FA' static readonly CARD_BG: string = '#FFFFFF' static readonly DIVIDER: string = '#EEEEEE' static readonly TEXT_PRIMARY: string = '#1A1A1A' static readonly TEXT_SECONDARY: string = '#475467' static readonly TEXT_HINT: string = '#6B7280' }HomePage.ets、LearningPage.ets、LabPage.ets、MinePage.ets、ExperimentCard.ets、DataTable.ets等文件都在消费这些值。集中常量已经减少了主页面之间的随机色差,也使品牌蓝和正文色可以统一修改。
但static readonly string只是在代码层共享字符串。它不会根据资源限定词自动变化,也不能让系统在颜色模式切换时重新匹配资源。这个结构适合迁移前盘点,不适合作为最终的双模式资源层。
三、资源文件与代码常量目前存在两套真源
工程的base/element/color.json中已经存在primary_blue、page_background、card_background、text_primary等资源,但它们与Theme.ets的值并不完全一致。例如资源文件中的主色是#2979FF,Theme.ets中的主色是#0052D9;资源里的辅助绿色是#00C853,代码常量中则是#087443。
当同一个语义有两套值时,会出现三个后果:
- 使用
$r()的启动页和使用AppColors的业务页看起来不是同一套品牌。 - 修改
Theme.ets无法覆盖资源引用,修改color.json也无法覆盖字符串常量。 - 深色资源只能替换
$r()引用,无法替换编译进代码的字符串。
迁移时必须指定唯一真源。对需要根据配置自动变化的颜色、尺寸和媒体,优先使用资源系统;Theme.ets可以保留类型、状态映射和非视觉常量,但不再保存两套重复色值。
四、dark 目录只有一个资源意味着什么
当前dark/element/color.json只有:
{ "color": [ { "name": "start_window_background", "value": "#FFFFFF" } ] }它不仅没有覆盖页面、卡片和文字颜色,连启动背景仍是白色。若解除浅色锁定,系统能匹配到的深色资源极少,大多数业务组件继续使用浅色色值。这会形成“系统进入深色、应用局部仍为浅色”的割裂状态。
HarmonyOS 资源系统支持颜色模式限定词。base提供默认值,dark目录提供同名覆盖值;组件仍引用同一个资源名称,运行时根据当前配置匹配。重点是同名、同语义、不同取值,而不是给深色版本再创建一套dark_text_primary名称。
五、先设计语义名称,再选择色值
不建议直接把现有变量机械转换为blue_500、gray_900。页面真正关心的是用途:这是页面背景、卡片背景、正文、弱提示、边界、主操作还是危险状态。推荐最小语义集合如下:
| 语义资源 | 浅色职责 | 深色职责 |
|---|---|---|
surface_page | 页面底色 | 深色页面底色 |
surface_card | 卡片与弹窗 | 抬高的深色表面 |
surface_muted | 输入框、未选标签 | 次级深色表面 |
content_primary | 标题与正文 | 高强调文字 |
content_secondary | 说明文字 | 中强调文字 |
content_hint | 占位与弱提示 | 低强调文字 |
border_subtle | 分割线与边框 | 可见但克制的边界 |
action_primary | 主按钮与选中态 | 深色环境下主操作 |
action_on_primary | 主按钮上的文字 | 主操作上的文字 |
status_success | 成功与安全 | 成功与安全 |
status_warning | 警告与注意 | 警告与注意 |
status_danger | 错误与危险 | 错误与危险 |
命名不带具体颜色,未来品牌蓝调整、对比度修复或高对比主题增加时,业务组件都无需改名。
六、建立 base 与 dark 的同名资源
浅色资源可以先从当前Theme.ets的稳定值迁移:
{ "color": [ { "name": "surface_page", "value": "#F5F7FA" }, { "name": "surface_card", "value": "#FFFFFF" }, { "name": "surface_muted", "value": "#F0F0F0" }, { "name": "content_primary", "value": "#1A1A1A" }, { "name": "content_secondary", "value": "#475467" }, { "name": "content_hint", "value": "#6B7280" }, { "name": "border_subtle", "value": "#E5E7EB" }, { "name": "action_primary", "value": "#0052D9" }, { "name": "action_on_primary", "value": "#FFFFFF" }, { "name": "status_success", "value": "#087443" }, { "name": "status_warning", "value": "#B45309" }, { "name": "status_danger", "value": "#B42318" } ] }dark/element/color.json使用完全相同的名称,但选择经过对比度验证的深色值:
{ "color": [ { "name": "surface_page", "value": "#111418" }, { "name": "surface_card", "value": "#1B2026" }, { "name": "surface_muted", "value": "#252B33" }, { "name": "content_primary", "value": "#F5F7FA" }, { "name": "content_secondary", "value": "#C4CBD4" }, { "name": "content_hint", "value": "#9AA4B2" }, { "name": "border_subtle", "value": "#353D47" }, { "name": "action_primary", "value": "#6EA8FF" }, { "name": "action_on_primary", "value": "#0B1F3A" }, { "name": "status_success", "value": "#5BD6A2" }, { "name": "status_warning", "value": "#F4B95E" }, { "name": "status_danger", "value": "#FF8C82" } ] }这些值是迁移起点,不是所有项目通用的固定答案。最终仍要用真实字体、字号、透明度和背景组合计算对比度,并在设备上观察视觉层级。
七、组件只引用语义资源
原代码:
Text('实验室') .fontColor(AppColors.TEXT_PRIMARY) Column() .backgroundColor(AppColors.CARD_BG)迁移后:
Text('实验室') .fontColor($r('app.color.content_primary')) Column() .backgroundColor($r('app.color.surface_card'))组件不需要读取isDark,也不需要写三元表达式。资源匹配层负责提供颜色,组件只表达“这里是主文字”“这里是卡片表面”。这样系统主题变化时,页面结构不会出现大量条件分支。
八、Theme.ets 应从颜色仓库变成主题契约
迁移完成后,Theme.ets不必被删除。它可以保存语义角色、组件状态映射或纯类型,避免业务页面拼接资源名:
export interface ComponentTone { background: ResourceColor foreground: ResourceColor border: ResourceColor } export enum VisualState { Normal, Selected, Disabled, Danger }如果团队希望集中导出资源引用,也要确保导出的仍是ResourceColor,不是十六进制字符串。最重要的边界是:主题契约描述角色,资源文件提供配置相关的值,业务组件消费角色。
九、间距和圆角也属于视觉令牌
Constants.ets已经集中保存:
static readonly PAGE_PADDING: number = 16 static readonly CARD_GAP: number = 12 static readonly CARD_RADIUS: number = 16 static readonly SMALL_RADIUS: number = 12这是正确方向,但命名仍偏“某个数值放在哪里”。随着平板、2in1 和自由窗口加入,同一语义可能需要不同密度。例如紧凑窗口页面边距 16vp,宽屏内容区边距 24vp;普通卡片圆角 12vp,弹窗圆角 20vp。
可以把数值升级为尺度:space_xs、space_s、space_m、space_l,再由组件规则决定使用哪一档。若尺寸需要随设备或配置匹配,也可以放入 float 资源,通过$r('app.float.space_m')引用。这样页面不会同时出现 15、16、18 三个几乎相同的边距。
十、字号令牌不能替代动态字体测试
当前AppFonts提供 20、16、14、12 四档字号和三档字重。集中字号能维持层级,但不能证明固定高度容器支持系统字体放大。HomePage的 Banner、列表行、按钮和 Tab 都需要在较大字体下验证。
建议用语义名表达用途:title_page、title_section、body、caption,并遵循以下规则:
- 普通正文优先允许换行,而不是缩小到不可读。
- 按钮文字设置合理
maxLines,容器高度保留弹性。 - 卡片标题与说明使用不同语义字号,不用单一字号加粗模拟所有层级。
- Canvas 内文字也要从同一套字号与颜色规则读取,不能独立写死。
十一、硬编码颜色需要分类迁移
源码扫描可看到多类硬编码:
CategoryTabs.ets #F0F0F0 SearchBar.ets #F0F0F0 DataTable.ets #F5F5F5 / #CCCCCC ChartView.ets #FFFFFF / #EEEEEE EntryAbility.ets #F5F7FA / #1A1A1A / #FFFFFF LearningPage.ets #1A237E / #3949AB不能用一次全局替换处理,因为它们职责不同:#F0F0F0可能是未选标签、搜索框或数据表头;#FFFFFF可能是卡片、按钮文字或 Canvas 背景;渐变色可能是品牌 Banner,而不是普通表面。
正确顺序是先给每处硬编码标注语义,再替换为对应资源。无法判断用途的颜色应保留到组件审查阶段,不能因为颜色相同就认定语义相同。
十二、交互状态需要独立令牌
当前分类标签通常只区分 selected 和 normal:
.fontColor(selected ? AppColors.TEXT_WHITE : AppColors.TEXT_SECONDARY) .backgroundColor(selected ? AppColors.PRIMARY : '#F0F0F0')真实交互还包括 pressed、focused、disabled 与 error。建议为组件建立状态表:
| 状态 | 背景 | 文字 | 边框 | 验收重点 |
|---|---|---|---|---|
| normal | surface_muted | content_secondary | 透明 | 标签可辨识 |
| selected | action_primary | action_on_primary | 主色 | 选中明显 |
| pressed | 主色压暗或叠层 | 高对比文字 | 主色 | 有即时反馈 |
| focused | 原背景 | 原文字 | 焦点色 | 键盘可定位 |
| disabled | 禁用表面 | 禁用文字 | 弱边界 | 不像可点击 |
| error | 危险弱背景 | 危险文字 | 危险色 | 不只依赖红色 |
状态不能只靠色相区分。错误状态还应有文字或图标,焦点状态要有轮廓,禁用状态要同时停止点击行为。
十三、系统栏必须和页面主题同步
EntryAbility.ets当前把系统栏固定为浅色:
mainWindow.setWindowSystemBarProperties({ statusBarColor: '#F5F7FA', statusBarContentColor: '#1A1A1A', isStatusBarLightIcon: false, navigationBarColor: '#FFFFFF', navigationBarContentColor: '#1A1A1A', isNavigationBarLightIcon: false })解除浅色锁定后,如果这里不变,深色页面顶部仍会出现浅色条。应把系统栏配置封装成一个窗口级方法,在窗口创建和颜色配置变化时调用。方法输入是当前主题画像,输出是背景色、内容色以及图标明暗方向。
interface SystemBarPalette { statusBackground: string statusContent: string navigationBackground: string navigationContent: string useLightIcon: boolean }系统栏属于窗口层,不应散落在每个业务页面。验证时要同时观察普通页面、弹窗、返回后的页面和冷启动窗口,避免只修主页面截图。
十四、Canvas 不会自动理解组件主题
ChartView.ets使用 Canvas 绘制背景、网格、文字和折线,其中既有AppColors,也有#FFFFFF、#CCCCCC、#EEEEEE。ArkUI 的Text可以直接消费资源颜色,但 Canvas 上下文最终需要明确的绘制色值,而且主题变化后还要重新绘制。
可以把图表调色板作为输入:
interface ChartPalette { background: string axis: string grid: string label: string line: string point: string } private drawChart(palette: ChartPalette): void { this.ctx.fillStyle = palette.background this.ctx.strokeStyle = palette.grid // 绘制网格、标签、折线和数据点 }窗口或主题层解析当前资源后生成ChartPalette,Canvas 组件在调色板变化时清屏并重绘。只修改页面背景而不重绘 Canvas,会留下白色绘图区或不可见坐标线。
十五、实验模拟 Canvas 要区分物体色与界面色
ExperimentSimPage.ets中存在大量烧杯、液体、导线、刻度和背景色。它们不能全部迁移成页面主题色。应拆成两类:
- 界面色:画布背景、说明文字、网格、控制面板、边框,随主题变化。
- 内容色:化学液体、火焰、金属、指示剂、实验器材,用于表达实验对象,通常保持稳定。
如果把指示剂红色替换成深色主题的危险色,实验含义就会被破坏;如果让画布背景始终白色,深色页面又会出现刺眼亮块。因此 Canvas 迁移必须按语义,而不是按色值数量批量替换。
十六、阴影在深色模式下不能照搬
源码多处使用半透明黑色阴影:
.shadow({ radius: 2, color: '#08000000', offsetY: 1 })在浅色背景中,轻微黑影能表达卡片层级;在深色背景中,同样阴影几乎不可见。深色层级更适合使用表面亮度差、细边框或更克制的环境光效果。建议把卡片层级拆成surface_page、surface_card、border_subtle,阴影仅作为增强,而不是唯一边界。
回归时关闭阴影观察卡片是否仍可识别。如果完全依赖阴影,说明背景与卡片表面缺少足够层级差。
十七、渐变与品牌色要验证文字对比度
HomePage和LearningPage使用蓝色渐变 Banner,文字通常是白色。深色模式不一定需要把品牌渐变反转,但要检查渐变最亮区域与白字的对比度,尤其是小字号说明和透明图标。
建议把 Banner 定义成一组配对令牌,而不是单个背景色:
interface BannerTone { start: ResourceColor end: ResourceColor title: ResourceColor description: ResourceColor buttonBackground: ResourceColor buttonText: ResourceColor }这样修改渐变时会同时审查其上的标题、说明和按钮,不会只调整背景后留下不可读文字。
十八、计算对比度而不是凭肉眼判断
对比度需要按前景与背景的相对亮度计算。以下 TypeScript 工具可用于开发期校验十六进制颜色:
function channel(value: number): number { const s = value / 255 return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4) } function luminance(hex: string): number { const value = hex.replace('#', '') const r = parseInt(value.slice(0, 2), 16) const g = parseInt(value.slice(2, 4), 16) const b = parseInt(value.slice(4, 6), 16) return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b) } function contrast(foreground: string, background: string): number { const a = luminance(foreground) const b = luminance(background) const light = Math.max(a, b) const dark = Math.min(a, b) return (light + 0.05) / (dark + 0.05) }工程发布检查可把正文与背景目标设为不低于 4.5:1,关键图标、标题和按钮等至少检查 3:1。透明色需要先与实际背景合成,再计算最终颜色,不能直接拿八位十六进制的 RGB 部分计算。
十九、迁移要按可回滚批次进行
一次改完几十个页面风险很高。建议分六批:
- 建立 base/dark 同名资源,不解除浅色锁定。
- 迁移
Index、首页、实验室、学习和“我的”五个主页面。 - 迁移共用卡片、搜索、分类标签、表格与弹窗。
- 迁移二级页面和交互状态,清理普通 UI 硬编码色。
- 处理系统栏、ChartView 与实验模拟 Canvas。
- 补齐深色启动资源,解除浅色锁定并执行完整回归。
每批结束都运行构建和核心页面检查。若发现颜色问题,可以回滚单个批次,不会在整个工程中追查一次性替换造成的连锁错误。
二十、用脚本持续发现新增硬编码
迁移完成后,仍需要阻止新代码重新引入散落色值。开发期可以执行:
rg -n --glob '*.ets' "#[0-9A-Fa-f]{6,8}" entry/src/main/ets白名单可以保留实验内容色、需要固定的品牌素材或确有依据的 Canvas 物体色;普通页面背景、文字、边框、按钮和状态色不应继续新增。审查记录要写明文件、行号、语义和保留原因。
资源层也需要检查同名覆盖。一个简单的校验脚本可以读取 base 与 dark 的color.json,比较资源名称集合:
const missingInDark = baseNames.filter((name) => !darkNames.includes(name)) if (missingInDark.length > 0) { throw new Error(`dark resources missing: ${missingInDark.join(', ')}`) }这样新增浅色语义资源却忘记补深色值时,构建或检查阶段就能发现。
二十一、构建验证不能省略
资源名迁移最容易产生拼写错误、资源类型错误和漏文件。工程根目录使用项目既有 hvigor 命令构建:
$env:DEVECO_SDK_HOME='D:\huawei\DevEco Studio\sdk' $env:PATH='D:\huawei\DevEco Studio\jbr\bin;' + $env:PATH & 'D:\huawei\DevEco Studio\tools\hvigor\bin\hvigorw.bat' --no-daemon assembleHap构建通过只说明资源可解析、ArkTS 可编译,不说明视觉正确。仍要在浅色、深色、冷启动、前后台切换、横竖屏和自由窗口下走完整页面。
二十二、主页面双模式验收表
| 页面 | 场景 | 预期结果 | 通过标准 |
|---|---|---|---|
| 首页 | 浅色启动 | 页面与卡片层级清楚 | 正文可读,Banner 按钮清晰 |
| 首页 | 切到深色 | 页面、卡片、标签同步 | 无突兀白块,未选标签可辨 |
| 实验室 | 分类切换 | selected/normal 明确 | 两种模式都能识别当前分类 |
| 学习 | 渐变 Banner | 白字与按钮可读 | 最亮区域仍保持清晰 |
| 我的 | 卡片与分割线 | 层级稳定 | 分割线不消失也不过亮 |
| Tabs | 选中与未选中 | 状态区别明确 | 颜色外仍有位置或图标反馈 |
测试时先进入页面,再切换系统颜色模式,并返回上一级。若只有重新启动后才更新,说明页面或绘制层缓存了旧主题值。
二十三、组件与绘制层验收表
| 对象 | 操作 | 预期结果 | 失败信号 |
|---|---|---|---|
| SearchBar | 输入、清空、失焦 | 输入区与占位符可辨 | 背景和卡片融为一体 |
| CategoryTabs | 点击、按压、键盘焦点 | 四类状态可区分 | 只在浅色可见焦点 |
| DataTable | 查看表头、行、分割线 | 数据行扫描顺畅 | 深色中网格消失 |
| NoteEditorDialog | 打开、编辑、关闭 | 浮层高于页面且文字清晰 | 弹窗仍是纯白或输入框发黑 |
| ChartView | 切换主题 | 背景、轴、标签、折线重绘 | 留下旧主题画布 |
| ExperimentSim | 运行实验 | 器材含义不变、界面适配 | 内容色被误替换成主题色 |
通过标准必须包含操作结果,不只记录“截图看起来正常”。例如 ChartView 需要在主题切换后重新绘制,分类标签需要能继续点击,弹窗关闭后背景恢复正确。
二十四、系统级场景验收表
| 场景 | 操作步骤 | 预期结果 |
|---|---|---|
| 冷启动 | 深色系统下杀进程后启动 | 启动背景、状态栏、首屏连续,无白色闪屏 |
| 热切换 | 应用前台切换系统主题 | 当前页与系统栏同步更新 |
| 前后台 | 切后台改主题再返回 | 页面不保留旧主题缓存 |
| 横竖屏 | 两种主题下旋转 | 无固定背景条与文字裁切 |
| 平板分屏 | 缩放窗口后切主题 | 响应式布局与颜色同时正确 |
| 2in1 | 鼠标悬停与键盘焦点 | 深浅色都能看见反馈轮廓 |
| 字体放大 | 较大系统字体下切主题 | 文字不被固定高度遮挡 |
二十五、常见失败与定位方式
页面切换后局部恢复浅色
检查二级页面是否仍引用AppColors字符串或硬编码色;同时检查页面是否缓存了主题解析结果。优先搜索#色值和AppColors使用点。
深色模式出现白色启动闪屏
检查dark/element/color.json中的start_window_background,并核对启动图、应用图标背景和首屏页面背景是否连续。
状态栏图标看不清
检查系统栏背景、内容色和isStatusBarLightIcon是否成套更新。不能只改背景,不改图标明暗方向。
Canvas 仍显示旧颜色
检查主题变化后是否调用清屏和重绘;确认调色板是新对象或可观察状态,避免仅修改普通 ArkUI 背景。
深色卡片没有边界
检查surface_page与surface_card的亮度差、border_subtle和阴影策略。不要只增加更重黑影。
二十六、源码级完成清单
EntryAbility.ets不再永久强制浅色,且系统栏可跟随主题。- base 与 dark 对所有语义资源使用相同名称。
Theme.ets不再与color.json维护重复色值。- 五个主页面的背景、文字、边界和状态均使用语义资源。
- SearchBar、CategoryTabs、DataTable、弹窗等共用组件已迁移。
- Canvas 界面色与实验内容色已分类,主题变化会重绘。
- 启动窗口在深色模式下没有白色闪屏。
- 浅色与深色正文对比度经过计算和设备观察。
- pressed、selected、disabled、focused、error 均有清晰反馈。
- 资源扫描与 base/dark 名称校验已进入日常检查。
二十七、源码事实与建议实现的边界
本文同时包含现状审计和迁移方案。为避免把示例资源、目标架构或验收标准误当成当前版本已经完成的功能,下面按源码可复核项逐一划清边界。
| 维度 | 当前源码事实 | 本文建议实现 |
|---|---|---|
| 颜色模式 | EntryAbility.onCreate()调用COLOR_MODE_LIGHT,应用固定浅色 | 先完成资源迁移和双模式回归,再评估移除强制浅色 |
| 代码主题 | Theme.ets用static readonly string保存品牌、背景、文字和分类色 | 保留类型与契约,把需随配置变化的颜色迁移为语义资源 |
| 字体令牌 | AppFonts集中 20/16/14/12 等字号与字重 | 继续复用字号令牌,同时执行系统字体放大测试 |
| 间距与圆角 | Constants.ets定义页面边距、卡片间距和两级圆角 | 将其纳入视觉契约,避免页面自行新增魔法数字 |
| 资源真源 | base/element/color.json与Theme.ets存在同义不同值 | 指定资源系统为配置响应颜色的唯一真源 |
| 深色资源 | dark/element/color.json只有白色启动背景 | 为所有语义颜色提供同名 dark 覆盖,并修正启动连续性 |
| 系统栏 | EntryAbility直接写浅色背景与深色内容色 | 主题变化时成套更新背景、内容色和图标明暗策略 |
| Canvas | ChartView.ets同时使用AppColors与#FFFFFF、#CCCCCC等硬编码 | 将界面色注入绘制调色板,主题变化后清屏并重绘 |
| 实验内容色 | 部分颜色表达试剂、火焰或分类含义 | 与界面主题色分离,只有确有语义依据时保持固定 |
| 交互状态 | 当前主题类未定义完整 pressed、focused、disabled、error 令牌 | 按交互语义补齐状态,并在浅色和深色分别验证 |
| 对比度 | 源码没有自动对比度校验流程 | 对正文、图标、按钮、透明叠色和 Canvas 标签建立检查 |
| 构建结果 | 本文未声称迁移方案已在该工程完成构建或真机验证 | 每个迁移批次执行构建,并按页面与系统场景表回归 |
其中,强制浅色、两套颜色值、dark 目录内容和 Canvas 硬编码都来自当前工程;surface_page、content_primary等命名以及六批迁移顺序是建议方案。实施时应保留浅色基线截图和构建产物对照,不能把文章示例直接视为已经交付的源码。
二十八、结语
化学实验室已经拥有AppColors、AppFonts、间距与圆角常量,这说明项目具备统一视觉的基础;但强制浅色、双重颜色真源、dark 资源缺失和 Canvas 硬编码,表明它尚未形成完整的配置响应主题。
最稳妥的升级路线不是立刻切换系统主题,而是先用真实源码盘点颜色来源,把“蓝色、白色、灰色”改写成“操作、表面、内容、边界和状态”等语义,再建立 base/dark 同名资源。随后按主页面、共用组件、二级页面、系统栏和 Canvas 分批迁移,每批都构建并执行明确的操作验收。
当业务页面不再判断深浅色、不再保存十六进制颜色,系统栏和 Canvas 也能消费同一主题契约时,视觉一致性才真正从设计约定变成工程能力。
参考资料
- Huawei Developer,深浅色颜色适配:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ui-dark-light-color-adaptation - Huawei Developer,资源分类与访问:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/resource-categories-and-access - Huawei Developer,ArkUI 概述:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkui-overview - 项目源码:
entry/src/main/ets/common/Theme.ets - 项目源码:
entry/src/main/ets/common/Constants.ets - 项目源码:
entry/src/main/ets/entryability/EntryAbility.ets - 项目资源:
entry/src/main/resources/base/element/color.json - 项目资源:
entry/src/main/resources/dark/element/color.json - 项目源码:
entry/src/main/ets/components/ChartView.ets
AI 辅助声明:本文由 AI 辅助完成源码梳理、结构校对与配图设计,技术结论均以文中列出的本地 ArkTS 源码和资源文件为依据,并由作者复核后发布。