HarmonyOS 6.1 深色模式与主题适配实战:三种实现方案的真实对比

📅 2026/7/9 3:39:47 👁️ 阅读次数 📝 编程学习
HarmonyOS 6.1 深色模式与主题适配实战:三种实现方案的真实对比

基于 ThemeDemo 实战验证,覆盖 @State 条件函数、AppStorage 全局主题、资源限定词三种方案,附带完整可运行代码


写在前面

做深色模式这事儿,很多人觉得"不就是换个背景色嘛"。天真。你把背景色改成 #121212,结果文字还是 #333333——恭喜,深色背景配深色文字,用户什么都看不见。

深色模式的核心不是改颜色,是保证所有颜色都有对应的深色版本,并且切换时不能有遗漏。这篇文章把三种实现方案都走了一遍,坑多得你不敢信。


一、方案一:@State + 条件函数(Demo 采用)

最直觉的方案——用@State isDark: boolean驱动,所有颜色通过函数返回:

@StateisDark:boolean=falseprivatebgColor():string{returnthis.isDark?'#121212':'#ffffff'}privatecardBg():string{returnthis.isDark?'#1e1e1e':'#f8f8f8'}privatetextColor():string{returnthis.isDark?'#e0e0e0':'#333333'}

UI 里所有硬编码颜色替换成函数调用:

Column(){Text('标题').fontColor(this.textColor()).backgroundColor(this.cardBg())}.backgroundColor(this.bgColor())

优点

  • 实现简单,不需要额外架构
  • 切换即时生效,@State 变了 UI 自动刷新
  • 方便调试,isDark 一眼就知道当前主题

缺点

  • 颜色散落在各组件的函数里,不好统一管理
  • 新增颜色需要在每个组件里加函数
  • 页面多了,函数重复率很高

适合场景

小项目、单页面 Demo、快速验证。颜色不多的时候这个方案够用。


二、方案二:AppStorage + @StorageLink 全局主题

项目稍微大一点,就需要全局管理主题色。把颜色存到 AppStorage,所有组件通过 @StorageLink 监听:

// 初始化(EntryAbility 或首组件 aboutToAppear)AppStorage.setOrCreate('themeBgColor','#ffffff')AppStorage.setOrCreate('themeCardBg','#f8f8f8')AppStorage.setOrCreate('themeTextColor','#333333')AppStorage.setOrCreate('themePrimaryColor','#1a73e8')AppStorage.setOrCreate('isDarkMode',false)// 各组件@StorageLink('themeBgColor')bgColor:string='#ffffff'@StorageLink('themeTextColor')textColor:string='#333333'

切换主题时,统一修改 AppStorage:

toggleDarkMode():void{constisDark:boolean=!(AppStorage.get<boolean>('isDarkMode')??false)AppStorage.setOrCreate('isDarkMode',isDark)AppStorage.setOrCreate('themeBgColor',isDark?'#121212':'#ffffff')AppStorage.setOrCreate('themeCardBg',isDark?'#1e1e1e':'#f8f8f8')AppStorage.setOrCreate('themeTextColor',isDark?'#e0e0e0':'#333333')}

优点

  • 全局统一,一处修改处处生效
  • 跨页面共享主题状态
  • 新页面只需声明 @StorageLink,不用重复定义函数

缺点

  • @StorageLink 的 key 必须先 setOrCreate,否则崩溃
  • 主题色多了,初始化代码很长
  • 需要维护一个主题色常量表

适合场景

中大型项目、多页面共享主题。主题色在 20 个以内的项目用这个方案最合适。


三、方案三:资源限定词(resources/dark/)

HarmonyOS 支持通过资源目录自动适配深色模式:

resources/ base/ element/ color.json // 浅色颜色 media/ icon.png // 浅色图片 dark/ element/ color.json // 深色颜色 media/ icon.png // 深色图片

UI 代码里用$r()引用资源,系统自动根据当前模式选择:

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

优点

  • 系统原生支持,跟随系统深色模式自动切换
  • UI 代码不需要任何 isDark 判断
  • 图片资源也能自动适配

缺点

  • 只能跟随系统,不能应用内独立切换
  • 资源文件维护成本高,新增颜色要改两个 color.json
  • 调试不直观,看不出当前用的是哪个资源

适合场景

跟随系统深色模式的项目,不需要应用内手动切换主题。


四、三种方案对比

方案切换方式作用域跨页面图片适配维护成本适合规模
@State+函数应用内切换组件级需手动传递不支持小项目
AppStorage应用内切换全局自动不支持中项目
资源限定词跟随系统全局自动支持大项目

我的建议:中小项目用 AppStorage 方案,大项目用资源限定词 + AppStorage 组合。资源限定词处理跟随系统的自动适配,AppStorage 处理应用内手动切换。


五、主题色切换的实现

Demo 里还实现了主题色切换,用户可以选择不同的主色调:

@StateprimaryColor:string='#1a73e8'Row({space:8}){ForEach(['#1a73e8','#e65100','#34a853','#9c27b0','#d32f2f'],(color:string)=>{Row().width(36).height(36).borderRadius(18).backgroundColor(color).border({width:this.primaryColor===color?3:0,color:'#333333'}).onClick(()=>{this.primaryColor=color})},(color:string,index:number)=>index.toString())}

深色模式下,主题色需要用更亮的变体,否则在深色背景上对比度不够:

privatelightColors:string[]=['#1a73e8','#e65100','#34a853','#9c27b0','#d32f2f']privatedarkColors:string[]=['#64b5f6','#ff9800','#81c784','#ce93d8','#ef5350']


六、踩坑合集

坑一:只改背景色不改文字色

这是最常见的低级错误。改了背景色忘了改文字色,深色背景上深色文字,用户什么都看不见。检查清单:背景色、文字色、副文字色、分割线色、卡片色,全都要改。

坑二:阴影在深色模式下不可见

浅色模式下shadow({ radius: 8, color: '#1a000000' })看着挺好的,深色模式下透明度 10% 的黑色阴影在 #121212 背景上完全看不见。深色模式的阴影应该用更亮的颜色或者改用边框替代。

坑三:Web/Canvas 内容不跟随主题

WebView 加载的网页、Canvas 绘制的内容不会自动适配深色模式。网页需要自己实现深色主题(CSS prefers-color-scheme),Canvas 需要重新绘制。

坑四:系统弹窗不跟随应用主题

AlertDialog.show()等系统弹窗默认跟随系统主题,不受应用内 isDark 控制。如果你的应用是浅色模式但系统是深色,弹窗会显示深色。目前没有好的解决方案,只能用 CustomDialog 替代。

坑五:PNG 图标不好适配

PNG 图标在深色模式下需要准备两套,维护成本翻倍。建议用 SVG 格式,通过.fillColor()动态改颜色,一套资源搞定两种模式。


七、透明度调整

深色模式下,相同的透明度值视觉效果不一样。浅色背景上 10% 透明度的叠加色很淡,深色背景上同样的值可能完全看不出。

通用规律:

  • 深色背景上,主色的透明度需要提高(10%→20%)
  • 浅色背景上,辅助色的透明度可以降低(20%→10%)

Demo 里的做法是浅色色板和深色色板分开定义,避免运行时算透明度。


八、状态栏颜色

深色模式切换后,状态栏颜色也要跟着改。通过窗口实例设置:

constmainWindow=AppStorage.get<window.Window>('mainWindow')if(mainWindow){mainWindow.setWindowSystemBarProperties({statusBarColor:this.isDark?'#121212':'#ffffff',statusBarContentColor:this.isDark?'#e0e0e0':'#333333'})}

如果忘了改状态栏,深色页面顶着白色状态栏,割裂感极强。


写在最后

深色模式看着简单,做起来全是细节。颜色遗漏一个地方就穿帮,阴影不调就消失,图标不换就别扭。建议做深色模式的时候列一个颜色清单,改一个划一个,不然一定会漏。

另外,如果项目时间紧,优先保证核心页面的深色模式。设置页、关于页这些次要页面可以后做,但首页和主功能页面必须第一时间适配。