及时做APP开发实战(十)-深浅色主题适配实现
本文将详细介绍如何在HarmonyOS NEXT中实现深浅色主题适配,让应用跟随系统主题动态切换。
一、需求背景
1.1 功能需求
现代应用需要支持深浅色主题切换,提供更好的用户体验:
┌─────────────────────────────────────┐ │ 主题适配需求 │ ├─────────────────────────────────────┤ │ • 跟随系统主题自动切换 │ │ • 动态更新所有UI组件颜色 │ │ • 保持主题切换的流畅性 │ │ • 抽离颜色常量便于维护 │ └─────────────────────────────────────┘1.2 技术方案
HarmonyOS提供Configuration.colorMode监听系统主题变化:
| 模式 | 说明 |
|---|---|
| COLOR_MODE_LIGHT | 浅色模式 |
| COLOR_MODE_DARK | 深色模式 |
| COLOR_MODE_NOT_SET | 跟随系统 |
二、主题颜色设计
2.1 颜色体系规划
【图1:颜色体系】
┌─────────────────────────────────────┐ │ 颜色分类 │ ├─────────────────────────────────────┤ │ 背景色 │ │ - 页面背景 background │ │ - 卡片背景 cardBackground │ │ │ │ 文本色 │ │ - 主文本 textPrimary │ │ - 次要文本 textSecondary │ │ - 提示文本 textHint │ │ │ │ 主题色 │ │ - 主色 primary │ │ - 主色浅 primaryLight │ │ - 成功色 success │ │ - 警告色 warning │ │ │ │ 边框色 │ │ - 边框 border │ │ - 分割线 divider │ └─────────────────────────────────────┘2.2 ThemeColors接口定义
exportinterfaceThemeColors{// 背景色background:string;cardBackground:string;// 文本色textPrimary:string;textSecondary:string;textHint:string;// 主题色primary:string;primaryLight:string;success:string;warning:string;// 边框色border:string;divider:string;}2.3 浅色主题配置
constLightTheme:ThemeColors={background:'#F5F5F5',cardBackground:'#FFFFFF',textPrimary:'#333333',textSecondary:'#666666',textHint:'#999999',primary:'#FF6B6B',primaryLight:'#FFE5E5',success:'#4CAF50',warning:'#FF9800',border:'#E0E0E0',divider:'#EEEEEE'};2.4 深色主题配置
constDarkTheme:ThemeColors={background:'#1A1A1A',cardBackground:'#2D2D2D',textPrimary:'#FFFFFF',textSecondary:'#B0B0B0',textHint:'#808080',primary:'#FF6B6B',primaryLight:'#3D2D2D',success:'#66BB6A',warning:'#FFB74D',border:'#404040',divider:'#333333'};三、ThemeUtil工具类实现
3.1 类结构设计
┌─────────────────────────────────────┐ │ ThemeUtil │ ├─────────────────────────────────────┤ │ - instance: ThemeUtil │ │ - currentTheme: ThemeColors │ │ - isDarkMode: boolean │ ├─────────────────────────────────────┤ │ + getInstance(): ThemeUtil │ │ + updateTheme(config): void │ │ + getColors(): ThemeColors │ │ + isDark(): boolean │ │ + getBackgroundColor(): string │ │ + getCardBackground(): string │ │ + getTextPrimary(): string │ │ + ...其他getter方法 │ └─────────────────────────────────────┘3.2 完整实现代码
importConfigurationConstantfrom'@ohos.app.ability.ConfigurationConstant';import{Configuration}from'@kit.AbilityKit';// 主题颜色接口(同上)exportinterfaceThemeColors{...}// 主题配置(同上)constLightTheme:ThemeColors={...};constDarkTheme:ThemeColors={...};exportclassThemeUtil{privatestaticinstance:ThemeUtil|null=null;privatecurrentTheme:ThemeColors=LightTheme;privateisDarkMode:boolean=false;// 私有构造函数privateconstructor(){}// 获取单例publicstaticgetInstance():ThemeUtil{if(!ThemeUtil.instance){ThemeUtil.instance=newThemeUtil();}returnThemeUtil.instance;}/** * 根据配置更新主题 */publicupdateTheme(configuration:Configuration):void{try{constcolorMode=configuration.colorMode;this.isDarkMode=colorMode===ConfigurationConstant.ColorMode.COLOR_MODE_DARK;this.currentTheme=this.isDarkMode?DarkTheme:LightTheme;console.info(`[ThemeUtil] 主题已更新:${this.isDarkMode?'深色':'浅色'}`);}catch(err){consterrorMsg=errinstanceofError?err.message:String(err);console.error(`[ThemeUtil] 更新主题失败:${errorMsg}`);}}/** * 获取当前主题颜色 */publicgetColors():ThemeColors{returnthis.currentTheme;}/** * 是否深色模式 */publicisDark():boolean{returnthis.isDarkMode;}// 其他getter方法...publicgetBackgroundColor():string{returnthis.currentTheme.background;}publicgetCardBackground():string{returnthis.currentTheme.cardBackground;}publicgetTextPrimary():string{returnthis.currentTheme.textPrimary;}publicgetTextSecondary():string{returnthis.currentTheme.textSecondary;}publicgetTextHint():string{returnthis.currentTheme.textHint;}publicgetPrimary():string{returnthis.currentTheme.primary;}publicgetSuccess():string{returnthis.currentTheme.success;}publicgetBorder():string{returnthis.currentTheme.border;}}四、Ability中监听主题变化
4.1 在EntryAbility中添加监听
import{AbilityConstant,ConfigurationConstant,UIAbility,Want,Configuration}from'@kit.AbilityKit';import{ThemeUtil}from'../utils/ThemeUtil';exportdefaultclassEntryAbilityextendsUIAbility{onCreate(want:Want,launchParam:AbilityConstant.LaunchParam):void{// 设置跟随系统主题try{this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);}catch(err){hilog.error(DOMAIN,'testTag','Failed to set colorMode');}}/** * 监听系统配置变化(包括主题变化) */onConfigurationUpdate(newConfig:Configuration):void{hilog.info(DOMAIN,'testTag','Configuration updated');// 更新主题try{ThemeUtil.getInstance().updateTheme(newConfig);}catch(err){hilog.error(DOMAIN,'testTag','Failed to update theme');}}}4.2 配置变化流程
【图2:主题切换流程】
┌─────────────────────────────────────┐ │ 主题切换流程 │ ├─────────────────────────────────────┤ │ 用户切换系统主题 │ │ ↓ │ │ 系统发送Configuration变化事件 │ │ ↓ │ │ EntryAbility.onConfigurationUpdate │ │ ↓ │ │ ThemeUtil.updateTheme() │ │ ↓ │ │ 更新currentTheme和isDarkMode │ │ ↓ │ │ UI组件响应式更新 │ └─────────────────────────────────────┘五、页面中使用主题
5.1 定义主题状态变量
import{ThemeUtil,ThemeColors}from'../utils/ThemeUtil';@Entry@Componentstruct PomodoroPage{// 主题状态@StatethemeColors:ThemeColors=ThemeUtil.getInstance().getColors();@StateisDarkMode:boolean=false;// 工具类privatethemeUtil:ThemeUtil=ThemeUtil.getInstance();// 生命周期asyncaboutToAppear(){// 初始化主题this.themeColors=this.themeUtil.getColors();this.isDarkMode=this.themeUtil.isDark();}}5.2 在UI组件中应用主题颜色
@BuilderTopBar(){Row(){Text('🍅 番茄钟').fontSize(24).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')}.width('100%').height(56).backgroundColor(this.isBreak?this.themeColors.success:this.themeColors.primary)}@BuilderTimerSection(){Column(){// 进度条Progress({value:this.getProgress(),total:100,type:ProgressType.Ring}).color(this.isBreak?this.themeColors.success:this.themeColors.primary).backgroundColor(this.themeColors.border)// 时间显示Text(this.formatTime(this.timeLeft)).fontSize(64).fontWeight(FontWeight.Bold).fontColor(this.themeColors.textPrimary)}.width('100%').backgroundColor(this.themeColors.background)}@BuilderPomodoroStats(){Column(){Row(){Column(){Text(this.pomodoroCount.toString()).fontSize(28).fontWeight(FontWeight.Bold).fontColor(this.themeColors.primary)Text('今日完成').fontSize(12).fontColor(this.themeColors.textSecondary)}}}.backgroundColor(this.themeColors.cardBackground)}5.3 半模态弹窗主题适配
.bindSheet($$this.showTimePicker,this.TimePickerSheet(),{height:300,backgroundColor:this.isDarkMode?'#2D2D2D':Color.White,dragBar:true,onWillDismiss:()=>{this.showTimePicker=false;}})六、颜色对比效果
6.1 浅色主题效果
【图3:浅色主题】
6.2 深色主题效果
【图4:深色主题】
七、最佳实践
7.1 颜色使用规范
// ✅ 推荐:使用主题颜色.backgroundColor(this.themeColors.background).fontColor(this.themeColors.textPrimary)// ❌ 不推荐:硬编码颜色.backgroundColor('#F5F5F5').fontColor('#333333')7.2 状态变量设计
// ✅ 推荐:使用@State自动响应@StatethemeColors:ThemeColors=ThemeUtil.getInstance().getColors();// ❌ 不推荐:普通变量不会触发更新privatethemeColors:ThemeColors=ThemeUtil.getInstance().getColors();7.3 主题颜色扩展
// 扩展主题颜色接口exportinterfaceThemeColors{// 基础颜色...// 扩展颜色error:string;// 错误色info:string;// 信息色disabled:string;// 禁用色}// 在主题配置中添加constLightTheme:ThemeColors={// 基础颜色...error:'#F44336',info:'#2196F3',disabled:'#BDBDBD'};八、总结
本文实现了番茄钟应用的深浅色主题适配:
- 颜色体系设计:定义完整的ThemeColors接口
- 工具类封装:ThemeUtil单例类管理主题状态
- 系统监听:在Ability中监听配置变化
- 响应式更新:使用@State实现UI自动更新
主题适配是现代应用的基本要求,合理的设计可以让应用在各种环境下都有良好的显示效果。