【化学实验室|17】HarmonyOS ArkTS 亮暗色与视觉令牌实战:集中颜色、间距和交互状态避免页面割裂

一个项目拥有 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() 中存在以下真实代码:

ts 复制代码
this.context.getApplicationContext().setColorMode(
  ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT
)

这行代码把应用锁在浅色模式。它解释了为什么当前页面即使在系统开启深色模式时仍保持浅色,也解释了为什么 dark/element/color.json 长期没有被补齐:运行时通常不会进入深色资源匹配。

因此本文的第一条结论是:在迁移完成前,锁定浅色可以避免半成品主题进入用户界面;在 base、dark、系统栏和绘制层全部验证后,才删除这条强制浅色设置,让应用重新跟随系统。不能先解除锁定,再边运行边寻找白底白字问题。

二、Theme.ets 已经提供了一个起点

当前 Theme.ets 把颜色分成品牌、状态、背景、文字、Tab 和实验分类几组:

ts 复制代码
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。

当同一个语义有两套值时,会出现三个后果:

  1. 使用 $r() 的启动页和使用 AppColors 的业务页看起来不是同一套品牌。
  2. 修改 Theme.ets 无法覆盖资源引用,修改 color.json 也无法覆盖字符串常量。
  3. 深色资源只能替换 $r() 引用,无法替换编译进代码的字符串。

迁移时必须指定唯一真源。对需要根据配置自动变化的颜色、尺寸和媒体,优先使用资源系统;Theme.ets 可以保留类型、状态映射和非视觉常量,但不再保存两套重复色值。

四、dark 目录只有一个资源意味着什么

当前 dark/element/color.json 只有:

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 的稳定值迁移:

json 复制代码
{
  "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 使用完全相同的名称,但选择经过对比度验证的深色值:

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" }
  ]
}

这些值是迁移起点,不是所有项目通用的固定答案。最终仍要用真实字体、字号、透明度和背景组合计算对比度,并在设备上观察视觉层级。

七、组件只引用语义资源

原代码:

ts 复制代码
Text('实验室')
  .fontColor(AppColors.TEXT_PRIMARY)

Column()
  .backgroundColor(AppColors.CARD_BG)

迁移后:

ts 复制代码
Text('实验室')
  .fontColor($r('app.color.content_primary'))

Column()
  .backgroundColor($r('app.color.surface_card'))

组件不需要读取 isDark,也不需要写三元表达式。资源匹配层负责提供颜色,组件只表达"这里是主文字""这里是卡片表面"。这样系统主题变化时,页面结构不会出现大量条件分支。

八、Theme.ets 应从颜色仓库变成主题契约

迁移完成后,Theme.ets 不必被删除。它可以保存语义角色、组件状态映射或纯类型,避免业务页面拼接资源名:

ts 复制代码
export interface ComponentTone {
  background: ResourceColor
  foreground: ResourceColor
  border: ResourceColor
}

export enum VisualState {
  Normal,
  Selected,
  Disabled,
  Danger
}

如果团队希望集中导出资源引用,也要确保导出的仍是 ResourceColor,不是十六进制字符串。最重要的边界是:主题契约描述角色,资源文件提供配置相关的值,业务组件消费角色。

九、间距和圆角也属于视觉令牌

Constants.ets 已经集中保存:

ts 复制代码
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 内文字也要从同一套字号与颜色规则读取,不能独立写死。

十一、硬编码颜色需要分类迁移

源码扫描可看到多类硬编码:

text 复制代码
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:

ts 复制代码
.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 当前把系统栏固定为浅色:

ts 复制代码
mainWindow.setWindowSystemBarProperties({
  statusBarColor: '#F5F7FA',
  statusBarContentColor: '#1A1A1A',
  isStatusBarLightIcon: false,
  navigationBarColor: '#FFFFFF',
  navigationBarContentColor: '#1A1A1A',
  isNavigationBarLightIcon: false
})

解除浅色锁定后,如果这里不变,深色页面顶部仍会出现浅色条。应把系统栏配置封装成一个窗口级方法,在窗口创建和颜色配置变化时调用。方法输入是当前主题画像,输出是背景色、内容色以及图标明暗方向。

ts 复制代码
interface SystemBarPalette {
  statusBackground: string
  statusContent: string
  navigationBackground: string
  navigationContent: string
  useLightIcon: boolean
}

系统栏属于窗口层,不应散落在每个业务页面。验证时要同时观察普通页面、弹窗、返回后的页面和冷启动窗口,避免只修主页面截图。

十四、Canvas 不会自动理解组件主题

ChartView.ets 使用 Canvas 绘制背景、网格、文字和折线,其中既有 AppColors,也有 #FFFFFF、#CCCCCC、#EEEEEE。ArkUI 的 Text 可以直接消费资源颜色,但 Canvas 上下文最终需要明确的绘制色值,而且主题变化后还要重新绘制。

可以把图表调色板作为输入:

ts 复制代码
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 迁移必须按语义,而不是按色值数量批量替换。

十六、阴影在深色模式下不能照搬

源码多处使用半透明黑色阴影:

ts 复制代码
.shadow({ radius: 2, color: '#08000000', offsetY: 1 })

在浅色背景中,轻微黑影能表达卡片层级;在深色背景中,同样阴影几乎不可见。深色层级更适合使用表面亮度差、细边框或更克制的环境光效果。建议把卡片层级拆成 surface_page、surface_card、border_subtle,阴影仅作为增强,而不是唯一边界。

回归时关闭阴影观察卡片是否仍可识别。如果完全依赖阴影,说明背景与卡片表面缺少足够层级差。

十七、渐变与品牌色要验证文字对比度

HomePage 和 LearningPage 使用蓝色渐变 Banner,文字通常是白色。深色模式不一定需要把品牌渐变反转,但要检查渐变最亮区域与白字的对比度,尤其是小字号说明和透明图标。

建议把 Banner 定义成一组配对令牌,而不是单个背景色:

ts 复制代码
interface BannerTone {
  start: ResourceColor
  end: ResourceColor
  title: ResourceColor
  description: ResourceColor
  buttonBackground: ResourceColor
  buttonText: ResourceColor
}

这样修改渐变时会同时审查其上的标题、说明和按钮,不会只调整背景后留下不可读文字。

十八、计算对比度而不是凭肉眼判断

对比度需要按前景与背景的相对亮度计算。以下 TypeScript 工具可用于开发期校验十六进制颜色:

ts 复制代码
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 部分计算。

十九、迁移要按可回滚批次进行

一次改完几十个页面风险很高。建议分六批:

  1. 建立 base/dark 同名资源,不解除浅色锁定。
  2. 迁移 Index、首页、实验室、学习和"我的"五个主页面。
  3. 迁移共用卡片、搜索、分类标签、表格与弹窗。
  4. 迁移二级页面和交互状态,清理普通 UI 硬编码色。
  5. 处理系统栏、ChartView 与实验模拟 Canvas。
  6. 补齐深色启动资源,解除浅色锁定并执行完整回归。

每批结束都运行构建和核心页面检查。若发现颜色问题,可以回滚单个批次,不会在整个工程中追查一次性替换造成的连锁错误。

二十、用脚本持续发现新增硬编码

迁移完成后,仍需要阻止新代码重新引入散落色值。开发期可以执行:

powershell 复制代码
rg -n --glob '*.ets' "#[0-9A-Fa-f]{6,8}" entry/src/main/ets

白名单可以保留实验内容色、需要固定的品牌素材或确有依据的 Canvas 物体色;普通页面背景、文字、边框、按钮和状态色不应继续新增。审查记录要写明文件、行号、语义和保留原因。

资源层也需要检查同名覆盖。一个简单的校验脚本可以读取 base 与 dark 的 color.json,比较资源名称集合:

ts 复制代码
const missingInDark = baseNames.filter((name) => !darkNames.includes(name))
if (missingInDark.length > 0) {
  throw new Error(`dark resources missing: ${missingInDark.join(', ')}`)
}

这样新增浅色语义资源却忘记补深色值时,构建或检查阶段就能发现。

二十一、构建验证不能省略

资源名迁移最容易产生拼写错误、资源类型错误和漏文件。工程根目录使用项目既有 hvigor 命令构建:

powershell 复制代码
$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 也能消费同一主题契约时,视觉一致性才真正从设计约定变成工程能力。

参考资料

  1. Huawei Developer,深浅色颜色适配:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ui-dark-light-color-adaptation
  2. Huawei Developer,资源分类与访问:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/resource-categories-and-access
  3. Huawei Developer,ArkUI 概述:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkui-overview
  4. 项目源码:entry/src/main/ets/common/Theme.ets
  5. 项目源码:entry/src/main/ets/common/Constants.ets
  6. 项目源码:entry/src/main/ets/entryability/EntryAbility.ets
  7. 项目资源:entry/src/main/resources/base/element/color.json
  8. 项目资源:entry/src/main/resources/dark/element/color.json
  9. 项目源码:entry/src/main/ets/components/ChartView.ets

AI 辅助声明:本文由 AI 辅助完成源码梳理、结构校对与配图设计,技术结论均以文中列出的本地 ArkTS 源码和资源文件为依据,并由作者复核后发布。

相关推荐
Csvn2 小时前
SSR/SSG/ISR(上):峡谷与渲染策略的时间预算
前端
Csvn2 小时前
SSR/SSG/ISR(下):可交互性、新鲜度与回源账
前端
Elcker5 小时前
YnuoAgent(依诺):一个 npm 包,跑起属于自己的 AI 研发平台
前端·人工智能·npm
计算机魔术师5 小时前
发现2天,修复2个月:AI挖穿PHP后,卡兹克讲透攻防时间差
前端
风骏时光牛马5 小时前
测试标题001
前端
贾伟康5 小时前
【化学实验室|18】HarmonyOS ArkTS 权限与隐私实战:让 module.json5、功能说明和拒绝路径一致
安全·移动开发·harmonyos·arkts·arkui
IT_陈寒6 小时前
Java性能优化完全指南
前端·人工智能·后端
电子IT笔记6 小时前
对接群晖NAS,上传和删除踩坑记录
前端·后端
计算机魔术师6 小时前
半年前说不缺钱,半年后150亿美元超募
前端