HarmonyOS7 主题切换别只做黑白两套:亮色、暗色和自定义主题完整方案

📅 2026/6/30 14:47:23
HarmonyOS7 主题切换别只做黑白两套:亮色、暗色和自定义主题完整方案
文章目录前言系统级暗色模式 vs App 内主题切换主题架构设计ThemeManager核心管理器ThemeProvider让页面感知主题变化主题持久化实战感受前言上次聊了 Design Token但 Token 只是基础值真正的挑战是怎么让整套 Token 在运行时切换。之前我做一个电商 App产品经理说要支持亮色、暗色、品牌色三套主题用户自己选我当时觉得这不难结果搞了两天才跑通。今天把完整方案整理出来。系统级暗色模式 vs App 内主题切换HarmonyOS 本身支持跟随系统的亮暗色切换你只要在resources/dark/目录下放一套暗色资源系统会自动帮你切换。这方案对简单场景够用。但如果你的 App 需要不跟随系统、用户自己选主题或者要支持品牌色这种自定义主题光靠系统资源切换就不够了。你需要一套自己的主题管理架构。我的方案是两条腿走路基础的亮色/暗色用系统资源做保证跟随系统时零成本自定义主题用 ThemeManager 做 Token 覆盖支持运行时动态切换。主题架构设计核心思路是这样的定义一个 Theme 接口每套主题就是这个接口的一个实现。ThemeManager 持有当前主题的引用组件通过 ThemeManager 拿 Token 值。// theme/ThemeTokens.ets - 主题 Token 接口定义exportinterfaceThemeTokens{// 品牌色colorPrimary:stringcolorPrimaryLight:stringcolorPrimaryDark:string// 语义色colorSuccess:stringcolorWarning:stringcolorError:string// 表面色colorSurface:stringcolorSurfaceVariant:stringcolorBackground:string// 文本色colorTextPrimary:stringcolorTextSecondary:stringcolorTextDisabled:stringcolorTextOnPrimary:string// 边框与分割线colorBorder:stringcolorDivider:string// 组件色colorInputBg:stringcolorCardBg:stringcolorNavBarBg:string}然后定义具体的主题实现// theme/LightTheme.etsimport{ThemeTokens}from./ThemeTokensexportconstLightTheme:ThemeTokens{colorPrimary:#0A59F7,colorPrimaryLight:#3B82F6,colorPrimaryDark:#0040C0,colorSuccess:#34C759,colorWarning:#FF9500,colorError:#FF3B30,colorSurface:#FFFFFF,colorSurfaceVariant:#F5F5F5,colorBackground:#FAFAFA,colorTextPrimary:#212121,colorTextSecondary:#757575,colorTextDisabled:#BDBDBD,colorTextOnPrimary:#FFFFFF,colorBorder:#EEEEEE,colorDivider:#EEEEEE,colorInputBg:#F5F5F5,colorCardBg:#FFFFFF,colorNavBarBg:#FFFFFF}// theme/DarkTheme.etsimport{ThemeTokens}from./ThemeTokensexportconstDarkTheme:ThemeTokens{colorPrimary:#5B9BFF,colorPrimaryLight:#7EB3FF,colorPrimaryDark:#3A7AE0,colorSuccess:#4CD964,colorWarning:#FFB340,colorError:#FF6961,colorSurface:#1C1C1E,colorSurfaceVariant:#2C2C2E,colorBackground:#000000,colorTextPrimary:#F5F5F5,colorTextSecondary:#ABABAB,colorTextDisabled:#5C5C5C,colorTextOnPrimary:#FFFFFF,colorBorder:#3A3A3C,colorDivider:#3A3A3C,colorInputBg:#2C2C2E,colorCardBg:#1C1C1E,colorNavBarBg:#1C1C1E}再来一套品牌色主题比如电商 App 的橙色主题// theme/BrandTheme.etsimport{ThemeTokens}from./ThemeTokensexportconstBrandTheme:ThemeTokens{colorPrimary:#FF6600,colorPrimaryLight:#FF8533,colorPrimaryDark:#CC5200,colorSuccess:#34C759,colorWarning:#FF9500,colorError:#FF3B30,colorSurface:#FFFFFF,colorSurfaceVariant:#FFF8F2,colorBackground:#FFFAF5,colorTextPrimary:#1A1A1A,colorTextSecondary:#666666,colorTextDisabled:#CCCCCC,colorTextOnPrimary:#FFFFFF,colorBorder:#FFE8D6,colorDivider:#FFE8D6,colorInputBg:#FFF5EB,colorCardBg:#FFFFFF,colorNavBarBg:#FFFFFF}ThemeManager核心管理器ThemeManager 要做几件事管理当前主题、支持切换、持久化用户选择、通知组件刷新。// theme/ThemeManager.etsimport{ThemeTokens}from./ThemeTokensimport{LightTheme}from./LightThemeimport{DarkTheme}from./DarkThemeimport{BrandTheme}from./BrandThemeexportenumThemeMode{LIGHTlight,DARKdark,BRANDbrand,SYSTEMsystem// 跟随系统}constPREF_KEY_THEMEuser_theme_modeObservedexportclassThemeManager{privatestaticinstance:ThemeManagerprivatecurrentTheme:ThemeTokensLightThemeprivatecurrentMode:ThemeModeThemeMode.SYSTEMprivateconstructor(){}staticgetInstance():ThemeManager{if(!ThemeManager.instance){ThemeManager.instancenewThemeManager()}returnThemeManager.instance}// 初始化读取用户上次的选择init(context:Context):void{try{constprefscontext.getSharedPreferencesSync(theme_prefs,0)constsavedModeprefs.getSync(PREF_KEY_THEME,ThemeMode.SYSTEM)asstringthis.applyMode(savedModeasThemeMode,context)}catch(e){// 首次启动跟随系统this.applyMode(ThemeMode.SYSTEM,context)}}// 切换主题switchTheme(mode:ThemeMode,context:Context):void{this.applyMode(mode,context)// 持久化用户选择constprefscontext.getSharedPreferencesSync(theme_prefs,0)prefs.putSync(PREF_KEY_THEME,mode)prefs.flush()}privateapplyMode(mode:ThemeMode,context:Context):void{this.currentModemodeswitch(mode){caseThemeMode.LIGHT:this.currentThemeLightThemebreakcaseThemeMode.DARK:this.currentThemeDarkThemebreakcaseThemeMode.BRAND:this.currentThemeBrandThemebreakcaseThemeMode.SYSTEM:// 根据系统配置决定亮暗色constconfigcontext.configif(config.colorMode1){// COLOR_MODE_DARKthis.currentThemeDarkTheme}else{this.currentThemeLightTheme}break}}// 获取当前主题 TokengetTheme():ThemeTokens{returnthis.currentTheme}// 获取当前模式getMode():ThemeMode{returnthis.currentMode}}ThemeProvider让页面感知主题变化ThemeManager 用了Observed配合ObjectLink就能让组件自动响应主题变化。我封装了一个 ThemeProvider 组件来做这件事// theme/ThemeProvider.etsimport{ThemeManager}from./ThemeManagerimport{ThemeTokens}from./ThemeTokensComponentexportstruct ThemeProvider{ObjectLinkthemeManager:ThemeManagerBuilderParamcontent:(theme:ThemeTokens)voidbuild(){Column(){this.content(this.themeManager.getTheme())}.width(100%).height(100%).backgroundColor(this.themeManager.getTheme().colorBackground)}}页面里这样用import{ThemeManager,ThemeMode}frommyteam/uikitimport{ThemeProvider}frommyteam/uikitEntryComponentstruct HomePage{ObjectLinkthemeManager:ThemeManagerThemeManager.getInstance()build(){ThemeProvider({themeManager:this.themeManager}){(theme:ThemeTokens){Column(){// 顶部导航栏Row(){Text(首页).fontSize(20).fontWeight(FontWeight.Bold).fontColor(theme.colorTextPrimary)Blank()// 主题切换按钮Text(this.themeManager.getMode()ThemeMode.DARK?☀:).fontSize(20).onClick((){constnextModethis.themeManager.getMode()ThemeMode.DARK?ThemeMode.LIGHT:ThemeMode.DARKthis.themeManager.switchTheme(nextMode,getContext(this))})}.width(100%).height(56).padding({left:16,right:16}).backgroundColor(theme.colorNavBarBg)// 内容区Scroll(){Column({space:12}){// 商品卡片Column(){Text(限时特价).fontSize(17).fontWeight(FontWeight.Bold).fontColor(theme.colorTextPrimary)Text(精选好物低至5折).fontSize(13).fontColor(theme.colorTextSecondary).margin({top:4})}.width(100%).padding(16).borderRadius(12).backgroundColor(theme.colorCardBg)// 操作按钮Button(立即抢购).width(100%).height(44).fontSize(15).fontColor(theme.colorTextOnPrimary).backgroundColor(theme.colorPrimary).borderRadius(10)}.padding(16)}.layoutWeight(1)}}}}}切换主题的时候整个页面的颜色都会即时刷新不用手动刷新任何东西——ObservedObjectLink帮我们搞定了响应式更新。主题持久化用户选了暗色主题杀掉 App 重新打开不能变回亮色。上面的 ThemeManager 已经做了持久化——用SharedPreferences存储用户选择App 启动时init()方法会读取上次的选择。在 EntryAbility 里初始化一下// EntryAbility.etsexportdefaultclassEntryAbilityextendsUIAbility{onCreate(want:Want,launchParam:AbilityConstant.LaunchParam):void{// 初始化主题ThemeManager.getInstance().init(this.context)}}实战感受这套方案跑通之后加新主题的成本极低——新建一个满足 ThemeTokens 接口的对象就行一行代码都不用改。我们后来加了护眼绿和春节红两套节日主题每套也就半小时搞定。有个小建议暗色主题的颜色别简单地把亮色反转。纯白文字配纯黑背景看着很累用 #F5F5F5 配 #1C1C1E 舒服得多。还有暗色模式下阴影效果会变弱需要用更亮的边框来替代阴影做层级区分。这些细节调好了暗色模式的体验才能上去。