当前位置: 首页 > news >正文

乒乓球口袋教练 HarmonyOS 学习应用(06):主题 Token 与球台视觉风格

一、视觉切换为什么不能直接改业务页面

球台绿、记分卡白底和深色背景都属于视觉语义;课程完成度、收藏和练习记录则属于业务事实。如果页面里散落着十几处颜色常量,切换深色模式时最容易把文本、分隔线和阴影遗漏,同时也诱发组件把视觉选择误当成业务状态。本项目把颜色收敛为 palette,把主题模式收敛为 system、light、dark 三个值。

// 2. 根据用户在设置页选择的 mode 写入对应 palette 到 AppStorage // 3. UI 用 @StorageProp('palette') 拿色,用 @StorageProp('is_dark') 切图标 / 阴影 export class ThemeManager { private static instance: ThemeManager | null = null; private listener: mediaquery.MediaQueryListener | null = null; private systemDark: boolean = false; private mode: ThemeMode = 'system'; static get(): ThemeManager { if (ThemeManager.instance === null) { ThemeManager.instance = new ThemeManager(); } return ThemeManager.instance; } // 应用启动时调用:从持久化恢复 mode(默认 system),订阅系统主题 init(persistedMode: ThemeMode, systemColorMode?: ConfigurationConstant.ColorMode): void { this.mode = persistedMode; AppStorage.setOrCreate<ThemeMode>(KEY_THEME_MODE, this.mode); this.listener = mediaquery.matchMediaSync('(prefers-color-scheme: dark)'); this.systemDark = this.listener.matches; this.updateSystemColorMode(systemColorMode, false); this.refreshSystemDark(false);

二、ThemeManager 如何确定当前调色板

ThemeManager 初始化时读取持久化的 theme_mode,并结合 mediaquery 监听系统颜色模式。它把 palette 与 is_dark 写进 AppStorage,页面通过 StorageProp 读取同一套 Token。这样课程卡片、设置页和首页并不需要各自监听系统事件,视觉更新仍能由一个管理器完成。

参与者输入输出或约束
模型或配置稳定标识、模式或模块字段给出可追溯的工程事实
服务或系统能力经过归一化的请求返回明确结果或失败原因
页面回读后的结果只渲染,不保存第二份事实
} setMode(mode: ThemeMode): void { this.mode = mode; AppStorage.setOrCreate<ThemeMode>(KEY_THEME_MODE, mode); if (mode === 'system') { this.refreshSystemDark(false); } this.applyPalette(); } private resolveDark(): boolean { if (this.mode === 'dark') return true; if (this.mode === 'light') return false; return this.systemDark; } private applyPalette(): void { const isDark: boolean = this.resolveDark(); const palette: ColorPalette = isDark ? DarkPalette : LightPalette; AppStorage.setOrCreate<boolean>(KEY_IS_DARK, isDark); AppStorage.setOrCreate<ColorPalette>(KEY_PALETTE, palette); }

三、设置页只提交模式而不拼接颜色

设置页的单选项只调用 ThemeManager.get().setMode(m.id)。setMode 先写入 theme_mode,再依据模式调用 apply;它不修改课程模型,也不会清空正在输入的搜索词。这个边界很重要:用户回到课程页看到的是同一份学习数据换了一套色彩,而不是一次看起来像“刷新成功”的数据重置。

.onClick(() => { this.themeMode = m.id; ThemeManager.get().setMode(m.id); PreferencesHelper.get().persist('theme_mode', m.id); }); if (i < MODE_OPTIONS.length - 1) { Column().width('92%').height(0.5).backgroundColor(this.palette.divider); } }, (m: ModeOption) => m.id); } .width('100%') .backgroundColor(this.palette.card) .borderRadius(AppSizes.r3) .clip(true); } .alignItems(HorizontalAlign.Start) .width('100%'); } @Builder fontSection() { Column({ space: AppSizes.s2 }) { Text('字体大小')

四、系统主题变化与人工选择怎样区分

system 模式与 dark 模式不能混为一谈。前者允许监听器随系统变化,后者是用户明确锁定深色。当用户从 system 改为 light 后,系统再次变暗不应覆盖其选择;重新选回 system 才恢复监听。若不区分这两条路径,设置页的显示值会和实际调色板发生错位。

情况容易出现的错误本文采用的处理
数据或配置缺项伪造默认成功状态停在可解释的失败或空态
页面重进使用上一页残留对象从模型、服务或系统重新回读
重复动作再写一遍相同业务事实由稳定入口或回调收敛
} export const LightPalette: ColorPalette = { primary: '#0A84FF', primaryEnd: '#5B5BF0', accent: '#FF8A00', bg: '#F5F7FA', bgElevated: '#FFFFFF', card: '#FFFFFF', cardOverlay: '#00000010', divider: '#EAECEF', textPrimary: '#1A1A1F', textSecondary: '#5A6072', textTertiary: '#9097A5', textOnPrimary: '#FFFFFF', success: '#34C759', danger: '#FF3B30', badgeBg: '#E6F0FF', badgeText: '#0A84FF', tabActive: '#0A84FF', tabInactive: '#9097A5', shadow: '#0F1A3320', scrim: '#00000066'

五、切换后的可读性如何回读

验证先在设置页依次选择浅色、深色和跟随系统,分别进入首页和课程卡片回读背景、主文字和辅助文字的对比;再返回设置页确认选中项与主题一致;最后重启应用,检查 theme_mode 恢复而课程进度没有变化。配图展示的是深色设置状态,不能替代对另两种模式的检查。

验收阶段实际动作回读重点
前置确认启动正确 bundle 或打开目标页标题、入口与模块身份
主题操作执行搜索、切换、完成或跳转服务/系统返回的结果
重进检查返回、重启或切换范围后再进入事实没有依赖旧页面残留

六、实现边界与维护顺序

深浅色切换只改主题模式和调色板 Token,课程内容、进度与输入状态不随视觉模式发生第二份写入。 新增需求时应先补齐模型、配置或服务合同,再调整页面入口;把同一个判断复制到多个组件,短期看似方便,后续会使结果无法回读。ArkTS 状态管理的基础机制可参考 HarmonyOS 官方文档。

七、继续扩展时的约束

调色板的变化范围应当只包括表现层。课程模型、视频位置、练习日志和测验成绩即使被写入 AppStorage,也不能和 theme_mode 使用同一个含义含混的键。主题状态越独立,越能避免一次皮肤升级被误判为学习数据迁移。

深色模式的验收要注意可读不是只有文字没有消失。辅助说明、时间戳、未解锁徽章、禁用按钮和卡片边线在低亮度背景上也要保持区分度。把这些元素的角色写进 palette,可以让设计调整只影响 Token,而不是让每一个页面临时增加深色分支。

如果设备系统颜色变化发生在设置页之外,页面重新出现时应订阅已经更新的 palette,而不需要导航栈逐页重建。这个体验依赖 ThemeManager 的唯一实例,因此模块初始化和销毁时要避免重复注册监听器。

Token 不能被组件私自覆盖

组件可以根据 is_dark 选择图标资源,却不应在局部把文本颜色改成另一套固定色。局部覆盖一多,设置页显示深色而课程页仍有浅色残留的问题就无法从 ThemeManager 追踪。需要新增视觉角色时,应先扩展 palette 定义,再让组件消费这个名字明确的角色。

设置值的恢复顺序

应用重新启动后先恢复 theme_mode,再生成 palette,最后让页面读取 StorageProp。若先渲染页面再异步补颜色,用户会看到短暂的闪白;若恢复失败,则使用明确的 system 默认值并允许在设置页重新选择,而不是留下半初始化的颜色对象。

http://www.jsqmd.com/news/1293620/

相关文章:

  • Navicat重置工具Mac版终极指南:轻松实现无限试用的完整解决方案
  • 从ASK/FSK信号调制到雷达通信系统:基础原理与工程实践全解析
  • 大模型Agents工作流优化与本地部署实践
  • RFID电力工器具全流程数字化管理方案设计与实践
  • A-59工业级语音模块:十种工作模式的架构逻辑与接口兼容性分析
  • Prompt Drift 检测实战:Taotoken 监控 4 个模型 30 天后,我们这样止损
  • Codex 工程化落地指南 05:.codex/config.toml 与权限配置——沙箱: 审批、网络访问与可信项目
  • 2026年07月浙江三联环保科技股份有限公司LWnj系列泥浆分离脱水机市场格局与价值解析 - 优企名品
  • 徐汇业主参考|2026.7月上海专属防水修缮深度指南:适配长三角滨海软土地质、全域辖区堵漏、根治申城梅雨季渗漏顽疾 - 吉林同城获客
  • 一加15顶配版游戏性能实测:骁龙8 Gen4与散热系统深度解析
  • C++学生信息管理系统:从类设计到文件存储的完整项目实践
  • 重新定义局域网文件共享:LANShare如何颠覆传统传输范式
  • SDR++完全指南:跨平台软件定义无线电的终极解决方案
  • C语言fork炸弹原理与防御:从Linux进程耗尽到Docker容器安全
  • 【PC】 MuMu模拟器国际版(MuMuPlayer)V6.3.1
  • 抖音批量下载终极指南:如何一键保存所有喜欢的内容
  • 徐州婚房装修怎么选?公认本土婚房第一品牌:徐州金墨斗装饰深度实力解析 - 装修大师
  • 2027届毕业生就业前景分析与在校期间职业发展规划建议指南
  • 【2027最新】基于SpringBoot+Vue的ONLY在线商城系统管理系统源码+MyBatis+MySQL
  • 从政策到薪资再到课程体系:数据安全工程师证书全景解析
  • 从带宽到GBW:模拟电路频率响应与稳定性设计核心解析
  • 武汉科谷技工学校形象设计专业招生 轻美业稳定创业 中考落档女生热门选择 - 武汉中职最新信息发布
  • Keil5 STM32汇编工程创建与Hex文件深度解析
  • 2026年7月评测:五家主流江北新区家装公司选型参考 - 博客万
  • 如何永久保存微信聊天记忆:WeChatMsg完全指南
  • 2024年C++基础面试核心考点深度解析与实战指南
  • Fate/Grand Automata 终极指南:快速实现FGO自动战斗的完整解决方案
  • 高校附属医院的医疗技术转化收益分配比例,国家规定的最高上限是多少?
  • 涡旋压缩机壳体焊完就漏?精密激光焊接两招破局
  • 9款AI论文写作工具提升MBA学术效率