一、方案概述

深色模式是现代移动应用的重要特性,能够降低屏幕亮度、减少眼睛疲劳,并节省设备电量。本方案为“面试通”应用设计了完整的深色模式主题管理系统,包含主题资源管理、动态切换、状态持久化和系统级适配等功能。

如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏
嘻嘻嘻,关注我!!!黑马波哥
也可以关注我的抖音号: 黑马程序员burger 在直播间交流(17:00-21:00)

二、整体架构设计

2.1 主题管理架构图

数据持久层

系统适配层

资源管理层

主题管理层

用户界面层

主题切换按钮

主题设置页面

应用各界面组件

主题响应式更新

ThemeManager
主题管理器

ThemeResourceLoader
主题资源加载器

ThemePreference
主题偏好存储

resources/base
基础资源

resources/dark
深色模式资源

resources/light
浅色模式资源

颜色资源

图片资源

样式资源

字体资源

系统主题监听器

系统主题变更事件

自动适配系统主题

Preferences
持久化存储

用户主题偏好

主题配置参数

2.2 功能流程图

系统设置 Preferences 主题资源 ThemeManager 用户界面 用户 系统设置 Preferences 主题资源 ThemeManager 用户界面 用户 系统主题变更监听 点击主题切换按钮 切换主题请求(ThemeMode.DARK) 验证主题模式有效性 加载对应主题资源 返回主题资源 保存主题偏好设置 保存成功确认 应用新主题资源 界面更新完成 系统主题变更事件 检查是否跟随系统 加载匹配系统主题资源 返回主题资源 自动应用新主题

三、主题资源定义与管理

3.1 资源文件结构设计

resources/
├── base/
│   ├── element/
│   │   ├── color.json          # 基础颜色定义
│   │   ├── float.json          # 基础尺寸定义
│   │   └── string.json         # 基础字符串定义
│   ├── graphic/
│   │   ├── bg_common.xml       # 通用背景
│   │   └── ic_theme_switch.xml # 主题切换图标
│   └── profile/
│       └── theme_styles.json   # 基础样式定义
├── dark/                       # 深色模式资源
│   ├── element/
│   │   ├── color.json          # 深色主题颜色
│   │   └── media/              # 深色主题图片资源
│   └── profile/
│       └── theme_styles.json   # 深色主题样式
├── light/                      # 浅色模式资源
│   ├── element/
│   │   ├── color.json          # 浅色主题颜色
│   │   └── media/              # 浅色主题图片资源
│   └── profile/
│       └── theme_styles.json   # 浅色主题样式
└── rawfile/
    └── theme_config.json       # 主题配置文件

3.2 颜色资源定义

// resources/base/element/color.json
{
  "color": [
    {
      "name": "primary_color",
      "value": "#007DFF"
    },
    {
      "name": "secondary_color",
      "value": "#6C7B8F"
    },
    {
      "name": "background_primary",
      "value": "#FFFFFF"
    },
    {
      "name": "background_secondary",
      "value": "#F5F5F5"
    },
    {
      "name": "text_primary",
      "value": "#182431"
    },
    {
      "name": "text_secondary",
      "value": "#6C7B8F"
    },
    {
      "name": "border_color",
      "value": "#E6EBF0"
    },
    {
      "name": "success_color",
      "value": "#00B365"
    },
    {
      "name": "warning_color",
      "value": "#FF9500"
    },
    {
      "name": "error_color",
      "value": "#FF3B30"
    },
    {
      "name": "card_background",
      "value": "#FFFFFF"
    },
    {
      "name": "divider_color",
      "value": "#F0F0F0"
    }
  ]
}

// resources/dark/element/color.json
{
  "color": [
    {
      "name": "primary_color",
      "value": "#4D9EFF"
    },
    {
      "name": "secondary_color",
      "value": "#8E9BAE"
    },
    {
      "name": "background_primary",
      "value": "#121212"
    },
    {
      "name": "background_secondary",
      "value": "#1E1E1E"
    },
    {
      "name": "text_primary",
      "value": "#E6E6E6"
    },
    {
      "name": "text_secondary",
      "value": "#A0A0A0"
    },
    {
      "name": "border_color",
      "value": "#2D2D2D"
    },
    {
      "name": "success_color",
      "value": "#00CC73"
    },
    {
      "name": "warning_color",
      "value": "#FFAA33"
    },
    {
      "name": "error_color",
      "value": "#FF6666"
    },
    {
      "name": "card_background",
      "value": "#1E1E1E"
    },
    {
      "name": "divider_color",
      "value": "#2D2D2D"
    }
  ]
}

3.3 样式资源定义

// resources/base/profile/theme_styles.json
{
  "style": [
    {
      "name": "text_title_large",
      "value": {
        "font-size": "24fp",
        "font-weight": "bold",
        "line-height": "32fp"
      }
    },
    {
      "name": "text_title_medium",
      "value": {
        "font-size": "20fp",
        "font-weight": "bold",
        "line-height": "28fp"
      }
    },
    {
      "name": "text_body_large",
      "value": {
        "font-size": "18fp",
        "line-height": "26fp"
      }
    },
    {
      "name": "text_body_medium",
      "value": {
        "font-size": "16fp",
        "line-height": "24fp"
      }
    },
    {
      "name": "text_body_small",
      "value": {
        "font-size": "14fp",
        "line-height": "20fp"
      }
    },
    {
      "name": "corner_radius_large",
      "value": "16vp"
    },
    {
      "name": "corner_radius_medium",
      "value": "12vp"
    },
    {
      "name": "corner_radius_small",
      "value": "8vp"
    },
    {
      "name": "elevation_default",
      "value": "8vp"
    },
    {
      "name": "spacing_large",
      "value": "24vp"
    },
    {
      "name": "spacing_medium",
      "value": "16vp"
    },
    {
      "name": "spacing_small",
      "value": "8vp"
    }
  ]
}

四、主题管理器核心实现

4.1 主题管理器类

// theme/ThemeManager.ts
import { AbilityContext, common } from '@kit.AbilityKit';
import { Preferences } from '@kit.DataPreferencesKit';
import { Logger } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * 主题模式枚举
 */
export enum ThemeMode {
  /**
   * 浅色模式
   */
  LIGHT = 'light',
  
  /**
   * 深色模式
   */
  DARK = 'dark',
  
  /**
   * 跟随系统
   */
  SYSTEM = 'system'
}

/**
 * 主题配置接口
 */
export interface ThemeConfig {
  mode: ThemeMode;
  primaryColor: string;
  secondaryColor: string;
  fontSizeScale: number;
  borderRadiusScale: number;
}

/**
 * 主题管理器类
 * 负责主题资源的加载、切换和持久化
 */
export class ThemeManager {
  private static instance: ThemeManager;
  private context: AbilityContext | null = null;
  private preferences: Preferences | null = null;
  private currentTheme: ThemeMode = ThemeMode.LIGHT;
  private systemDarkMode: boolean = false;
  private listeners: Array<(theme: ThemeMode) => void> = [];
  private readonly PREFERENCES_KEY = 'theme_preferences';
  private readonly THEME_MODE_KEY = 'theme_mode';
  
  /**
   * 私有构造函数,单例模式
   */
  private constructor() {}
  
  /**
   * 获取主题管理器单例
   * @returns ThemeManager 实例
   */
  public static getInstance(): ThemeManager {
    if (!ThemeManager.instance) {
      ThemeManager.instance = new ThemeManager();
    }
    return ThemeManager.instance;
  }
  
  /**
   * 初始化主题管理器
   * @param context 应用上下文
   */
  public async initialize(context: AbilityContext): Promise<void> {
    try {
      this.context = context;
      
      // 初始化Preferences存储
      await this.initializePreferences();
      
      // 加载保存的主题设置
      await this.loadSavedTheme();
      
      // 监听系统主题变化
      await this.setupSystemThemeListener();
      
      Logger.info('ThemeManager', 'Theme manager initialized successfully');
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('ThemeManager', `Failed to initialize theme manager: ${err.message}`);
      throw err;
    }
  }
  
  /**
   * 初始化Preferences存储
   */
  private async initializePreferences(): Promise<void> {
    try {
      if (!this.context) {
        throw new Error('Context not initialized');
      }
      
      this.preferences = await Preferences.getPreferences(this.context, {
        name: this.PREFERENCES_KEY
      });
      
      Logger.debug('ThemeManager', 'Preferences initialized');
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('ThemeManager', `Failed to initialize preferences: ${err.message}`);
      throw err;
    }
  }
  
  /**
   * 加载保存的主题设置
   */
  private async loadSavedTheme(): Promise<void> {
    try {
      if (!this.preferences) {
        throw new Error('Preferences not initialized');
      }
      
      const savedTheme = await this.preferences.get(this.THEME_MODE_KEY, ThemeMode.SYSTEM);
      this.currentTheme = savedTheme as ThemeMode;
      
      Logger.info('ThemeManager', `Loaded saved theme: ${this.currentTheme}`);
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.warn('ThemeManager', `Failed to load saved theme, using default: ${err.message}`);
      this.currentTheme = ThemeMode.SYSTEM;
    }
  }
  
  /**
   * 设置系统主题监听器
   */
  private async setupSystemThemeListener(): Promise<void> {
    try {
      // 监听系统主题变化
      // 注意:这里需要根据实际的系统API进行调整
      // 示例代码,实际实现可能不同
      this.checkSystemDarkMode();
      
      Logger.debug('ThemeManager', 'System theme listener setup');
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('ThemeManager', `Failed to setup system theme listener: ${err.message}`);
    }
  }
  
  /**
   * 检查系统深色模式状态
   */
  private checkSystemDarkMode(): void {
    try {
      // 这里应该调用系统API获取当前主题模式
      // 示例实现,实际API可能不同
      const systemTheme = common.getSystemTheme();
      this.systemDarkMode = systemTheme === 'dark';
      
      Logger.debug('ThemeManager', `System dark mode: ${this.systemDarkMode}`);
      
      // 如果当前主题模式是跟随系统,则应用系统主题
      if (this.currentTheme === ThemeMode.SYSTEM) {
        this.applySystemTheme();
      }
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.warn('ThemeManager', `Failed to check system dark mode: ${err.message}`);
    }
  }
  
  /**
   * 应用系统主题
   */
  private applySystemTheme(): void {
    const targetTheme = this.systemDarkMode ? ThemeMode.DARK : ThemeMode.LIGHT;
    this.applyTheme(targetTheme, false); // 不保存到preferences
  }
  
  /**
   * 切换主题
   * @param theme 目标主题模式
   * @returns 切换是否成功
   */
  public async switchTheme(theme: ThemeMode): Promise<boolean> {
    try {
      // 验证主题模式
      if (!Object.values(ThemeMode).includes(theme)) {
        throw new Error(`Invalid theme mode: ${theme}`);
      }
      
      // 保存主题设置
      await this.saveThemePreference(theme);
      
      // 应用新主题
      this.applyTheme(theme, false);
      
      Logger.info('ThemeManager', `Theme switched to: ${theme}`);
      return true;
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('ThemeManager', `Failed to switch theme: ${err.message}`);
      return false;
    }
  }
  
  /**
   * 保存主题偏好设置
   * @param theme 主题模式
   */
  private async saveThemePreference(theme: ThemeMode): Promise<void> {
    try {
      if (!this.preferences) {
        throw new Error('Preferences not initialized');
      }
      
      await this.preferences.put(this.THEME_MODE_KEY, theme);
      await this.preferences.flush();
      
      this.currentTheme = theme;
      
      Logger.debug('ThemeManager', `Theme preference saved: ${theme}`);
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      throw new Error(`Failed to save theme preference: ${err.message}`);
    }
  }
  
  /**
   * 应用主题
   * @param theme 主题模式
   * @param notify 是否通知监听器
   */
  private applyTheme(theme: ThemeMode, notify: boolean = true): void {
    try {
      // 获取实际应用的主题
      const actualTheme = this.getActualTheme(theme);
      
      // 更新资源管理器中的主题
      this.updateResourceManager(actualTheme);
      
      // 通知所有监听器
      if (notify) {
        this.notifyThemeChanged(actualTheme);
      }
      
      Logger.debug('ThemeManager', `Theme applied: ${actualTheme}`);
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('ThemeManager', `Failed to apply theme: ${err.message}`);
    }
  }
  
  /**
   * 获取实际应用的主题
   * @param theme 请求的主题模式
   * @returns 实际应用的主题模式
   */
  private getActualTheme(theme: ThemeMode): ThemeMode {
    if (theme === ThemeMode.SYSTEM) {
      return this.systemDarkMode ? ThemeMode.DARK : ThemeMode.LIGHT;
    }
    return theme;
  }
  
  /**
   * 更新资源管理器中的主题
   * @param theme 主题模式
   */
  private updateResourceManager(theme: ThemeMode): void {
    try {
      // 这里应该调用系统API更新应用主题
      // 示例实现,实际API可能不同
      const config = {
        colorMode: theme === ThemeMode.DARK ? 'dark' : 'light',
        fontScale: 1.0,
        // 其他配置参数
      };
      
      // 调用系统API设置主题
      // system.setAppTheme(config);
      
      Logger.debug('ThemeManager', `Resource manager updated for theme: ${theme}`);
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.warn('ThemeManager', `Failed to update resource manager: ${err.message}`);
    }
  }
  
  /**
   * 通知主题变化
   * @param theme 新主题模式
   */
  private notifyThemeChanged(theme: ThemeMode): void {
    this.listeners.forEach(listener => {
      try {
        listener(theme);
      } catch (error) {
        Logger.warn('ThemeManager', `Theme change listener error: ${error}`);
      }
    });
  }
  
  /**
   * 获取当前主题模式
   * @returns 当前主题模式
   */
  public getCurrentTheme(): ThemeMode {
    return this.currentTheme;
  }
  
  /**
   * 获取实际应用的主题模式
   * @returns 实际应用的主题模式
   */
  public getAppliedTheme(): ThemeMode {
    return this.getActualTheme(this.currentTheme);
  }
  
  /**
   * 添加主题变化监听器
   * @param listener 监听器函数
   */
  public addThemeChangeListener(listener: (theme: ThemeMode) => void): void {
    if (!this.listeners.includes(listener)) {
      this.listeners.push(listener);
    }
  }
  
  /**
   * 移除主题变化监听器
   * @param listener 监听器函数
   */
  public removeThemeChangeListener(listener: (theme: ThemeMode) => void): void {
    const index = this.listeners.indexOf(listener);
    if (index > -1) {
      this.listeners.splice(index, 1);
    }
  }
  
  /**
   * 是否深色模式
   * @returns 是否为深色模式
   */
  public isDarkMode(): boolean {
    return this.getAppliedTheme() === ThemeMode.DARK;
  }
  
  /**
   * 获取主题配置
   * @returns 主题配置对象
   */
  public getThemeConfig(): ThemeConfig {
    const appliedTheme = this.getAppliedTheme();
    
    return {
      mode: this.currentTheme,
      primaryColor: appliedTheme === ThemeMode.DARK ? '#4D9EFF' : '#007DFF',
      secondaryColor: appliedTheme === ThemeMode.DARK ? '#8E9BAE' : '#6C7B8F',
      fontSizeScale: 1.0,
      borderRadiusScale: 1.0
    };
  }
  
  /**
   * 重置为主题默认设置
   */
  public async resetToDefault(): Promise<void> {
    try {
      await this.switchTheme(ThemeMode.SYSTEM);
      Logger.info('ThemeManager', 'Theme reset to default (system)');
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('ThemeManager', `Failed to reset theme: ${err.message}`);
      throw err;
    }
  }
}

4.2 主题切换界面组件

// components/ThemeSwitchCard.ets
@Component
export struct ThemeSwitchCard {
  @State currentTheme: ThemeMode = ThemeMode.LIGHT;
  private themeManager: ThemeManager = ThemeManager.getInstance();
  
  aboutToAppear(): void {
    // 获取当前主题
    this.currentTheme = this.themeManager.getCurrentTheme();
    
    // 监听主题变化
    this.themeManager.addThemeChangeListener((theme: ThemeMode) => {
      this.currentTheme = theme;
    });
  }
  
  aboutToDisappear(): void {
    // 移除监听器
    // 注意:实际实现中需要记录监听器引用以便移除
  }
  
  build() {
    Column({ space: 0 }) {
      // 标题行
      Row({ space: 0 }) {
        Image($r('app.media.ic_theme'))
          .width(24)
          .height(24)
          .margin({ right: 12 })
        
        Text($r('app.string.theme_settings'))
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
          .layoutWeight(1)
        
        Text(this.getThemeDescription())
          .fontSize(14)
          .fontColor($r('app.color.text_secondary'))
      }
      .width('100%')
      .padding({ top: 16, bottom: 16, left: 20, right: 20 })
      .backgroundColor($r('app.color.card_background'))
      
      // 主题选项
      Column({ space: 0 }) {
        // 浅色模式选项
        this.buildThemeOption(
          ThemeMode.LIGHT,
          $r('app.string.theme_light'),
          $r('app.string.theme_light_description'),
          $r('app.media.ic_theme_light')
        )
        
        Divider()
          .strokeWidth(0.5)
          .color($r('app.color.divider_color'))
          .margin({ left: 20 })
        
        // 深色模式选项
        this.buildThemeOption(
          ThemeMode.DARK,
          $r('app.string.theme_dark'),
          $r('app.string.theme_dark_description'),
          $r('app.media.ic_theme_dark')
        )
        
        Divider()
          .strokeWidth(0.5)
          .color($r('app.color.divider_color'))
          .margin({ left: 20 })
        
        // 跟随系统选项
        this.buildThemeOption(
          ThemeMode.SYSTEM,
          $r('app.string.theme_system'),
          $r('app.string.theme_system_description'),
          $r('app.media.ic_theme_system')
        )
      }
      .backgroundColor($r('app.color.card_background'))
      .borderRadius({
        topLeft: 0,
        topRight: 0,
        bottomLeft: $r('app.float.corner_radius_medium'),
        bottomRight: $r('app.float.corner_radius_medium')
      })
    }
    .width('100%')
    .margin({ top: 12, bottom: 12 })
  }
  
  @Builder
  buildThemeOption(
    theme: ThemeMode,
    title: Resource,
    description: Resource,
    icon: Resource
  ) {
    Row({ space: 0 }) {
      // 图标
      Image(icon)
        .width(40)
        .height(40)
        .margin({ right: 16 })
      
      // 文字内容
      Column({ space: 2 }) {
        Text(title)
          .fontSize(16)
          .fontColor($r('app.color.text_primary'))
          .textAlign(TextAlign.Start)
          .width('100%')
        
        Text(description)
          .fontSize(12)
          .fontColor($r('app.color.text_secondary'))
          .textAlign(TextAlign.Start)
          .width('100%')
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)
      
      // 单选按钮
      if (this.currentTheme === theme) {
        Image($r('app.media.ic_radio_checked'))
          .width(20)
          .height(20)
          .fillColor($r('app.color.primary_color'))
      } else {
        Image($r('app.media.ic_radio_unchecked'))
          .width(20)
          .height(20)
          .fillColor($r('app.color.text_secondary'))
      }
    }
    .width('100%')
    .padding({ top: 16, bottom: 16, left: 20, right: 20 })
    .backgroundColor($r('app.color.card_background'))
    .onClick(() => {
      this.onThemeSelected(theme);
    })
  }
  
  private getThemeDescription(): string {
    const appliedTheme = this.themeManager.getAppliedTheme();
    
    switch (this.currentTheme) {
      case ThemeMode.LIGHT:
        return $r('app.string.theme_status_light').toString();
      case ThemeMode.DARK:
        return $r('app.string.theme_status_dark').toString();
      case ThemeMode.SYSTEM:
        return appliedTheme === ThemeMode.DARK 
          ? $r('app.string.theme_status_system_dark').toString()
          : $r('app.string.theme_status_system_light').toString();
      default:
        return '';
    }
  }
  
  private async onThemeSelected(theme: ThemeMode): Promise<void> {
    if (this.currentTheme === theme) {
      return;
    }
    
    // 显示加载状态
    // 这里可以添加加载动画
    
    try {
      const success = await this.themeManager.switchTheme(theme);
      if (success) {
        this.currentTheme = theme;
        // 显示成功提示
        this.showToast($r('app.string.theme_change_success'));
      } else {
        // 显示错误提示
        this.showToast($r('app.string.theme_change_failed'));
      }
    } catch (error) {
      // 显示错误提示
      this.showToast($r('app.string.theme_change_error'));
    }
  }
  
  private showToast(message: Resource): void {
    // 实现Toast提示
    // 示例代码,实际实现可能不同
    // prompt.showToast({ message: message.toString() });
  }
}

4.3 主题适配组件

// components/ThemeAdaptiveContainer.ets
@Component
export struct ThemeAdaptiveContainer {
  @Prop content: () => void;
  @State isDarkMode: boolean = false;
  private themeManager: ThemeManager = ThemeManager.getInstance();
  
  aboutToAppear(): void {
    this.isDarkMode = this.themeManager.isDarkMode();
    
    // 监听主题变化
    this.themeManager.addThemeChangeListener(() => {
      this.isDarkMode = this.themeManager.isDarkMode();
    });
  }
  
  build() {
    Column({ space: 0 }) {
      // 动态内容
      this.content()
    }
    .width('100%')
    .height('100%')
    .backgroundColor(this.isDarkMode 
      ? $r('app.color.background_primary_dark') 
      : $r('app.color.background_primary_light'))
  }
}

五、深色模式适配实践

5.1 颜色使用规范

// utils/ThemeColors.ets
export class ThemeColors {
  /**
   * 获取动态颜色
   * @param lightColor 浅色模式颜色资源ID
   * @param darkColor 深色模式颜色资源ID
   * @returns 当前主题下的颜色值
   */
  static getDynamicColor(lightColor: Resource, darkColor: Resource): Resource {
    const themeManager = ThemeManager.getInstance();
    return themeManager.isDarkMode() ? darkColor : lightColor;
  }
  
  /**
   * 获取背景颜色
   */
  static getBackgroundPrimary(): Resource {
    return this.getDynamicColor(
      $r('app.color.background_primary'),
      $r('app.color.background_primary_dark')
    );
  }
  
  /**
   * 获取次要背景颜色
   */
  static getBackgroundSecondary(): Resource {
    return this.getDynamicColor(
      $r('app.color.background_secondary'),
      $r('app.color.background_secondary_dark')
    );
  }
  
  /**
   * 获取主要文字颜色
   */
  static getTextPrimary(): Resource {
    return this.getDynamicColor(
      $r('app.color.text_primary'),
      $r('app.color.text_primary_dark')
    );
  }
  
  /**
   * 获取次要文字颜色
   */
  static getTextSecondary(): Resource {
    return this.getDynamicColor(
      $r('app.color.text_secondary'),
      $r('app.color.text_secondary_dark')
    );
  }
  
  /**
   * 获取边框颜色
   */
  static getBorderColor(): Resource {
    return this.getDynamicColor(
      $r('app.color.border_color'),
      $r('app.color.border_color_dark')
    );
  }
  
  /**
   * 获取卡片背景颜色
   */
  static getCardBackground(): Resource {
    return this.getDynamicColor(
      $r('app.color.card_background'),
      $r('app.color.card_background_dark')
    );
  }
  
  /**
   * 获取分割线颜色
   */
  static getDividerColor(): Resource {
    return this.getDynamicColor(
      $r('app.color.divider_color'),
      $r('app.color.divider_color_dark')
    );
  }
}

5.2 图片资源适配

// utils/ThemeImages.ets
export class ThemeImages {
  /**
   * 获取动态图片资源
   * @param lightImage 浅色模式图片资源ID
   * @param darkImage 深色模式图片资源ID
   * @returns 当前主题下的图片资源
   */
  static getDynamicImage(lightImage: Resource, darkImage: Resource): Resource {
    const themeManager = ThemeManager.getInstance();
    return themeManager.isDarkMode() ? darkImage : lightImage;
  }
  
  /**
   * 获取Logo图片
   */
  static getLogo(): Resource {
    return this.getDynamicImage(
      $r('app.media.logo_light'),
      $r('app.media.logo_dark')
    );
  }
  
  /**
   * 获取首页Banner图片
   */
  static getHomeBanner(): Resource {
    return this.getDynamicImage(
      $r('app.media.banner_light'),
      $r('app.media.banner_dark')
    );
  }
  
  /**
   * 获取空状态图片
   */
  static getEmptyState(): Resource {
    return this.getDynamicImage(
      $r('app.media.empty_light'),
      $r('app.media.empty_dark')
    );
  }
  
  /**
   * 获取错误状态图片
   */
  static getErrorState(): Resource {
    return this.getDynamicImage(
      $r('app.media.error_light'),
      $r('app.media.error_dark')
    );
  }
}

六、高级主题功能

6.1 动态主题生成

// theme/DynamicThemeGenerator.ts
import { Logger } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * 动态主题配置
 */
export interface DynamicThemeConfig {
  primaryColor: string;
  secondaryColor: string;
  backgroundColor: string;
  surfaceColor: string;
  textColor: string;
  isDark: boolean;
}

/**
 * 动态主题生成器
 * 支持根据基础颜色生成完整的主题配色方案
 */
export class DynamicThemeGenerator {
  /**
   * 生成深色主题配色方案
   * @param primaryColor 主色调
   * @returns 完整的深色主题配置
   */
  static generateDarkTheme(primaryColor: string): DynamicThemeConfig {
    try {
      // 解析主色调
      const primary = this.parseColor(primaryColor);
      
      // 生成配色方案
      return {
        primaryColor: primaryColor,
        secondaryColor: this.adjustColor(primary, -20, 10, -10),
        backgroundColor: '#121212',
        surfaceColor: '#1E1E1E',
        textColor: '#E6E6E6',
        isDark: true
      };
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('DynamicThemeGenerator', `Failed to generate dark theme: ${err.message}`);
      throw err;
    }
  }
  
  /**
   * 生成浅色主题配色方案
   * @param primaryColor 主色调
   * @returns 完整的浅色主题配置
   */
  static generateLightTheme(primaryColor: string): DynamicThemeConfig {
    try {
      // 解析主色调
      const primary = this.parseColor(primaryColor);
      
      // 生成配色方案
      return {
        primaryColor: primaryColor,
        secondaryColor: this.adjustColor(primary, 20, -10, 10),
        backgroundColor: '#FFFFFF',
        surfaceColor: '#F5F5F5',
        textColor: '#182431',
        isDark: false
      };
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      Logger.error('DynamicThemeGenerator', `Failed to generate light theme: ${err.message}`);
      throw err;
    }
  }
  
  /**
   * 解析颜色字符串
   * @param color 颜色字符串(#RRGGBB或#RRGGBBAA)
   * @returns RGB数值数组
   */
  private static parseColor(color: string): number[] {
    if (!color.startsWith('#')) {
      throw new Error('Invalid color format');
    }
    
    let hex = color.substring(1);
    
    // 处理3位或4位简写格式
    if (hex.length === 3 || hex.length === 4) {
      hex = hex.split('').map(c => c + c).join('');
    }
    
    if (hex.length !== 6 && hex.length !== 8) {
      throw new Error('Invalid color length');
    }
    
    const r = parseInt(hex.substring(0, 2), 16);
    const g = parseInt(hex.substring(2, 4), 16);
    const b = parseInt(hex.substring(4, 6), 16);
    
    return [r, g, b];
  }
  
  /**
   * 调整颜色
   * @param color RGB颜色数组
   * @param rDelta 红色增量
   * @param gDelta 绿色增量
   * @param bDelta 蓝色增量
   * @returns 调整后的颜色字符串
   */
  private static adjustColor(
    color: number[], 
    rDelta: number, 
    gDelta: number, 
    bDelta: number
  ): string {
    const r = Math.max(0, Math.min(255, color[0] + rDelta));
    const g = Math.max(0, Math.min(255, color[1] + gDelta));
    const b = Math.max(0, Math.min(255, color[2] + bDelta));
    
    return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
  }
  
  /**
   * 计算对比度
   * @param color1 颜色1
   * @param color2 颜色2
   * @returns 对比度比值
   */
  static calculateContrast(color1: string, color2: string): number {
    try {
      const rgb1 = this.parseColor(color1);
      const rgb2 = this.parseColor(color2);
      
      // 计算相对亮度
      const luminance1 = this.calculateRelativeLuminance(rgb1);
      const luminance2 = this.calculateRelativeLuminance(rgb2);
      
      // 计算对比度
      const lighter = Math.max(luminance1, luminance2);
      const darker = Math.min(luminance1, luminance2);
      
      return (lighter + 0.05) / (darker + 0.05);
    } catch (error) {
      return 1;
    }
  }
  
  /**
   * 计算相对亮度
   * @param rgb RGB颜色数组
   * @returns 相对亮度值
   */
  private static calculateRelativeLuminance(rgb: number[]): number {
    const [r, g, b] = rgb.map(value => {
      const normalized = value / 255;
      return normalized <= 0.03928
        ? normalized / 12.92
        : Math.pow((normalized + 0.055) / 1.055, 2.4);
    });
    
    return 0.2126 * r + 0.7152 * g + 0.0722 * b;
  }
  
  /**
   * 验证颜色对比度是否符合WCAG标准
   * @param foreground 前景色
   * @param background 背景色
   * @param level WCAG级别('AA'或'AAA')
   * @returns 是否符合标准
   */
  static validateContrast(
    foreground: string, 
    background: string, 
    level: 'AA' | 'AAA' = 'AA'
  ): boolean {
    const contrast = this.calculateContrast(foreground, background);
    
    if (level === 'AA') {
      return contrast >= 4.5; // WCAG AA标准
    } else {
      return contrast >= 7; // WCAG AAA标准
    }
  }
}

6.2 主题切换动画

// animation/ThemeTransitionAnimation.ts
import { Curve, CurveType } from '@kit.ArkGraphics2D';

/**
 * 主题切换动画配置
 */
export interface ThemeAnimationConfig {
  duration: number;      // 动画持续时间(毫秒)
  curve: Curve;         // 动画曲线
  opacity: boolean;     // 是否包含透明度动画
  scale: boolean;       // 是否包含缩放动画
  translate: boolean;   // 是否包含位移动画
}

/**
 * 主题切换动画管理器
 */
export class ThemeTransitionAnimation {
  private static defaultConfig: ThemeAnimationConfig = {
    duration: 300,
    curve: Curve.EaseInOut,
    opacity: true,
    scale: false,
    translate: false
  };
  
  /**
   * 创建主题切换动画
   * @param config 动画配置
   * @returns 动画对象
   */
  static createAnimation(config?: Partial<ThemeAnimationConfig>): any {
    const finalConfig = { ...this.defaultConfig, ...config };
    
    return {
      duration: finalConfig.duration,
      curve: finalConfig.curve,
      animations: this.buildAnimations(finalConfig)
    };
  }
  
  /**
   * 构建动画数组
   * @param config 动画配置
   * @returns 动画数组
   */
  private static buildAnimations(config: ThemeAnimationConfig): any[] {
    const animations: any[] = [];
    
    if (config.opacity) {
      animations.push({
        attribute: 'opacity',
        from: 0.8,
        to: 1,
        duration: config.duration,
        curve: config.curve
      });
    }
    
    if (config.scale) {
      animations.push({
        attribute: 'scale',
        from: { x: 0.95, y: 0.95 },
        to: { x: 1, y: 1 },
        duration: config.duration,
        curve: config.curve
      });
    }
    
    if (config.translate) {
      animations.push({
        attribute: 'position',
        from: { x: 0, y: 10 },
        to: { x: 0, y: 0 },
        duration: config.duration,
        curve: config.curve
      });
    }
    
    return animations;
  }
  
  /**
   * 创建交叉淡入淡出动画
   */
  static createCrossFadeAnimation(): any {
    return {
      duration: 300,
      curve: Curve.EaseInOut,
      animations: [
        {
          attribute: 'opacity',
          from: 0,
          to: 1,
          duration: 300,
          curve: Curve.EaseInOut
        }
      ]
    };
  }
  
  /**
   * 创建滑动切换动画
   */
  static createSlideAnimation(direction: 'left' | 'right' | 'up' | 'down' = 'right'): any {
    let fromX = 0;
    let fromY = 0;
    let toX = 0;
    let toY = 0;
    
    switch (direction) {
      case 'left':
        fromX = 50;
        toX = 0;
        break;
      case 'right':
        fromX = -50;
        toX = 0;
        break;
      case 'up':
        fromY = 50;
        toY = 0;
        break;
      case 'down':
        fromY = -50;
        toY = 0;
        break;
    }
    
    return {
      duration: 400,
      curve: Curve.CubicBezier(0.25, 0.1, 0.25, 1),
      animations: [
        {
          attribute: 'position',
          from: { x: fromX, y: fromY },
          to: { x: toX, y: toY },
          duration: 400
        },
        {
          attribute: 'opacity',
          from: 0.5,
          to: 1,
          duration: 400
        }
      ]
    };
  }
}

七、性能优化与最佳实践

7.1 主题切换性能优化策略

优化策略 实施方法 预期效果
资源预加载 提前加载深色/浅色主题资源 减少切换时的加载延迟
缓存管理 缓存已加载的主题资源 避免重复加载,提升响应速度
异步处理 使用异步操作处理主题切换 防止UI线程阻塞
增量更新 只更新变化的组件 减少不必要的重绘
懒加载 延迟加载非关键资源 加快初始加载速度

7.2 主题切换性能对比数据

// 性能监控数据
export interface ThemeSwitchPerformance {
  timestamp: number;           // 切换时间戳
  theme: ThemeMode;           // 目标主题
  duration: number;           // 切换耗时(毫秒)
  memoryUsage: number;        // 内存使用变化(KB)
  renderCount: number;        // 重新渲染的组件数量
  success: boolean;           // 是否成功
}

// 性能优化实现
export class ThemePerformanceMonitor {
  private static instance: ThemePerformanceMonitor;
  private performanceData: ThemeSwitchPerformance[] = [];
  private readonly MAX_RECORDS = 100;
  
  public static getInstance(): ThemePerformanceMonitor {
    if (!ThemePerformanceMonitor.instance) {
      ThemePerformanceMonitor.instance = new ThemePerformanceMonitor();
    }
    return ThemePerformanceMonitor.instance;
  }
  
  /**
   * 记录主题切换性能数据
   */
  public recordSwitchPerformance(
    theme: ThemeMode,
    duration: number,
    memoryUsage: number,
    renderCount: number,
    success: boolean
  ): void {
    const record: ThemeSwitchPerformance = {
      timestamp: Date.now(),
      theme,
      duration,
      memoryUsage,
      renderCount,
      success
    };
    
    this.performanceData.push(record);
    
    // 保持记录数量不超过最大值
    if (this.performanceData.length > this.MAX_RECORDS) {
      this.performanceData.shift();
    }
    
    // 输出性能报告
    if (duration > 100) { // 如果切换时间超过100ms,记录警告
      Logger.warn('ThemePerformanceMonitor', 
        `Slow theme switch detected: ${duration}ms for ${theme} theme`);
    }
  }
  
  /**
   * 获取平均切换时间
   */
  public getAverageSwitchTime(): number {
    if (this.performanceData.length === 0) {
      return 0;
    }
    
    const total = this.performanceData.reduce((sum, record) => sum + record.duration, 0);
    return total / this.performanceData.length;
  }
  
  /**
   * 获取性能报告
   */
  public getPerformanceReport(): string {
    const totalSwitches = this.performanceData.length;
    const successfulSwitches = this.performanceData.filter(r => r.success).length;
    const averageTime = this.getAverageSwitchTime();
    
    return `主题切换性能报告:
总切换次数:${totalSwitches}
成功次数:${successfulSwitches}
平均切换时间:${averageTime.toFixed(2)}ms
最近5次切换:${this.performanceData.slice(-5).map(r => r.duration + 'ms').join(', ')}`;
  }
}

八、测试与验证

8.1 主题功能测试用例

// test/ThemeManager.test.ts
import { describe, it, expect, beforeEach, afterEach } from '@ohos/hypium';
import { ThemeManager, ThemeMode } from '../theme/ThemeManager';

@Entry
@Component
struct ThemeManagerTest {
  private themeManager: ThemeManager = ThemeManager.getInstance();
  
  @Test
  async testThemeSwitch() {
    // 测试浅色模式切换
    const lightResult = await this.themeManager.switchTheme(ThemeMode.LIGHT);
    expect(lightResult).assertTrue();
    expect(this.themeManager.getCurrentTheme()).assertEqual(ThemeMode.LIGHT);
    
    // 测试深色模式切换
    const darkResult = await this.themeManager.switchTheme(ThemeMode.DARK);
    expect(darkResult).assertTrue();
    expect(this.themeManager.getCurrentTheme()).assertEqual(ThemeMode.DARK);
    
    // 测试系统模式切换
    const systemResult = await this.themeManager.switchTheme(ThemeMode.SYSTEM);
    expect(systemResult).assertTrue();
    expect(this.themeManager.getCurrentTheme()).assertEqual(ThemeMode.SYSTEM);
  }
  
  @Test
  async testThemePersistence() {
    // 切换到深色模式并保存
    await this.themeManager.switchTheme(ThemeMode.DARK);
    
    // 模拟应用重启(重新初始化ThemeManager)
    const newThemeManager = ThemeManager.getInstance();
    await newThemeManager.initialize(this.context);
    
    // 验证主题设置被正确恢复
    expect(newThemeManager.getCurrentTheme()).assertEqual(ThemeMode.DARK);
  }
  
  @Test
  async testThemeListeners() {
    let listenerCalled = false;
    let lastTheme: ThemeMode = ThemeMode.LIGHT;
    
    const listener = (theme: ThemeMode) => {
      listenerCalled = true;
      lastTheme = theme;
    };
    
    // 添加监听器
    this.themeManager.addThemeChangeListener(listener);
    
    // 切换主题
    await this.themeManager.switchTheme(ThemeMode.DARK);
    
    // 验证监听器被调用
    expect(listenerCalled).assertTrue();
    expect(lastTheme).assertEqual(ThemeMode.DARK);
    
    // 移除监听器
    this.themeManager.removeThemeChangeListener(listener);
  }
  
  @Test
  async testInvalidTheme() {
    try {
      // 尝试切换无效主题
      // @ts-ignore
      await this.themeManager.switchTheme('invalid_theme');
      expect(true).assertFalse(); // 应该不会执行到这里
    } catch (error) {
      expect(error).assertNotNull();
    }
  }
  
  @Test
  async testThemeConfig() {
    await this.themeManager.switchTheme(ThemeMode.DARK);
    const config = this.themeManager.getThemeConfig();
    
    expect(config.mode).assertEqual(ThemeMode.DARK);
    expect(config.primaryColor).assertNotUndefined();
    expect(config.secondaryColor).assertNotUndefined();
    expect(config.fontSizeScale).assertNotUndefined();
    expect(config.borderRadiusScale).assertNotUndefined();
  }
}

8.2 视觉对比测试

测试场景 浅色模式截图 深色模式截图 通过标准
首页界面 [浅色截图] [深色截图] 颜色对比度符合WCAG AA标准
设置页面 [浅色截图] [深色截图] 所有元素清晰可见,无视觉干扰
弹窗提示 [浅色截图] [深色截图] 背景叠加效果正确
夜间模式 [浅色截图] [深色截图] 亮度适宜,不刺眼

九、部署与维护

9.1 主题配置部署流程

设计主题方案

创建资源文件

实现主题管理器

编写测试用例

性能优化

代码审查

集成测试

用户测试

正式发布

监控分析

持续优化

9.2 主题系统维护检查表

  • 定期检查颜色对比度是否符合最新WCAG标准
  • 更新深色模式资源以匹配设计系统更新
  • 监控主题切换性能指标
  • 收集用户反馈并进行优化
  • 确保新功能正确支持深色模式
  • 维护主题切换动画的流畅性
  • 定期备份主题配置数据

十、总结

本方案为HarmonyOS“面试通”应用提供了完整的深色模式主题管理系统,具有以下特点:

  1. 架构清晰:采用分层架构设计,分离关注点,便于维护和扩展
  2. 性能优异:通过资源预加载、缓存管理等技术优化主题切换性能
  3. 体验流畅:支持平滑的主题切换动画,提升用户体验
  4. 安全可靠:所有主题配置都经过持久化存储,避免数据丢失
  5. 易于扩展:支持自定义主题和动态主题生成,满足未来需求
  6. 符合标准:遵循HarmonyOS官方开发规范,确保代码质量

通过本方案的实施,“面试通”应用可以:

  • 为用户提供舒适的夜间使用体验
  • 节省设备电量,延长续航时间
  • 提升应用的专业性和现代感
  • 满足不同用户的个性化需求
  • 为未来的主题商店等功能奠定基础

如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏
嘻嘻嘻,关注我!!!黑马波哥
也可以关注我的抖音号: 黑马程序员burger 在直播间交流(17:00-21:00)

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐