news 2026/9/8 19:26:01

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【句匠|17】HarmonyOS ArkTS 亮暗色与视觉令牌实战:集中颜色、间距和交互状态避免页面割裂

一个 HarmonyOS 应用页面增加到十几个之后,视觉问题往往不是“某个颜色不好看”,而是同一种语义在不同页面被写成不同值:标题有时 18vp、有时 20vp;卡片圆角一会儿 12vp、一会儿 18vp;成功状态和主按钮都用绿色,却没有区分品牌色与语义色;暗色模式下某些页面自动变暗,另一些页面仍固定白底。这样的应用单页看似正常,连续使用时却会明显割裂。

本文基于句匠项目D:\huawei\one18-11的真实 ArkTS 源码,重点核对entry/src/main/ets/common/constants/ThemeConstants.etsEntryAbility.etsresources/base/element/color.jsonresources/dark/element/color.json,并抽查TopBar.etsGreenButton.etsBankCard.etsSectionHeader.etsPracticePage.etsSettingsPage.ets等页面和组件。

本文唯一源码标识:com.jiaweikang.one18

先说结论:句匠已经把大量颜色、字号、间距、圆角和状态色集中到ColorsSizes,共享组件也在复用这些令牌;但应用当前明确锁定浅色模式,dark 资源与 base 资源值相同,ArkTS 颜色常量又是固定十六进制字符串。因此,源码可以证明的是“浅色视觉体系已经部分令牌化”,不能证明“亮暗色自动适配已经完成”。

一、视觉令牌解决的不是美术问题,而是语义一致性

视觉令牌是把“为什么使用这个值”变成稳定名称。比如:

Colors.TEXT_PRIMARY Colors.TEXT_HINT Colors.SUCCESS Sizes.BODY_FONT Sizes.CARD_RADIUS Sizes.PADDING_LARGE

页面不需要记住标题到底是#1F2A26,只需要表达“这是主要文本”。以后调整配色时,修改令牌即可影响所有正确引用它的页面。

视觉令牌通常分成三层:

层级示例作用
原始值#2F6B5E、16vp最底层数值
语义令牌PRIMARYTEXT_HINT描述用途
组件令牌按钮背景、卡片圆角描述具体组件状态

句匠已有前两层的一部分,也有答题选项这一类接近组件级的状态令牌。

二、句匠的品牌色如何被集中

ThemeConstants.ets将英伦学院风定义为墨绿、米白与暗金:

export class Colors { static readonly PRIMARY: string = '#2F6B5E' static readonly PRIMARY_DARK: string = '#1F5246' static readonly PRIMARY_LIGHT: string = '#E6EFEB' static readonly ACCENT: string = '#8A6814' static readonly INK: string = '#3A4A45' }

这些值承担不同职责:

  • PRIMARY用于主按钮、进度、选中状态;
  • PRIMARY_DARK用于按压感或渐变下端;
  • PRIMARY_LIGHT用于标签底色和选中区域浅底;
  • ACCENT用于金色点缀;
  • INK作为墨色扩展。

把“深一点的绿色”命名为PRIMARY_DARK,比页面里散落#1F5246更容易维护。设计调整时也能知道它与主色的关系。

三、背景与表面必须分开

项目定义了三种背景层级:

static readonly BACKGROUND = '#F5F1E8' static readonly BACKGROUND_ALT = '#FBF8F0' static readonly SURFACE = '#FFFFFF'

BACKGROUND是页面底,SURFACE是卡片与工具栏,BACKGROUND_ALT是卡片内部的次级区域。即使都接近浅色,语义仍不同。

如果所有区域都直接写Color.White,页面层级只能依赖阴影和边框;如果每个页面随意挑一种米白,又会出现轻微但持续的色差。令牌让页面背景、卡片表面与嵌套区域保持稳定关系。

四、文本颜色要按信息层级定义

真实源码中有:

static readonly TEXT_PRIMARY = '#1F2A26' static readonly TEXT_SECONDARY = '#444444' static readonly TEXT_HINT = '#5F6763'

它们分别服务标题、正文、辅助信息。TEXT_HINT的注释明确写了“符合 4.5:1”,说明项目在调整辅助文字时考虑了正文可读性,而不是只追求浅灰效果。

页面使用时应按语义选择:

Text('题库') .fontColor(Colors.TEXT_PRIMARY) Text('按地区与题型挑选练习内容') .fontColor(Colors.TEXT_HINT)

同一页面内,如果标题、正文、占位符、禁用文本都用同一种灰色,信息层级会消失;如果辅助文字过浅,又会成为 AppGallery 色彩对比风险。

五、状态色不能由品牌色代替

句匠单独定义:

static readonly SUCCESS = '#267A55' static readonly ERROR = '#B3261E' static readonly WARNING = '#8A5A00'

虽然品牌主色也是绿色,但成功色仍有独立语义。这样未来品牌色改成蓝色时,“答题正确”不需要跟着变成蓝色。

错误、警告、成功还应同时使用文字、图标或形状表达,不能只依赖色相。对于色觉差异用户,仅靠红绿判断选项正误并不可靠。当前项目已有正确/错误背景与边框令牌,后续可以结合图标或明确文案继续增强。

六、答题选项已经形成组件状态矩阵

Colors对答题选项定义了完整状态:

OPTION_BG OPTION_BORDER OPTION_SELECTED_BG OPTION_SELECTED_BORDER OPTION_CORRECT_BG OPTION_CORRECT_BORDER OPTION_WRONG_BG OPTION_WRONG_BORDER

这比页面里写一串三元表达式更稳。一个答题选项至少包含默认、选中、正确、错误四种状态,每种状态又有背景与边框两个维度。

状态矩阵可以写成:

状态背景边框文本/图标
默认OPTION_BGOPTION_BORDER主要文本
选中OPTION_SELECTED_BGOPTION_SELECTED_BORDER主色强调
正确OPTION_CORRECT_BGOPTION_CORRECT_BORDER成功语义
错误OPTION_WRONG_BGOPTION_WRONG_BORDER错误语义

这类组件级令牌最能防止 PracticePage 多种题型之间出现交互状态不一致。

七、字号、间距和圆角同样是令牌

Sizes并不只有颜色:

static readonly H1_FONT = 22 static readonly H2_FONT = 18 static readonly TITLE_FONT = 16 static readonly BODY_FONT = 14 static readonly CAPTION_FONT = 12 static readonly SMALL_FONT = 10

同时还集中:

CARD_RADIUS CARD_RADIUS_SM BTN_RADIUS PADDING_SMALL PADDING_MEDIUM PADDING_LARGE PADDING_XL TAB_BAR_HEIGHT BOTTOM_NAV_MIN_PADDING

这让视觉一致性从颜色扩展到排版、空间与形状。TopBarSectionHeaderBankCardGreenButton都在引用这些值。

需要注意,SMALL_FONT = 10对手机辅助信息尚可,但不能无差别用于正文;在 PC/2in1 场景,10vp 也可能偏小。令牌化不代表数值永远正确,它只是让后续统一调整变得可能。

八、共享组件是令牌真正落地的位置

仅定义ColorsSizes不会自动获得一致性,页面必须通过共享组件消费它们。

TopBar.ets使用:

.colorBlend(Colors.PRIMARY) .backgroundColor(Colors.SURFACE) .border({ width: 1, color: Colors.DIVIDER }) .fontSize(Sizes.H2_FONT) .fontColor(Colors.TEXT_PRIMARY)

GreenButton.ets使用主色与深主色构建渐变:

.linearGradient({ angle: 135, colors: [ [Colors.PRIMARY, 0], [Colors.PRIMARY_DARK, 1] ] })

BankCard.ets则统一卡片背景、标题、标签、辅助文字和圆角。共享组件覆盖得越多,页面越不容易自行发明另一套样式。

九、alpha 工具如何减少透明色散落

项目提供:

export function alpha( color: string, alphaHex: string ): string { if (!color || color.length < 7) return color return '#' + alphaHex + color.substring(1, 7) }

ArkUI 十六进制颜色采用#AARRGGBB,这个工具把基础颜色和透明度组合。比如:

alpha(Colors.SUCCESS, '15')

比直接写#15267A55更能看出语义关系。

真实边界也要写清楚:该函数只取color.substring(1, 7),适用于项目当前的#RRGGBB字符串;如果传入资源颜色、短格式、已有 Alpha 或非十六进制表示,就不一定符合预期。它不是通用颜色解析器。

十、当前应用明确锁定浅色模式

EntryAbility.onCreate()中存在:

this.context .getApplicationContext() .setColorMode( ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT )

这意味着应用启动时主动选择浅色,而不是跟随系统暗色模式。调用放在try/catch中,失败会记录日志,但正常情况下用户切换系统暗色不会让应用进入真正暗色主题。

因此,标题里的“亮暗色”应理解为对现状与迁移方法的实战分析,不能解释为项目当前已提供亮暗色开关。

十一、dark 资源存在,但内容与 base 完全相同

项目同时存在:

resources/base/element/color.json resources/dark/element/color.json

但两个文件中的start_window_backgroundprimarybackgroundsurfacetext_primary等值完全相同,例如背景都为#F5F5F5,表面都为#FFFFFF

这说明资源目录结构已经准备好,但暗色值并没有设计完成。即使移除强制浅色,如果页面引用这些资源,暗色限定目录也不会产生视觉变化。

更关键的是,主要页面实际大量引用ThemeConstants.ets中的固定字符串,而不是$r('app.color.background')。资源限定机制无法自动替换这些 ArkTS 常量。

十二、当前存在两套并不一致的颜色来源

ThemeConstants.ets的主色是#2F6B5E,资源color.jsonprimary却是#4CAF50;前者背景是米白#F5F1E8,后者是灰白#F5F5F5

这构成双重真源:

ArkTS Colors.PRIMARY → #2F6B5E resource app.color.primary → #4CAF50

如果启动页、原生配置或少数页面使用资源,而大部分页面使用 ArkTS 常量,就可能在启动过渡、对话框或系统组件上看到配色跳变。

真正稳定的主题体系需要指定唯一真源。对于需要系统亮暗色限定的颜色,优先使用同名资源;ArkTS 中可以保留尺寸、业务映射和辅助函数,但不应再复制一套固定颜色值。

十三、硬编码颜色仍然大量存在

源码抽查显示,多个页面除了Colors.*之外,还直接使用:

Color.White '#E6FFFFFF' '#33000000' '#29000000' '#00000000' '#B3000000'

部分硬编码是合理的,例如深色渐变上的白字与半透明遮罩;但它们仍需进入主题审计。暗色模式下,固定白字可能落在浅色背景上,固定黑色遮罩可能使内容过暗。

迁移时可以将常见叠加层集中:

HERO_TEXT HERO_TEXT_SECONDARY SCRIM_LIGHT SCRIM_STRONG PRESSED_OVERLAY

这段命名是建议,不是当前源码已有字段。

十四、真正双主题应怎样迁移

建议按风险从低到高分四步:

  1. 先确定是否跟随系统、提供应用内切换,还是继续锁定浅色;
  2. 统一颜色真源,把页面级固定颜色迁移到语义资源;
  3. 为 dark 限定目录填写真实暗色值;
  4. 移除强制浅色并做全页面回归。

资源可以保持同名:

{ "color": [ { "name": "background", "value": "#F5F1E8" }, { "name": "surface", "value": "#FFFFFF" }, { "name": "text_primary", "value": "#1F2A26" } ] }

dark 目录使用同样名称,但填写经过对比度验证的深色值。页面引用:

.backgroundColor($r('app.color.background')) .fontColor($r('app.color.text_primary'))

这样系统资源解析才会根据限定目录切换。

十五、不能机械反转浅色调色板

暗色主题不是把白变黑、黑变白。句匠的墨绿和暗金在深色背景上可能失去对比度,浅绿色标签底也不能直接沿用。

暗色设计至少要重新确定:

语义浅色关注点暗色关注点
页面背景米白氛围避免纯黑造成强烈反差
卡片表面与背景分层比背景略亮并保持边界
主文本深色高对比近白但避免刺眼
辅助文本满足 4.5:1不使用过暗灰
主色品牌识别提升亮度或降低饱和
分割线可见但克制避免完全消失

成功、错误、警告状态还要分别检查文字、背景和边框组合,不能只验证单个色值。

十六、组件状态要覆盖按压、禁用与聚焦

当前GreenButton能表达主按钮与部分视觉变化,答题选项也有选中、正确、错误状态。但完整的 PC/2in1 和多输入体验还需要:

  • 普通;
  • 按压;
  • 禁用;
  • 键盘聚焦;
  • 鼠标悬停;
  • 加载中;
  • 操作成功或失败。

视觉令牌可以继续扩展:

BUTTON_PRIMARY_BG BUTTON_PRIMARY_PRESSED_BG BUTTON_DISABLED_BG BUTTON_DISABLED_TEXT FOCUS_RING HOVER_OVERLAY

是否需要全部字段取决于组件,不应一次性制造庞大体系。但关键工作流至少要有可识别的禁用与反馈状态。

十七、主题切换必须连同系统栏和启动页验证

主题不仅存在于 ArkUI 页面。句匠还涉及:

  • start_window_background
  • 状态栏图标明暗;
  • 底部导航指示区;
  • 对话框与弹窗;
  • 图片、图标和渐变;
  • 空状态与错误状态;
  • TextArea 占位符;
  • Canvas 或自绘内容。

如果启动页仍是白色,而首屏变为深色,会出现明显闪白。若状态栏背景变深但图标仍使用深色模式,图标会不可见。移除COLOR_MODE_LIGHT前必须把这些系统表面一起纳入验收。

十八、用可执行矩阵检查对比度

发布前不应只凭肉眼说“看起来清楚”。建议至少检查:

正文文字 / 页面背景 > 4.5:1 标题与关键图标 / 背景 > 3:1 按钮文字 / 按钮背景 > 3:1 辅助文字 / 卡片表面 > 4.5:1 禁用态仍能识别,但不与可用态混淆

矩阵需要覆盖浅色与暗色、普通与按压、选中与未选中、正确与错误、占位符、分割线和弹窗。句匠当前注释提到了TEXT_HINT对比度,但源码中没有完整自动化对比度测试记录,因此不能把整个应用描述为已经全部测量通过。

十九、如何减少页面割裂

从真实源码出发,最有效的整理顺序是:

  1. 保留ColorsSizes已形成的语义命名;
  2. TopBarGreenButtonBankCardSectionHeader等共享组件成为页面默认入口;
  3. 统计页面中的硬编码颜色与尺寸,按出现频率迁移;
  4. 合并 ArkTS 常量与资源颜色双重真源;
  5. 再设计 dark 限定资源和系统栏策略;
  6. 最后移除强制浅色并做真机/模拟器回归。

这样可以先获得一致性收益,再承担主题切换风险。直接删除setColorMode(),只会把未完成的 dark 资源和固定十六进制颜色同时暴露出来。

二十、结语:先把现状说清楚,再谈暗色适配

句匠真实源码已经完成了一项有价值的基础工作:墨绿、米白、暗金、文本层级、状态色、答题选项状态、字号、间距、圆角和安全区尺寸,大量集中在ThemeConstants.ets,共享组件也在持续消费这些令牌。这能显著降低页面各写一套样式的风险。

但“存在 dark 目录”不等于支持暗色,“定义 Colors 类”也不等于具备动态主题。当前应用锁定浅色,dark 与 base 资源相同,主要 ArkTS 颜色是固定字符串,资源文件与主题常量还存在不同配色。这些都是必须如实记录的工程边界。

一个可靠的 HarmonyOS 5.0 及以上主题体系,应以语义令牌为中心,以同名亮暗资源为唯一颜色真源,让共享组件统一消费,并对系统栏、启动页、图片和完整交互状态做对比度回归。做到这一步,亮暗色适配才不是“换一张色表”,而是整个应用视觉语义的一致切换。

本文部分内容由 AI 辅助整理,所有能力判断均基于句匠真实源码;未把浅色锁定状态描述成已完成的系统暗色适配,也未虚构审核或发布结果。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 19:25:04

用Hermes实现自动化代码评审:GitHub PR审查的部署、规则与实战

夜里十一点&#xff0c;我打开 GitHub 的 PR 列表&#xff0c;一排 pull request 挂着 changes requested&#xff0c;点进去却发现根本没有一条有效评论。这种状态持续了一个多月后&#xff0c;我决定把 Hermes 接进来做自动化代码评审。让机器先把每一条 PR 完整读一遍&#…

作者头像 李华
网站建设 2026/9/8 19:23:37

Jetson显示架构解析:从硬件DC到Virtual Channel的无头部署实战

不用把Virtual Channel Driver想成某个单独的内核模块&#xff0c;它更像Jetson显示系统里一张隐形的网——平时没人注意&#xff0c;一旦你要在Orin NX上搞虚拟显示、多屏扩展或者无头部署&#xff0c;它立刻变成绕不过去的坎。这篇文章我就从自己调板子的经验出发&#xff0c…

作者头像 李华
网站建设 2026/9/8 19:20:25

YOLOv3表情识别实战:从数据集标注到模型训练与部署全流程

简介&#xff1a;针对YOLO系列模型训练的人脸表情识别资源包&#xff0c;整合了预训练权重与完整数据集&#xff0c;特别适合需要快速开展表情识别实验的开发者或研究团队。数据集已完成train、val、test划分&#xff0c;并预先编写好data.yaml配置&#xff0c;包含anger、happ…

作者头像 李华
网站建设 2026/9/8 19:18:16

防火墙IPSEC IKEV1与明细策略配置

一 组网说明 如上图:防火墙1和防火墙2建立IPSEC vpn,使用ikev1协议,并且防火墙侧放通明细的防火墙策略 二 设备配置 2.1 FW1设备配置 sysname FW1 # ip unreachables enable ip ttl-expires enable # lldp global enable # acl advanced 3000 //NAT转换数据流 descr…

作者头像 李华