HarmonyOS时光胶囊——主题系统的四套配色与切换机制

📅 2026/7/16 19:55:16
HarmonyOS时光胶囊——主题系统的四套配色与切换机制
时光胶囊 App 支持四套主题——暖金、亮色、暗色、护眼。切换主题后整个 App 的背景色、文字色、主色、边框色全部同步变化。这种一个变量驱动全局颜色的机制核心是一个 ThemeColors 接口和一个 getThemeColors 函数。设计得好新增主题只需要加一个分支设计得差改一个颜色需要改几十个文件。完整效果一、主题系统的整体架构ThemeType 枚举 定义四种主题 ↓ getThemeColors() 根据 ThemeType 返回 ThemeColors 对象 ↓ this.getColors() Index 页面的快捷方法 ↓ 所有 UI 组件 通过 colors 参数接收颜色四层架构——枚举→工厂函数→快捷方法→组件消费。每一层只做一件事职责清晰。二、ThemeType 枚举// 从 utils/Theme.ets 导入enumThemeType{CAPSULECAPSULE,// 暖金主题默认LIGHTLIGHT,// 亮色主题DARKDARK,// 暗色主题EYE_CAREEYE_CARE// 护眼主题}四种主题的定位主题键值目标场景CAPSULE‘CAPSULE’默认主题温暖金色调LIGHT‘LIGHT’标准亮色高对比度DARK‘DARK’暗色背景低亮度EYE_CARE‘EYE_CARE’低蓝光长时间阅读CAPSULE 是默认主题——和其他 App 的默认主题不同。知识库 App 默认紫色旅行 App 默认橙色时光胶囊默认暖金色。每个 App 的默认主题和它的产品调性匹配——时光胶囊的暖金传递温暖和期待。三、ThemeColors 接口从所有页面的使用方式推断ThemeColors 包含这些字段interfaceThemeColors{primary:string// 主色按钮、标签、进度条background:string// 页面背景色surface:string// 卡片/容器背景色text:string// 主文字色标题、正文textSecondary:string// 辅助文字色说明、标签textTertiary:string// 最弱文字色时间、占位符border:string// 边框色error:string// 错误/危险色}颜色字段的使用频率字段使用位置频率primary按钮、选中标签、进度条、链接最高background页面背景高surface卡片、输入框、导航栏背景最高text标题、正文最高textSecondary说明文字、辅助信息高textTertiary时间戳、占位符中border边框、分隔线高error清空按钮、危险操作低primary 和 surface 是使用频率最高的——几乎所有交互元素都用到。这两个字段的配色决定了整个主题的性格。四、四套配色方案暖金主题CAPSULE字段值说明primary#D4A574暖金色background#FBF8F3米白色surface#FFFFFF纯白text#2C2417深棕textSecondary#8C7B6B浅棕textTertiary#B8A99A更浅棕border#E8E0D5暖灰error#E74C3C红色整个色系围绕棕色金色——温暖、复古、有时间感。和时光胶囊的产品主题高度契合。亮色主题LIGHT字段值说明primary#3B82F6标准蓝background#F9FAFB浅灰白surface#FFFFFF纯白text#111827近黑textSecondary#6B7280中灰textTertiary#9CA3AF浅灰border#E5E7EB边框灰error#EF4444红色标准亮色主题——高对比度适合白天使用。蓝色主色是最安全的选择——不暖不冷大多数用户接受度高。暗色主题DARK字段值说明primary#60A5FA亮蓝background#111827深灰黑surface#1F2937稍浅灰text#F9FAFB近白textSecondary#9CA3AF中灰textTertiary#6B7280暗灰border#374151深灰error#F87171浅红暗色主题的文字和背景完全反转——背景变深文字变浅。主色从标准蓝变为亮蓝——在深色背景上需要更亮的颜色才能保持可读性。护眼主题EYE_CARE字段值说明primary#059669绿色background#ECFDF5浅绿白surface#FFFFFF纯白text#065F46深绿textSecondary#6B7280中灰textTertiary#9CA3AF浅灰border#D1FAE5浅绿error#EF4444红色护眼主题用绿色为主色——绿色对眼睛的刺激最小。背景带微弱绿色调减少蓝光辐射。五、getThemeColors 工厂函数exportfunctiongetThemeColors(theme:ThemeType):ThemeColors{switch(theme){caseThemeType.CAPSULE:return{primary:#D4A574,background:#FBF8F3,...}caseThemeType.LIGHT:return{primary:#3B82F6,background:#F9FAFB,...}caseThemeType.DARK:return{primary:#60A5FA,background:#111827,...}caseThemeType.EYE_CARE:return{primary:#059669,background:#ECFDF5,...}default:return{primary:#D4A574,background:#FBF8F3,...}}}switch-case 返回完整的 ThemeColors 对象——每个主题返回 8 个颜色值。default 回退到暖金主题——保证即使传入无效主题值也不会崩溃。为什么用工厂函数而不是对象映射// 方案 A当前工厂函数functiongetThemeColors(theme:ThemeType):ThemeColors{switch(theme){...}}// 方案 B对象映射constTHEME_MAP:RecordThemeType,ThemeColors{[ThemeType.CAPSULE]:{...},[ThemeType.LIGHT]:{...},...}方案 A 更好——因为工厂函数可以加逻辑。未来如果需要根据时间自动切换白天亮色/晚上暗色工厂函数里加new Date().getHours()判断即可。对象映射是纯数据没有扩展空间。六、主题切换的持久化写入 PreferencesprivateasyncsaveTheme(theme:ThemeType):Promisevoid{try{constprefsawaitpreferences.getPreferences(this.context!,settings)awaitprefs.put(theme,theme)awaitprefs.flush()this.currentThemetheme}catch(e){// 静默失败——使用默认主题}}先 flush 再更新 State——保证数据持久化成功后才切换 UI。如果顺序反过来先更新 State 再 flush用户看到主题切换了但 Preferences 写入失败——下次重启又回到默认主题。读取 PreferencesprivateasyncloadTheme():Promisevoid{try{constprefsawaitpreferences.getPreferences(this.context!,settings)constthemeawaitprefs.get(theme,ThemeType.CAPSULE)this.currentThemethemeasThemeType}catch(e){// 使用默认主题}}默认值ThemeType.CAPSULE——第一次打开 App 时是暖金主题。as ThemeType类型断言——Preferences 返回的是PreferencesDataValue需要断言为 ThemeType。主题切换的刷新链路用户点击主题标签 → saveTheme() → prefs.put(theme, theme) 持久化 → prefs.flush() 写入文件 → this.currentTheme theme 更新 State → build 重新执行 触发重渲染 → getColors() 返回新主题 所有颜色更新 → 所有 UI 元素刷新 背景/文字/按钮全部变色从用户点击到 UI 刷新——四步。关键是this.currentTheme theme这一步——它是触发 build 重新执行的开关。没有这步即使 Preferences 写入成功UI 也不会变。七、颜色传递的架构模式模式一State getColors()Index 页面StatecurrentTheme:ThemeTypeThemeType.CAPSULEprivategetColors():ThemeColors{returngetThemeColors(this.currentTheme)}build(){Column(){// 所有颜色通过 this.getColors() 获取}}Index 页面直接持有 currentTheme State——切换主题后 build 自动刷新。getColors() 是快捷方法避免每次写getThemeColors(this.currentTheme)。模式二colors 参数传递子组件// Index 传 colors 给子组件MoodPicker({colors:this.getColors(),...})// 子组件用 this.colors 渲染Componentstruct MoodPicker{colors:ThemeColorsgetThemeColors(ThemeType.CAPSULE)build(){Column(){// 用 this.colors.primary 等}}}子组件不持有 currentTheme——只接收 colors 参数。这保证了子组件的独立性——它不需要知道当前是什么主题只用传进来的颜色渲染。两种模式的对比特性模式一Index模式二子组件持有 currentTheme是否切换主题后自动刷新是是父组件传新 colors依赖关系依赖 ThemeType依赖 ThemeColors可复用性低和 ThemeType 耦合高只依赖颜色对象子组件用模式二——高可复用性。MoodPicker 在封印 Tab 和详情页都能用——只要传不同的 colors 参数即可。八、透明度后缀的系统化所有使用透明度的位置位置后缀透明度用途统计卡片背景无100%surface 色本身笔记本图标背景‘15’15%轻度背景MoodPicker 选中‘20’20%中度背景快捷操作图标‘12’12%轻度背景标签输入背景‘15’15%轻度背景可开启卡片‘#FFF8E1’固定暖黄背景透明度的设计规则透明度适用场景视觉效果10%~15%图标背景、输入框几乎不可见微弱底色15%~20%选中状态、活跃状态可感知但不抢内容20%~30%强调、特殊状态明显引导注意力30%不使用太深影响内容可读性透明度是主题系统里最容易被忽视的细节。它控制颜色的强度——同一个 primary 色10% 透明度是微弱底色20% 是选中状态100% 是按钮实色。三个层次让同一颜色在不同场景下有不同的视觉重量。九、Spacing 和 BorderRadius 常量Spacing 间距系统exportconstSpacing{xs:4,// 最小间距sm:8,// 小间距md:16,// 标准间距lg:24,// 大间距xl:32// 最大间距}BorderRadius 圆角系统exportconstBorderRadius{sm:6,// 小圆角进度条、标签md:12,// 中圆角卡片、输入框lg:16,// 大圆角按钮、区块full:999// 全圆角胶囊形}FontSize 字号系统exportconstFontSize{xs:10,// 辅助文字sm:12,// 说明文字md:14,// 正文lg:16,// 小标题xl:20,// 标题xxl:24,// 大标题hero:32// 数字展示}三套常量Spacing/BorderRadius/FontSize和颜色系统平行——共同构成 App 的设计语言。颜色控制看起来是什么感觉间距控制元素之间多远圆角控制多圆润字号控制多大。十、主题系统的设计原则1. 颜色不硬编码所有颜色通过this.getColors()或colors.xxx获取——不在组件里写死#6C5CE7或#FFFFFF。这让主题切换成为可能——改一个变量全局变色。2. 透明度可预测所有浅色背景都用主色 透明度后缀——不随机选色。用户看到primary 15就知道这是15% 透明度的主色不需要看渲染结果。3. 持久化优先主题选择写入 Preferences——重启后保留。这是用户个性化设置的基本要求——没有人想每次打开 App 都重新选主题。4. 默认值兜底所有主题相关的方法都有 default/fallback——getThemeColors的 default 返回暖金主题loadTheme的 catch 使用默认值。任何异常都不会导致 App 崩溃或显示异常。5. 单一数据源currentTheme是唯一的主题状态——所有组件通过它或它派生的 colors获取颜色。不存在这个组件用 currentTheme那个组件用 localStorage的混乱情况。