本文档详细记录了如何在HarmonyOS应用中实现响应式多主题系统,包括手动切换主题和跟随系统主题变化的功能。

目录

  1. 项目结构
  2. 核心概念
  3. 实施步骤
  4. 代码详解
  5. 运行项目
  6. 常见问题
  7. ArkTS 语法注意事项

项目结构

在开始之前,让我们了解一下项目的文件结构:

demo/
├── entry/
│   └── src/
│       └── main/
│           └── ets/
│               ├── entryability/
│               │   └── EntryAbility.ets      # Ability入口,监听系统配置变化
│               ├── pages/
│               │   └── Index.ets            # 演示页面
│               ├── models/
│               │   └── ThemeModel.ets       # 主题模型核心逻辑
│               ├── utils/
│               │   └── Logger.ets           # 日志工具类
│               └── components/
│                   └── icons/
│                       └── ThemeToggleIcon.ets  # 主题切换图标组件

核心概念

1. 主题类型

系统支持三种主题模式:

  • light(亮色主题):固定使用亮色配色方案
  • dark(暗色主题):固定使用暗色配色方案
  • system(跟随系统):自动跟随系统主题变化

2. 状态管理

使用 AppStorage 进行全局状态管理:

  • app_theme_type:当前主题类型(light/dark/system)
  • app_is_dark_mode:是否为暗色模式(boolean)
  • app_theme_colors:当前主题颜色配置(JSON字符串)
  • currentColorMode:系统颜色模式(从系统配置同步)

3. 响应式更新

通过以下机制实现响应式更新:

  • EntryAbility.onConfigurationUpdate:监听系统配置变化
  • @StorageProp:在组件中自动响应 AppStorage 变化
  • ThemeModel:统一管理主题状态和切换逻辑

实施步骤

步骤1:创建工具类 Logger

文件路径entry/src/main/ets/utils/Logger.ets

作用:提供统一的日志输出接口,方便调试和问题排查。

操作

  1. entry/src/main/ets/ 目录下创建 utils 文件夹
  2. 创建 Logger.ets 文件
  3. 复制以下代码:
/*
 * 日志工具类
 */

enum LogLevel {
  DEBUG = 0,
  INFO = 1,
  WARN = 2,
  ERROR = 3
}

export class Logger {
  private static readonly LOG_LEVEL: LogLevel = LogLevel.INFO

  static info(tag: string, message: string): void {
    if (LogLevel.INFO >= Logger.LOG_LEVEL) {
      console.info(`[${tag}] ${message}`)
    }
  }

  static error(tag: string, message: string, error?: Error): void {
    if (LogLevel.ERROR >= Logger.LOG_LEVEL) {
      console.error(`[${tag}] ${message}`, error || '')
    }
  }
}

步骤2:创建主题模型 ThemeModel

文件路径entry/src/main/ets/models/ThemeModel.ets

作用:核心主题管理类,负责主题切换、状态同步和系统主题监听。

关键代码片段

import { Logger } from '../utils/Logger'
import { common, ConfigurationConstant } from '@kit.AbilityKit'

let uiContext: common.UIAbilityContext | null = null

export function setUIContext(context: common.UIAbilityContext): void {
  uiContext = context
}

export type ThemeType = 'light' | 'dark' | 'system'

export interface ThemeColors {
  primary: string
  background: string
  onBackground: string
  surface: string
  card: string
  // ... 更多颜色定义
}

// 亮色主题配色
export const LightTheme: ThemeColors = {
  primary: '#2E7D32',
  background: '#FFFFFF',
  onBackground: '#212121',
  surface: '#F5F5F5',
  card: '#FFFFFF',
  // ... 完整配色
}

// 暗色主题配色
export const DarkTheme: ThemeColors = {
  primary: '#4CAF50',
  background: '#121212',
  onBackground: '#FFFFFF',
  surface: '#1E1E1E',
  card: '#2C2C2C',
  // ... 完整配色
}

@Observed
export class ThemeModel {
  themeType: ThemeType = 'system'
  isDarkMode: boolean = false
  currentThemeColors: ThemeColors = LightTheme

  constructor() {
    console.log('[ThemeModel] 📦 构造函数开始');
    
    // 确保立即设置默认主题颜色,避免初始化异步导致 undefined
    this.currentThemeColors = LightTheme
    this.themeType = 'system'
    this.isDarkMode = false
    
    console.log('[ThemeModel] 📦 构造函数完成,currentThemeColors:', this.currentThemeColors);

    // 异步初始化主题
    this.initTheme()
  }

  // 初始化主题
  async initTheme(): Promise<void> {
    const storedThemeType = AppStorage.get<ThemeType>('app_theme_type')
    if (storedThemeType) {
      this.themeType = storedThemeType
    } else {
      Logger.info('ThemeModel', '首次启动,使用system模式跟随系统')
    }
    
    await this.applyTheme(this.themeType)
    this.syncToAppStorage()
    
    Logger.info('ThemeModel', `主题初始化完成: type=${this.themeType}, isDark=${this.isDarkMode}`)
  }

  // 切换主题
  async toggleTheme(): Promise<void> {
    const nextTheme: ThemeType = this.isDarkMode ? 'light' : 'dark'
    await this.setTheme(nextTheme)
  }

  // 设置主题
  async setTheme(theme: ThemeType): Promise<void> {
    this.themeType = theme
    await this.applyTheme(theme)
    this.syncToAppStorage()
  }

  // 应用主题
  private async applyTheme(theme: ThemeType): Promise<void> {
    Logger.info('ThemeModel', `应用主题: ${theme}`)
    
    switch (theme) {
      case 'light':
        this.isDarkMode = false
        this.currentThemeColors = LightTheme
        break
      case 'dark':
        this.isDarkMode = true
        this.currentThemeColors = DarkTheme
        break
      case 'system':
        await this.followSystemTheme()
        break
    }
  }

  // 跟随系统主题
  private async followSystemTheme(): Promise<void> {
    Logger.info('ThemeModel', '=== 开始跟随系统主题 ===')
    const colorMode = AppStorage.get<number>('currentColorMode')
    
    Logger.info('ThemeModel', `从AppStorage读取系统颜色模式: ${colorMode}`)
    
    if (colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK) {
      this.isDarkMode = true
      this.currentThemeColors = DarkTheme
      Logger.info('ThemeModel', '✓ 系统处于暗色模式')
    } else {
      this.isDarkMode = false
      this.currentThemeColors = LightTheme
      Logger.info('ThemeModel', '✓ 系统处于亮色模式')
    }
    
    Logger.info('ThemeModel', `✓ 跟随系统主题完成: ${this.isDarkMode ? '暗色' : '亮色'} (colorMode: ${colorMode})`)
  }

  // 同步到 AppStorage
  private syncToAppStorage(): void {
    AppStorage.setOrCreate('app_theme_type', this.themeType)
    AppStorage.setOrCreate('app_is_dark_mode', this.isDarkMode)
    AppStorage.setOrCreate('app_theme_colors', JSON.stringify(this.currentThemeColors))
    
    Logger.info('ThemeModel', `主题状态已同步到 AppStorage: ${this.themeType}, isDark: ${this.isDarkMode}`)
  }

  // 系统配置变化回调
  async onConfigurationChanged(): Promise<void> {
    if (this.themeType === 'system') {
      await this.followSystemTheme()
      this.syncToAppStorage()
    }
  }
  
  // 其他辅助方法...
}

关键说明

  • 构造函数中立即设置默认值:确保对象创建后立即可用,避免异步初始化导致的 undefined 错误
  • @Observed 装饰器使类可被观察
  • followSystemTheme() 从 AppStorage 读取系统颜色模式
  • syncToAppStorage() 确保所有页面都能响应主题变化

步骤3:创建主题切换图标组件

文件路径entry/src/main/ets/components/icons/ThemeToggleIcon.ets

@Component
export struct ThemeToggleIcon {
  @Prop iconWidth: number = 24
  @Prop iconHeight: number = 24
  @Prop color: string = '#000000'
  @Prop isDarkMode: boolean = false

  build() {
    Stack() {
      if (this.isDarkMode) {
        // 太阳图标
        Path()
          .width(this.iconWidth)
          .height(this.iconHeight)
          .commands('M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5...')
          .fill(this.color)
      } else {
        // 月亮图标
        Path()
          .width(this.iconWidth)
          .height(this.iconHeight)
          .commands('M12 3c-4.97 0-9 4.03-9 9...')
          .fill(this.color)
      }
    }
  }
}

步骤4:更新 EntryAbility

文件路径entry/src/main/ets/entryability/EntryAbility.ets

关键代码

import { AbilityConstant, ConfigurationConstant, UIAbility, Want, Configuration } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { Logger } from '../utils/Logger';
import { setUIContext } from '../models/ThemeModel';
import { ThemeModel } from '../models/ThemeModel';

export default class EntryAbility extends UIAbility {
  private themeModel: ThemeModel | null = null;

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
    setUIContext(this.context);

    const colorMode = this.context.config.colorMode;
    AppStorage.setOrCreate('currentColorMode', colorMode);
    Logger.info('EntryAbility', `当前系统颜色模式: ${colorMode}`);

    this.initializeTheme();
  }

  private async initializeTheme(): Promise<void> {
    try {
      this.themeModel = new ThemeModel();
      await this.themeModel.initTheme();
      Logger.info('EntryAbility', '主题模型初始化成功');
    } catch (error) {
      Logger.error('EntryAbility', '主题模型初始化失败:', error as Error);
    }
  }

  onConfigurationUpdate(newConfig: Configuration): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onConfigurationUpdate');
    try {
      if (newConfig.colorMode !== undefined) {
        Logger.info('EntryAbility', `系统颜色模式变化: ${newConfig.colorMode}`);
        AppStorage.setOrCreate('currentColorMode', newConfig.colorMode);
        Logger.info('EntryAbility', `已同步颜色模式到 AppStorage: ${newConfig.colorMode}`);
        if (this.themeModel) {
          this.themeModel.onConfigurationChanged();
        }
      }
    } catch (error) {
      Logger.error('EntryAbility', '处理配置变化失败:', error as Error);
    }
  }

  // ... 其他生命周期方法
}

关键说明

  • setUIContext() 将 UI 上下文传递给 ThemeModel
  • onConfigurationUpdate 是系统回调,响应系统配置变化
  • 必须将系统颜色模式同步到 AppStorage

步骤5:创建演示页面

文件路径entry/src/main/ets/pages/Index.ets

⚠️ 重要:ArkTS 语法限制

ArkTS 不支持 ES6 的 get 关键字定义 getter!必须使用普通方法。

import { ThemeModel, ThemeColors, LightTheme } from '../models/ThemeModel';
import { ThemeToggleIcon } from '../components/icons/ThemeToggleIcon';

@Entry
@Component
struct Index {
  @State themeModel: ThemeModel = new ThemeModel();
  @StorageProp('app_is_dark_mode') isDarkMode: boolean = false;
  @StorageProp('app_theme_colors') themeColorsJson: string = '';

  // ✅ 正确:使用普通方法而不是 get getter(ArkTS 语法要求)
  getCurrentThemeColors(): ThemeColors {
    console.log('[Index] 🔍 getCurrentThemeColors - 开始');
    
    // 优先从 AppStorage 读取
    if (this.themeColorsJson && this.themeColorsJson.trim() !== '') {
      try {
        const colors = JSON.parse(this.themeColorsJson) as ThemeColors;
        if (colors && colors.background) {
          console.log('[Index] ✅ 从 AppStorage 返回颜色');
          return colors;
        }
      } catch (e) {
        console.log('[Index] ⚠️ JSON 解析失败:', e);
      }
    }
    
    // 如果 AppStorage 中没有,尝试使用 themeModel 的颜色
    if (this.themeModel && 
        this.themeModel.currentThemeColors && 
        this.themeModel.currentThemeColors.background) {
      console.log('[Index] ✅ 从 themeModel 返回颜色');
      return this.themeModel.currentThemeColors;
    }
    
    // 最后使用默认的亮色主题
    console.log('[Index] ✅ 返回默认 LightTheme');
    return LightTheme;
  }

  async aboutToAppear() {
    console.log('[Index] 📱 aboutToAppear 开始');
    try {
      await this.themeModel.loadThemeSettings();
      this.updateThemeFromStorage();
    } catch (error) {
      console.error('主题初始化失败,使用默认主题', error);
    }
  }

  updateThemeFromStorage(): void {
    const hasChanged = this.themeModel.updateFromAppStorage();
  }

  build() {
    Column() {
      // 顶部导航栏
      Row() {
        Text('主题演示')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          // ✅ 调用方法获取颜色
          .fontColor(this.getCurrentThemeColors().onBackground)
          .layoutWeight(1)
          .textAlign(TextAlign.Center)

        // 主题切换按钮
        Button() {
          ThemeToggleIcon({
            iconWidth: 24,
            iconHeight: 24,
            color: this.getCurrentThemeColors().onBackground,
            isDarkMode: this.isDarkMode
          })
        }
        .type(ButtonType.Circle)
        .width(40)
        .height(40)
        .backgroundColor(Color.Transparent)
        .onClick(async () => {
          await this.themeModel.toggleTheme();
        })
      }
      .width('100%')
      .height(60)
      .padding({ left: 16, right: 16 })
      .backgroundColor(this.getCurrentThemeColors().surface)

      // 主内容区域
      Scroll() {
        Column({ space: 20 }) {
          this.buildThemeInfoCard()
          this.buildColorShowcaseCard()
          this.buildActionButtons()
        }
        .width('100%')
        .padding(16)
      }
      .layoutWeight(1)
      .width('100%')
    }
    .width('100%')
    .height('100%')
    .backgroundColor(this.getCurrentThemeColors().background)
  }

  @Builder
  buildThemeInfoCard() {
    Column({ space: 12 }) {
      Text('当前主题信息')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor(this.getCurrentThemeColors().onSurface)
        .width('100%')

      Row() {
        Text('主题类型:')
          .fontSize(14)
          .fontColor(this.getCurrentThemeColors().onSurfaceVariant)
        Text(this.themeModel.getThemeDescription())
          .fontSize(14)
          .fontWeight(FontWeight.Medium)
          .fontColor(this.getCurrentThemeColors().primary)
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)

      Row() {
        Text('当前模式:')
          .fontSize(14)
          .fontColor(this.getCurrentThemeColors().onSurfaceVariant)
        Text(this.isDarkMode ? '暗色模式' : '亮色模式')
          .fontSize(14)
          .fontWeight(FontWeight.Medium)
          .fontColor(this.getCurrentThemeColors().secondary)
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .width('100%')
    .padding(16)
    .backgroundColor(this.getCurrentThemeColors().card)
    .borderRadius(12)
    .shadow({
      radius: 8,
      color: this.getCurrentThemeColors().shadow,
      offsetX: 0,
      offsetY: 2
    })
  }

  @Builder
  buildColorShowcaseCard() {
    Column({ space: 12 }) {
      Text('主题颜色展示')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor(this.getCurrentThemeColors().onSurface)
        .width('100%')

      Row({ space: 12 }) {
        Column() {
          Text('主色')
            .fontSize(12)
            .fontColor('#FFFFFF')
        }
        .width('30%')
        .height(60)
        .backgroundColor(this.getCurrentThemeColors().primary)
        .borderRadius(8)
        .justifyContent(FlexAlign.Center)
        
        // 更多颜色展示...
      }
      .width('100%')
    }
    .width('100%')
    .padding(16)
    .backgroundColor(this.getCurrentThemeColors().card)
    .borderRadius(12)
  }

  @Builder
  buildActionButtons() {
    Column({ space: 12 }) {
      Text('主题切换操作')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor(this.getCurrentThemeColors().onSurface)
        .width('100%')

      Button('切换到亮色主题')
        .width('100%')
        .height(48)
        .fontSize(16)
        .fontColor(this.getCurrentThemeColors().onPrimary)
        .backgroundColor(this.getCurrentThemeColors().primary)
        .borderRadius(24)
        .onClick(async () => {
          await this.themeModel.setTheme('light');
        })

      Button('切换到暗色主题')
        .width('100%')
        .height(48)
        .fontSize(16)
        .fontColor(this.getCurrentThemeColors().onPrimary)
        .backgroundColor(this.getCurrentThemeColors().primary)
        .borderRadius(24)
        .onClick(async () => {
          await this.themeModel.setTheme('dark');
        })

      Button('跟随系统主题')
        .width('100%')
        .height(48)
        .fontSize(16)
        .fontColor(this.getCurrentThemeColors().onSecondary)
        .backgroundColor(this.getCurrentThemeColors().secondary)
        .borderRadius(24)
        .onClick(async () => {
          await this.themeModel.setTheme('system');
        })
    }
    .width('100%')
    .padding(16)
    .backgroundColor(this.getCurrentThemeColors().card)
    .borderRadius(12)
  }
}

关键说明

  • ⚠️ ArkTS 不支持 get currentThemeColors() getter 语法,必须使用普通方法 getCurrentThemeColors()
  • 使用 @StorageProp 监听 AppStorage 中的主题变化
  • 所有地方都调用 this.getCurrentThemeColors() 方法获取颜色
  • 完善的错误处理确保永远不会返回 undefined

代码详解

1. 为什么要使用普通方法而不是 getter?

// ❌ 错误:ArkTS 不支持 get 关键字
@Component
struct Index {
  get currentThemeColors(): ThemeColors {
    return LightTheme;
  }
  
  build() {
    // 这会返回 undefined 导致崩溃!
    Text('hello').fontColor(this.currentThemeColors.onBackground)
  }
}

// ✅ 正确:使用普通方法
@Component
struct Index {
  getCurrentThemeColors(): ThemeColors {
    return LightTheme;
  }
  
  build() {
    // 正常工作
    Text('hello').fontColor(this.getCurrentThemeColors().onBackground)
  }
}

2. AppStorage 状态管理

// 设置或创建值
AppStorage.setOrCreate('key', value)

// 获取值
const value = AppStorage.get<Type>('key')

// 在组件中使用 @StorageProp 自动响应变化
@StorageProp('key') myValue: Type = defaultValue

3. 主题切换流程

用户点击切换按钮
    ↓
ThemeModel.toggleTheme()
    ↓
ThemeModel.setTheme(theme)
    ↓
ThemeModel.applyTheme(theme)
    ↓
更新 isDarkMode 和 currentThemeColors
    ↓
ThemeModel.syncToAppStorage()
    ↓
AppStorage 值变化
    ↓
所有使用 @StorageProp 的组件自动更新

4. 系统主题跟随原理

  1. EntryAbility.onCreateonConfigurationUpdate 同步系统颜色模式到 AppStorage
  2. ThemeModel.followSystemTheme() 从 AppStorage 读取系统颜色模式
  3. 根据系统颜色模式应用对应的主题配色
  4. ThemeModel.syncToAppStorage() 同步主题颜色到 AppStorage
  5. 页面通过 @StorageProp('app_theme_colors') 监听变化,调用 getCurrentThemeColors() 自动更新UI

运行项目

1. 编译项目

cd demo
hvigorw assembleHap --mode module -p module=entry@default

预期输出

BUILD SUCCESSFUL in 8 s 530 ms

2. 在 DevEco Studio 中运行

  1. 使用 DevEco Studio 打开项目
  2. 连接设备或启动模拟器
  3. 点击运行按钮(或按 Shift+F10)
  4. 等待应用安装并启动

3. 查看日志

在 DevEco Studio 的 Log/HiLog 面板中,可以看到:

[ThemeModel] 📦 构造函数开始
[ThemeModel] 📦 LightTheme: [object Object]
[ThemeModel] 📦 构造函数完成,currentThemeColors: [object Object]
[ThemeModel] 📦 currentThemeColors.background: #FFFFFF
[EntryAbility] 当前系统颜色模式: 1
[EntryAbility] 主题模型初始化成功
[Index] 📱 aboutToAppear 开始
[Index] 🔍 getCurrentThemeColors - 开始
[Index] ✅ 从 themeModel 返回颜色

4. 测试功能

手动切换主题
  • 方法1:点击右上角的主题切换图标
  • 方法2:点击页面中的主题切换按钮
跟随系统主题
  1. 点击"跟随系统主题"按钮
  2. 在系统设置中切换主题(设置 > 显示和亮度 > 深色模式)
  3. 观察应用是否自动跟随系统主题变化
验证响应式更新
  • 切换主题后,观察页面所有元素是否立即更新
  • 检查主题信息卡片中的显示是否正确

常见问题

Q1: 应用启动时崩溃,错误 “Cannot read property background of undefined”?

原因:这是最常见的问题,有两个可能的原因:

  1. 使用了 ArkTS 不支持的 get 关键字
  2. ThemeModel 异步初始化未完成时访问了 currentThemeColors

解决方案

  1. 确保使用普通方法而不是 getter
// ❌ 错误
get currentThemeColors(): ThemeColors { ... }

// ✅ 正确
getCurrentThemeColors(): ThemeColors { ... }
  1. 在 ThemeModel 构造函数中立即设置默认值
constructor() {
  // 立即设置默认值,确保对象创建后立即可用
  this.currentThemeColors = LightTheme
  this.themeType = 'system'
  this.isDarkMode = false
  
  // 异步初始化
  this.initTheme()
}
  1. 在 getCurrentThemeColors() 中添加完善的错误处理
getCurrentThemeColors(): ThemeColors {
  // 1. 优先从 AppStorage 读取
  if (this.themeColorsJson && this.themeColorsJson.trim() !== '') {
    try {
      const colors = JSON.parse(this.themeColorsJson) as ThemeColors;
      if (colors && colors.background) {
        return colors;
      }
    } catch (e) {
      // 解析失败,继续下一步
    }
  }
  
  // 2. 尝试从 themeModel 读取
  if (this.themeModel?.currentThemeColors?.background) {
    return this.themeModel.currentThemeColors;
  }
  
  // 3. 返回默认值
  return LightTheme;
}
  1. 在 aboutToAppear 中添加错误处理
async aboutToAppear() {
  try {
    await this.themeModel.loadThemeSettings();
    this.updateThemeFromStorage();
  } catch (error) {
    console.error('主题初始化失败,使用默认主题', error);
  }
}

Q2: 跟随系统主题不生效?

解决方案

  1. 确保 EntryAbility 实现了 onConfigurationUpdate 方法
  2. 确保同步了 currentColorMode 到 AppStorage
  3. 确保 ThemeModel.themeType 设置为 'system'
  4. 确保页面使用了 @StorageProp('app_theme_colors')
  5. 确保页面调用 getCurrentThemeColors() 方法

Q3: 编译错误 “ArkTS Compiler Error”?

可能原因

  • 使用了 get 关键字定义 getter
  • build() 方法中有 console.log 等语句
  • 语法不符合 ArkTS 规范

解决方案

  • get currentThemeColors() 改为 getCurrentThemeColors()
  • build() 中的调试代码移到 aboutToAppear()
  • 参考本文档的正确代码示例

Q4: 如何清除缓存重新编译?

如果遇到奇怪的问题,可能是缓存导致,请清除缓存:

方法1:手动删除缓存文件夹

cd demo
Remove-Item -Recurse -Force .hvigor\cache
Remove-Item -Recurse -Force entry\build
Remove-Item -Recurse -Force build

方法2:在 DevEco Studio 中

  1. 点击菜单:Build → Clean Project
  2. 点击菜单:Build → Rebuild Project

Q5: 如何自定义主题颜色?

修改 ThemeModel.ets 中的 LightThemeDarkTheme 常量:

export const LightTheme: ThemeColors = {
  primary: '#你的主色',
  background: '#你的背景色',
  // ... 修改其他颜色
}

Q6: 如何在其他页面使用主题?

参考以下代码模板:

import { ThemeModel, ThemeColors, LightTheme } from '../models/ThemeModel';

@Component
struct MyPage {
  @State themeModel: ThemeModel = new ThemeModel();
  @StorageProp('app_is_dark_mode') isDarkMode: boolean = false;
  @StorageProp('app_theme_colors') themeColorsJson: string = '';

  // 使用普通方法获取主题颜色
  getCurrentThemeColors(): ThemeColors {
    if (this.themeColorsJson && this.themeColorsJson.trim() !== '') {
      try {
        const colors = JSON.parse(this.themeColorsJson) as ThemeColors;
        if (colors && colors.background) {
          return colors;
        }
      } catch (e) {}
    }
    
    if (this.themeModel?.currentThemeColors?.background) {
      return this.themeModel.currentThemeColors;
    }
    
    return LightTheme;
  }

  async aboutToAppear() {
    await this.themeModel.loadThemeSettings();
    this.themeModel.updateFromAppStorage();
  }

  build() {
    Column() {
      Text('我的页面')
        .fontColor(this.getCurrentThemeColors().onBackground)
    }
    .backgroundColor(this.getCurrentThemeColors().background)
  }
}

Q7: 主题设置会持久化吗?

是的,ThemeModel 使用 AppStorage 保存主题设置,应用重启后会恢复上次的主题选择。

ArkTS 语法注意事项

1. 不支持 ES6 Getter/Setter

// ❌ 错误 - ArkTS 不支持
get currentThemeColors(): ThemeColors {
  return LightTheme;
}

// ✅ 正确 - 使用普通方法
getCurrentThemeColors(): ThemeColors {
  return LightTheme;
}

2. build() 方法限制

build() 方法只能包含一个根组件,不能有其他语句:

// ❌ 错误
build() {
  console.log('debug')  // 不能有 console.log
  const colors = this.getCurrentThemeColors()  // 不能有变量声明
  
  Column() { ... }
}

// ✅ 正确
build() {
  Column() { ... }
}

3. 组件装饰器限制

  • 只能使用官方提供的装饰器:@Component@Entry@State@Prop@StorageProp
  • 不能使用自定义装饰器

4. 其他限制

特性 ES6/TypeScript ArkTS 说明
get/set ✅ 支持 ❌ 不支持 使用普通方法
私有字段 #field ✅ 支持 ❌ 不支持 使用 private field
可选链 ?. ✅ 支持 ✅ 支持 推荐使用
空值合并 ?? ✅ 支持 ✅ 支持 推荐使用

总结

通过本实践指南,我们实现了:

  1. 三种主题模式:亮色、暗色、跟随系统
  2. 手动切换功能:通过按钮或图标快速切换
  3. 系统主题跟随:自动响应系统主题变化
  4. 响应式更新:所有UI元素自动适配主题
  5. 状态持久化:主题设置自动保存
  6. 防御性编程:完善的错误处理,避免崩溃

核心要点回顾

  1. ArkTS 语法:不支持 get 关键字,必须使用普通方法
  2. 防御性初始化:构造函数中立即设置默认值
  3. 多层错误处理:AppStorage → themeModel → 默认值
  4. AppStorage 同步:EntryAbility → AppStorage → ThemeModel → UI
  5. 响应式更新:使用 @StorageProp 监听变化

希望这个实践指南能帮助你理解和实现鸿蒙应用的响应式主题设计!


如有问题,请参考

  • demo/核心问题修复说明.md - 详细的崩溃问题解决方案
  • 项目源码中的注释和日志
    源代码
    效果图
    请添加图片描述

https://developer.huawei.com/consumer/cn/training/classDetail/fd34ff9286174e848d34cde7f512ce22?type=1%3Fha_source%3Dhmosclass&ha_sourceId=89000248

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐