
鸿蒙5 主题切换:深色/浅色模式的全栈实现方案
全局主题系统设计
核心架构
主题系统 =
主题定义层(资源目录+样式变量) +
状态管理层(AppStorage) +
动态响应层(媒体查询+组件监听) +
切换控制层(主题切换器)
主题资源目录结构
resources/
├── base/
│ ├── element/ # 基础元素
│ ├── media/ # 基础图片资源
│ └── profile/ # 基础样式配置
│
├── dark/ # 深色模式资源
│ ├── element/
│ ├── media/
│ └── profile/
│
└── light/ # 浅色模式资源
├── element/
├── media/
└── profile/
基础实现方案
- 主题状态管理
// 在AppScope定义全局主题状态
AppStorage.SetOrCreate(‘themeMode’, ‘light’) // 默认浅色模式
// 系统深色模式检测
mediaQuery.matchMedia(‘(prefers-color-scheme: dark)’)
.on(‘change’, event => {
if (event.matches) {
AppStorage.Set(‘themeMode’, ‘dark’) // 系统切换到深色模式
} else {
AppStorage.Set(‘themeMode’, ‘light’)
}
})
2. 主题资源加载器
class ThemeResource {
// 获取颜色资源
static color(name: string): ResourceColor {
const mode = AppStorage.Get(‘themeMode’) as string
return $r(app.color.${name}_${mode}
)
}
// 获取图片资源
static image(name: string): Resource {
const mode = AppStorage.Get(‘themeMode’) as string
return $r(app.media.${name}_${mode}
)
}
// 获取尺寸资源
static dimension(name: string): number | string {
const mode = AppStorage.Get(‘themeMode’) as string
const value = $r(app.float.${name}_${mode}
) as number
return value > 0 ? ${value}vp
: value
}
}
// 注册为全局对象
globalThis.ThemeResource = ThemeResource
3. 定义主题资源文件
resources/base/element/color.json
{
“color”: [
{
“name”: “primary_color_light”,
“value”: “#2196F3”
},
{
“name”: “primary_color_dark”,
“value”: “#64B5F6”
},
{
“name”: “background_light”,
“value”: “#FFFFFF”
},
{
“name”: “background_dark”,
“value”: “#121212”
}
]
}
resources/dark/profile/theme.json
{
“float”: {
“card_radius”: 8,
“button_height”: 48
},
“media”: {
“main_bg_dark”: “dark_background.jpg”
}
}
动态主题应用组件
- 主题感知组件基类
@Component
export struct ThemeAwareComponent {
@StorageLink(‘themeMode’) @Watch(‘onThemeChange’) themeMode: string = ‘light’
private isDarkMode: boolean = false
onThemeChange() {
this.isDarkMode = this.themeMode === ‘dark’
// 主题变化时的自定义处理逻辑
this.themeChanged(this.isDarkMode)
}
// 子类重写的方法
themeChanged(isDark: boolean) {
// 留给子类实现具体的主题响应逻辑
}
build() {
// 由具体组件实现构建
}
}
2. 主题化按钮组件
@Component
struct ThemeButton extends ThemeAwareComponent {
@Prop text: string = ‘’
@State private pressed: boolean = false
themeChanged(isDark: boolean) {
// 主题切换时更新按钮样式
animateTo({ duration: 200 }, () => {
this.updateButtonStyle()
})
}
private updateButtonStyle() {
const bgColor = this.isDarkMode ?
ThemeResource.color(‘button_bg_dark’) :
ThemeResource.color(‘button_bg_light’)
return {
backgroundColor: bgColor,
opacity: this.pressed ? 0.8 : 1
}
}
build() {
const style = this.updateButtonStyle()
Button(this.text)
.backgroundColor(style.backgroundColor)
.opacity(style.opacity)
.height(ThemeResource.dimension('button_height'))
.fontColor(this.isDarkMode ? '#FFFFFF' : '#000000')
.fontSize(16)
.onTouch(event => {
if (event.type === TouchType.Down) {
this.pressed = true
} else if (event.type === TouchType.Up) {
this.pressed = false
}
})
}
}
高级主题切换控制器
@Entry
@Component
struct ThemeSwitchController {
@StorageLink(‘themeMode’) themeMode: string = ‘light’
@State showAdvancedSettings: boolean = false
// 主题选项
private themeOptions = [
{ value: ‘light’, label: ‘浅色模式’ },
{ value: ‘dark’, label: ‘深色模式’ },
{ value: ‘auto’, label: ‘自动切换’ }
]
build() {
Column() {
// 模式选择器
Picker({ options: this.themeOptions })
.selected(this.themeMode === ‘auto’ ? 2 : this.themeMode === ‘dark’ ? 1 : 0)
.onChange(index => {
this.handleThemeChange(this.themeOptions[index].value)
})
// 高级设置区域
if (this.showAdvancedSettings) {
Column() {
// 自定义主题颜色
ColorPicker()
.onChange(color => {
this.saveCustomColor('primary', color)
})
// 动态对比度调节
Slider({ min: 1, max: 3, step: 0.1 })
.onChange(value => {
AppStorage.SetOrCreate('contrastRatio', value)
})
}
.animation({ duration: 300, curve: Curve.Ease })
}
// 展开/收起按钮
Button(this.showAdvancedSettings ? '收起设置' : '高级设置')
.onClick(() => {
this.showAdvancedSettings = !this.showAdvancedSettings
})
}
}
// 主题切换处理
private handleThemeChange(mode: string) {
if (mode === ‘auto’) {
// 注册系统主题变化监听
this.registerSystemThemeListener()
} else {
// 取消监听,应用指定主题
this.unregisterSystemThemeListener()
AppStorage.Set(‘themeMode’, mode)
}
}
// 保存自定义颜色
private saveCustomColor(type: string, color: string) {
// 生成新资源ID
const resId = custom_${type}_${new Date().getTime()}
// 添加到主题资源
appendResource({
color: {
name: `${type}_color`,
value: {
light: type === 'primary' ? color : '#FFFFFF',
dark: type === 'primary' ? lighten(color, 0.2) : '#121212'
}
}
})
// 更新当前主题资源
ThemeResource.update()
}
}
主题驱动式页面布局
@Entry
@Component
struct NewsReaderPage {
@StorageLink(‘themeMode’) themeMode: string
@StorageLink(‘contrastRatio’) contrastRatio: number = 1
build() {
Column() {
// 顶部导航
Row() {
Image(ThemeResource.image(‘app_logo’))
.width(120)
.height(48)
ThemeSwitchButton() // 主题切换按钮组件
}
.padding(16)
// 内容区域
Scroll() {
// 响应式内容卡片
ForEach(this.newsItems, (item) => {
NewsCard({
title: item.title,
summary: item.summary,
image: item.image
})
})
}
.backgroundColor(ThemeResource.color('background'))
.contrast(this.contrastRatio) // 应用对比度设置
}
.width('100%')
.height('100%')
}
}
@Component
struct NewsCard extends ThemeAwareComponent {
@Prop title: string
@Prop summary: string
@Prop image: string
build() {
Column() {
Text(this.title)
.fontSize(18)
.fontColor(ThemeResource.color(‘text_primary’))
.fontWeight(this.isDarkMode ? FontWeight.Normal : FontWeight.Bold)
Text(this.summary)
.fontSize(14)
.fontColor(ThemeResource.color('text_secondary'))
.margin({ top: 8 })
Image(this.image)
.width('100%')
.aspectRatio(1.78)
.margin({ top: 12 })
}
.padding(16)
.backgroundColor(ThemeResource.color('card_background'))
.borderRadius(ThemeResource.dimension('card_radius'))
.shadow({
radius: this.isDarkMode ? 8 : 4,
color: this.isDarkMode ? '#000000' : '#00000022'
})
.margin({ bottom: 16 })
}
}
性能优化方案
- 资源按需加载
class LazyThemeResource {
private static loadedThemes: Set<string> = new Set()
static loadTheme(mode: string) {
if (!this.loadedThemes.has(mode)) {
// 加载主题资源
import(./resources/${mode}/theme
).then(module => {
ThemeResource.registerTheme(mode, module.resources)
this.loadedThemes.add(mode)
})
}
}
// 组件内预加载
aboutToAppear() {
const currentMode = AppStorage.Get(‘themeMode’)
const oppositeMode = currentMode === ‘light’ ? ‘dark’ : ‘light’
LazyThemeResource.loadTheme(oppositeMode)
}
}
2. 主题切换动画优化
function applyThemeTransition() {
// 设置全局过渡效果
const transition = transitionController.createTransition()
transition
.addAnimation(‘backgroundColor’, { duration: 400, curve: Curve.EaseInOut })
.addAnimation(‘fontColor’, { duration: 350, delay: 50 })
.addAnimation(‘borderColor’, { duration: 300, curve: Curve.Spring })
// 应用到页面
getRouterPage().applyTransition(transition)
}
// 主题切换事件处理
AppStorage.on(‘themeMode’, (newValue, oldValue) => {
if (newValue !== oldValue) {
applyThemeTransition()
}
})
3. 主题资源缓存管理
class ThemeCache {
private static cache: Map<string, any> = new Map()
static get(key: string): any {
if (!this.cache.has(key)) {
const value = ThemeResource.load(key)
this.cache.set(key, value)
}
return this.cache.get(key)
}
static update(key: string) {
if (this.cache.has(key)) {
const value = ThemeResource.load(key)
this.cache.set(key, value)
}
}
static clear() {
this.cache.clear()
}
}
// 低内存时清除缓存
memoryMonitor.on(‘lowMemory’, () => {
ThemeCache.clear()
})
企业级主题解决方案
主题配置中心对接
class ThemeServer {
static async fetchThemeConfig() {
// 获取云端主题配置
const response = await http.get(‘https://theme.example.com/config’)
if (response.code === 200) {
// 解析并应用主题
const { primaryColor, secondaryColor, fontFamily } = response.data
// 生成资源定义
const themeDef = {
color: {
primary: { light: primaryColor, dark: lighten(primaryColor, 0.3) },
secondary: { light: secondaryColor, dark: lighten(secondaryColor, 0.2) }
},
font: {
main: { light: fontFamily, dark: fontFamily }
}
}
// 应用到系统
ThemeResource.applyRemoteTheme('custom_theme', themeDef)
}
}
static async applyThemeToServer(themeName: string) {
// 向服务器同步当前主题选择
await http.post(‘https://api.example.com/user/preference’, {
theme: themeName
})
}
}
// 用户首次启动时
AppStorage.on(‘firstLaunch’, async () => {
await ThemeServer.fetchThemeConfig()
// 检查用户是否在服务器有偏好设置
const userPrefs = await getUserPreferences()
if (userPrefs.theme) {
AppStorage.Set(‘themeMode’, userPrefs.theme)
}
})
AB测试主题方案
function applyABTestTheme() {
// 随机分配主题
const themeVariant = Math.random() > 0.5 ? ‘experimental’ : ‘standard’
// 从资源服务获取变体配置
ThemeResource.loadVariant(themeVariant).then(() => {
// 应用变体资源
ThemeResource.applyVariant(themeVariant)
// 记录用户分配
analytics.track('theme_variant_assigned', {
variant: themeVariant
})
})
}
主题开发最佳实践
- 主题安全颜色公式
深色模式颜色 = lighten(浅色模式颜色, 10%)
浅色模式颜色 = darken(深色模式颜色, 15%) - 无障碍设计规则
// 检查颜色对比度
function checkColorContrast(fg: string, bg: string): boolean {
const contrast = calculateContrastRatio(fg, bg)
return contrast >= (this.isDarkMode ? 4.5 : 4.5)
}
// 自动调整文本颜色
Text(‘无障碍文本’)
.fontColor(theme => {
const bg = ThemeResource.color(‘background’)
const defaultColor = ThemeResource.color(‘text’)
if (!checkColorContrast(defaultColor, bg)) {
// 使用备用高对比度颜色
return theme.resolve('high_contrast_text')
}
return defaultColor
})
总结与项目实践
在鸿蒙5应用中实现完整的主题系统需要:
资源分层管理:按主题组织颜色、尺寸、图片等资源
状态全局化:使用AppStorage管理主题状态
动态响应机制:组件实时响应主题变化
过渡动画优化:确保主题切换流畅自然
云端集成方案:实现主题配置远程管理
以下是一个完整的主题切换场景示例:
@Entry
@Component
struct ThemeShowcase {
build() {
Column() {
// 主题展示区域
ThemePreviewPanel()
// 主题控制面板
ThemeControllerPanel()
}
.themeStyles() // 应用全局主题样式
}
}
@Component
struct ThemePreviewPanel {
build() {
Column() {
// 展示各种主题化组件
ThemeButton({ text: ‘主题化按钮’ })
ThemeCard()
ThemeText(‘主题文本示例’)
}
}
}
@Component
struct ThemeControllerPanel {
@StorageLink(‘themeMode’) currentTheme: string
build() {
Row() {
// 深色模式切换
Toggle({ type: ToggleType.Checkbox })
.isOn(this.currentTheme === ‘dark’)
.onChange(checked => {
AppStorage.Set(‘themeMode’, checked ? ‘dark’ : ‘light’)
})
// 更多主题选项
ThemePaletteSelector()
}
}
}
// 全局主题样式定义
@Styles function themeStyles() {
.width(‘100%’)
.height(‘100%’)
.backgroundColor(ThemeResource.color(‘background’))
.fontFamily(ThemeResource.font(‘main’))
.opacityTransition({ duration: 300 })
}
通过以上方案,开发者可以构建出专业级的主题系统,使应用能够完美适配深色/浅色模式,同时为后续的主题定制和个性化功能提供坚实的基础架构。
