HarmonyOS鸿蒙Next中如何实现深色模式适配?

HarmonyOS鸿蒙Next中如何实现深色模式适配? 应用开发中,怎样实现字体、背景等相关的深色模式适配?

5 回复

直接使用$资源引用颜色/图片就好了

解决方案:resource/base/element/color.json和resource/dark/element/color.json分别配置好浅色、深色的颜色资源(同用途的颜色记得统一命名),UI里面使用资源引用就好了,图片同理

更多关于HarmonyOS鸿蒙Next中如何实现深色模式适配?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


直接使用鸿蒙动态颜色资源:

Text('系统级动态文本') .fontColor($r('sys.color.ohos_id_color_text_primary')) // 自动适配 .backgroundColor($r('sys.color.ohos_id_color_background'))

常用系统资源:

  • ohos_id_color_text_primary(主文本色)
  • ohos_id_color_background(背景色)
  • ohos_id_color_foreground(前景色)

解决方案

1. 技术架构

┌─────────────────────────────────────┐
│  AppColors (动态颜色管理类)          │
│  - 浅色配色类                       │
│  - 深色配色类                       │
│  - 动态getter属性                   │
└─────────────────────────────────────┘
                 ↕
┌─────────────────────────────────────┐
│  AppSettings (主题设置服务)          │
│  - 保存主题模式                     │
│  - 判断深浅色                       │
└─────────────────────────────────────┘
                 ↕
┌─────────────────────────────────────┐
│  UI组件                             │
│  - 使用AppColors动态颜色             │
│  - 监听主题变化                     │
│  - 更新状态栏                       │
└─────────────────────────────────────┘

2. 完整实现代码

步骤1: 创建动态颜色管理类

/**
 * 应用颜色配置类
 * 支持深浅色动态切换
 */
export class AppColors {
  private static isDarkMode: boolean = false;
  
  /**
   * 设置深色模式
   */
  static setDarkMode(isDark: boolean): void {
    AppColors.isDarkMode = isDark;
  }
  
  /**
   * 判断是否深色模式
   */
  static isDark(): boolean {
    return AppColors.isDarkMode;
  }
  
  /**
   * 获取当前颜色类
   */
  private static getCurrentColorClass() {
    return AppColors.isDarkMode ? DarkModeColors : LightModeColors;
  }
  
  // ========== 动态颜色属性 ==========
  
  /**
   * 主背景色
   */
  static get BG_PRIMARY(): string {
    return AppColors.getCurrentColorClass().BG_PRIMARY;
  }
  
  /**
   * 卡片背景色
   */
  static get BG_CARD(): string {
    return AppColors.getCurrentColorClass().BG_CARD;
  }
  
  /**
   * 主文字颜色
   */
  static get TEXT_PRIMARY(): string {
    return AppColors.getCurrentColorClass().TEXT_PRIMARY;
  }
  
  /**
   * 次要文字颜色
   */
  static get TEXT_SECONDARY(): string {
    return AppColors.getCurrentColorClass().TEXT_SECONDARY;
  }
  
  /**
   * 辅助文字颜色
   */
  static get TEXT_TERTIARY(): string {
    return AppColors.getCurrentColorClass().TEXT_TERTIARY;
  }
  
  /**
   * 分割线颜色
   */
  static get DIVIDER(): string {
    return AppColors.getCurrentColorClass().DIVIDER;
  }
  
  /**
   * 阴影颜色
   */
  static get SHADOW(): string {
    return AppColors.getCurrentColorClass().SHADOW;
  }
}

/**
 * 浅色模式配色
 */
class LightModeColors {
  static readonly BG_PRIMARY = '#FFFCF7';       // 米白色背景
  static readonly BG_CARD = '#FFFFFF';          // 纯白色卡片
  static readonly TEXT_PRIMARY = '#2D1F15';     // 深褐色文字
  static readonly TEXT_SECONDARY = '#6B5A48';   // 中褐色文字
  static readonly TEXT_TERTIARY = '#A89B8C';    // 浅褐色文字
  static readonly DIVIDER = '#F0E5D8';          // 淡边框
  static readonly SHADOW = 'rgba(0, 0, 0, 0.06)';  // 淡阴影
}

/**
 * 深色模式配色
 */
class DarkModeColors {
  static readonly BG_PRIMARY = '#1A1A1A';       // 深灰背景
  static readonly BG_CARD = '#2C2C2C';          // 卡片背景
  static readonly TEXT_PRIMARY = '#F0F0F0';     // 浅灰文字
  static readonly TEXT_SECONDARY = '#C8C8C8';   // 中灰文字
  static readonly TEXT_TERTIARY = '#999999';    // 深灰文字
  static readonly DIVIDER = '#3A3A3A';          // 深色边框
  static readonly SHADOW = 'rgba(0, 0, 0, 0.3)';  // 深色阴影
}

步骤2: 创建主题设置服务

import { preferences } from '@kit.ArkData';

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

/**
 * 应用设置服务
 */
export class AppSettings {
  private static instance: AppSettings;
  private dataPreferences: preferences.Preferences | null = null;
  private readonly THEME_MODE_KEY = 'theme_mode';
  
  private constructor() {}
  
  static getInstance(): AppSettings {
    if (!AppSettings.instance) {
      AppSettings.instance = new AppSettings();
    }
    return AppSettings.instance;
  }
  
  /**
   * 初始化
   */
  async init(context: Context): Promise<void> {
    this.dataPreferences = await preferences.getPreferences(context, 'app_settings');
  }
  
  /**
   * 获取主题模式
   */
  async getThemeMode(): Promise<ThemeMode> {
    if (!this.dataPreferences) {
      return ThemeMode.LIGHT;
    }
    
    const mode = await this.dataPreferences.get(this.THEME_MODE_KEY, ThemeMode.LIGHT);
    return mode as ThemeMode;
  }
  
  /**
   * 设置主题模式
   */
  async setThemeMode(mode: ThemeMode): Promise<void> {
    if (!this.dataPreferences) {
      return;
    }
    
    await this.dataPreferences.put(this.THEME_MODE_KEY, mode);
    await this.dataPreferences.flush();
  }
  
  /**
   * 判断是否使用深色模式
   */
  shouldUseDarkMode(mode: ThemeMode): boolean {
    if (mode === ThemeMode.LIGHT) {
      return false;
    } else if (mode === ThemeMode.DARK) {
      return true;
    } else {
      // AUTO: 可以获取系统设置
      // 这里简化为返回false,实际可以检测系统设置
      return false;
    }
  }
}

步骤3: 在EntryAbility中初始化并设置状态栏

import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { AppSettings } from '../services/AppSettings';
import { AppColors } from '../common/constants/AppColors';

export default class EntryAbility extends UIAbility {
  async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
    // 初始化设置
    await AppSettings.getInstance().init(this.context);
    
    // 获取主题模式
    const themeMode = await AppSettings.getInstance().getThemeMode();
    const isDark = AppSettings.getInstance().shouldUseDarkMode(themeMode);
    
    // 设置AppColors
    AppColors.setDarkMode(isDark);
    
    // ✅ 关键: 设置状态栏颜色
    try {
      const mainWindow = windowStage.getMainWindowSync();
      
      await mainWindow.setWindowSystemBarProperties({
        statusBarColor: isDark ? '#000000' : '#FFFFFF',
        statusBarContentColor: isDark ? '#FFFFFF' : '#000000',
        navigationBarColor: isDark ? '#000000' : '#FFFFFF',
        navigationBarContentColor: isDark ? '#FFFFFF' : '#000000'
      });
      
      console.info('状态栏颜色设置成功, 深色模式:', isDark);
    } catch (err) {
      console.error('设置状态栏失败:', JSON.stringify(err));
    }
    
    windowStage.loadContent('pages/Index');
  }
}

步骤4: 主页面监听主题变化

import { window } from '@kit.ArkUI';
import { AppColors } from '../common/constants/AppColors';

@Entry
@Component
struct Index {
  @State isDarkMode: boolean = false;
  
  /**
   * 主题变化回调
   */
  private async onThemeChanged(isDark: boolean): Promise<void> {
    this.isDarkMode = isDark;
    console.info('主题已切换:', isDark ? '深色' : '浅色');
    
    // ✅ 更新状态栏颜色
    try {
      const mainWindow = await window.getLastWindow(getContext(this));
      
      await mainWindow.setWindowSystemBarProperties({
        statusBarColor: isDark ? '#000000' : '#FFFFFF',
        statusBarContentColor: isDark ? '#FFFFFF' : '#000000',
        navigationBarColor: isDark ? '#000000' : '#FFFFFF',
        navigationBarContentColor: isDark ? '#FFFFFF' : '#000000'
      });
      
      console.info('状态栏颜色已更新');
    } catch (err) {
      console.error('更新状态栏失败:', JSON.stringify(err));
    }
  }
  
  build() {
    Column() {
      // 页面内容
      // ...
    }
    .width('100%')
    .height('100%')
    .backgroundColor(AppColors.BG_PRIMARY)  // ✅ 使用动态颜色
  }
}

步骤5: 设置页面实现主题切换

import { AppSettings, ThemeMode } from '../services/AppSettings';
import { AppColors } from '../common/constants/AppColors';

@Component
export struct SettingsPage {
  @State currentThemeMode: ThemeMode = ThemeMode.LIGHT;
  private appSettings: AppSettings = AppSettings.getInstance();
  
  // 主题变化回调函数
  onThemeChange?: (isDark: boolean) => void;
  
  async aboutToAppear(): Promise<void> {
    this.currentThemeMode = await this.appSettings.getThemeMode();
  }
  
  /**
   * 切换主题模式
   */
  private async changeThemeMode(mode: ThemeMode): Promise<void> {
    this.currentThemeMode = mode;
    
    // 保存设置
    await this.appSettings.setThemeMode(mode);
    
    // 判断是否深色模式
    const isDark = this.appSettings.shouldUseDarkMode(mode);
    
    // 更新AppColors
    AppColors.setDarkMode(isDark);
    
    // 通知父组件更新状态栏
    if (this.onThemeChange) {
      this.onThemeChange(isDark);
    }
  }
  
  build() {
    Column({ space: 16 }) {
      Text('主题设置')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor(AppColors.TEXT_PRIMARY);  // ✅ 动态颜色
      
      // 浅色模式
      Row() {
        Text('☀️ 浅色模式')
          .fontSize(15)
          .fontColor(AppColors.TEXT_PRIMARY);
        
        Blank();
        
        if (this.currentThemeMode === ThemeMode.LIGHT) {
          Text('✓').fontSize(20).fontColor('#FF6B3D');
        }
      }
      .width('100%')
      .padding(14)
      .backgroundColor(AppColors.BG_CARD)  // ✅ 动态颜色
      .borderRadius(12)
      .onClick(() => this.changeThemeMode(ThemeMode.LIGHT))
      
      // 深色模式
      Row() {
        Text('🌙 深色模式')
          .fontSize(15)
          .fontColor(AppColors.TEXT_PRIMARY);
        
        Blank();
        
        if (this.currentThemeMode === ThemeMode.DARK) {
          Text('✓').fontSize(20).fontColor('#FF6B3D');
        }
      }
      .width('100%')
      .padding(14)
      .backgroundColor(AppColors.BG_CARD)
      .borderRadius(12)
      .onClick(() => this.changeThemeMode(ThemeMode.DARK))
      
      // 跟随系统
      Row() {
        Text('⚙️ 跟随系统')
          .fontSize(15)
          .fontColor(AppColors.TEXT_PRIMARY);
        
        Blank();
        
        if (this.currentThemeMode === ThemeMode.AUTO) {
          Text('✓').fontSize(20).fontColor('#FF6B3D');
        }
      }
      .width('100%')
      .padding(14)
      .backgroundColor(AppColors.BG_CARD)
      .borderRadius(12)
      .onClick(() => this.changeThemeMode(ThemeMode.AUTO))
    }
    .width('100%')
    .padding(16)
  }
}

步骤6: UI组件使用动态颜色

@Component
struct ItemCard {
  @Prop item: Item;
  
  build() {
    Row() {
      Column({ space: 4 }) {
        // ✅ 所有颜色都使用AppColors动态属性
        Text(this.item.name)
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .fontColor(AppColors.TEXT_PRIMARY);  // 主文字
        
        Text(`数量: ${this.item.quantity}`)
          .fontSize(14)
          .fontColor(AppColors.TEXT_SECONDARY);  // 次要文字
        
        Text('备注信息')
          .fontSize(12)
          .fontColor(AppColors.TEXT_TERTIARY);  // 辅助文字
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)
    }
    .width('100%')
    .padding(16)
    .backgroundColor(AppColors.BG_CARD)  // 卡片背景
    .borderRadius(12)
    .border({ width: 1, color: AppColors.DIVIDER })  // 边框
    .shadow({
      radius: 8,
      color: AppColors.SHADOW,  // 阴影
      offsetY: 2
    })
  }
}

关键要点

1. 状态栏适配是关键

必须设置的地方:

  • EntryAbility初始化时
  • 主题切换时
await mainWindow.setWindowSystemBarProperties({
  statusBarColor: isDark ? '#000000' : '#FFFFFF',
  statusBarContentColor: isDark ? '#FFFFFF' : '#000000'
});

2. 颜色管理集中化

使用AppColors统一管理:

// ✅ 推荐
.fontColor(AppColors.TEXT_PRIMARY)

// ❌ 不推荐
.fontColor('#2D1F15')  // 硬编码

3. 深色模式配色原则

背景色:

  • ❌ 不使用纯黑 #000000 作为主背景
  • ✅ 使用深灰 #1A1A1A
  • ✅ 卡片用更浅的灰 #2C2C2C

文字对比度:

  • 主文字: #F0F0F0 on #1A1A1A ≈ 13.9:1 ✅
  • 次要文字: #C8C8C8 on #1A1A1A ≈ 9.4:1 ✅
  • 辅助文字: #999999 on #1A1A1A ≈ 5.1:1 ✅

4. 动态颜色实现原理

export class AppColors {
  private static isDarkMode: boolean = false;
  
  // 通过getter实现动态切换
  static get BG_PRIMARY(): string {
    return this.isDarkMode ? '#1A1A1A' : '#FFFCF7';
  }
}

优势:

  • UI组件无需修改代码
  • 切换主题时自动更新
  • 类型安全

最佳实践

1. 避免硬编码颜色

错误示例:

Text('标题')
  .fontColor('#333333')  // 深色模式下看不清
  .backgroundColor('#FFFFFF')  // 深色模式下刺眼

正确示例:

Text('标题')
  .fontColor(AppColors.TEXT_PRIMARY)
  .backgroundColor(AppColors.BG_CARD)

2. 卡片背景统一

浅色模式: 所有卡片用白色 深色模式: 所有卡片用深灰

Column() {
  // 统计卡片
  this.buildCard();
  
  // 列表卡片
  this.buildCard();
  
  // 详情卡片
  this.buildCard();
}

@Builder
buildCard() {
  Column() {
    // ...
  }
  .backgroundColor(AppColors.BG_CARD)  // ✅ 统一使用
  .borderRadius(12)
}

3. 边框和阴影

深色模式下边框更重要:

Column() {
  // ...
}
.border({ 
  width: 1, 
  color: AppColors.DIVIDER  // 深色模式: #3A3A3A
})
.shadow({
  radius: 8,
  color: AppColors.SHADOW,  // 深色模式: rgba(0,0,0,0.3)
  offsetY: 2
})

4. 图标颜色适配

Image($r('app.media.icon'))
  .fillColor(AppColors.TEXT_PRIMARY)  // 图标也要动态颜色

常见问题

Q1: 为什么状态栏颜色没变?

检查两个地方:

  1. EntryAbility中是否设置
  2. 主题切换时是否更新
// EntryAbility.ets
async onWindowStageCreate(windowStage: window.WindowStage) {
  // ✅ 第一处: 应用启动时设置
  const mainWindow = windowStage.getMainWindowSync();
  await mainWindow.setWindowSystemBarProperties({...});
}

// Index.ets
private async onThemeChanged(isDark: boolean) {
  // ✅ 第二处: 主题切换时更新
  const mainWindow = await window.getLastWindow(getContext(this));
  await mainWindow.setWindowSystemBarProperties({...});
}

Q2: 切换主题后部分颜色没变?

检查是否有硬编码颜色:

// ❌ 硬编码,不会动态切换
.fontColor('#333333')

// ✅ 动态颜色,会自动切换
.fontColor(AppColors.TEXT_PRIMARY)

Q3: 深色模式下文字看不清?

检查对比度是否足够:

// ❌ 对比度不足
static readonly TEXT_PRIMARY = '#

在HarmonyOS Next中,通过AppStorageLocalStorage管理深色模式状态。使用@Styles@Extend定义深色/浅色主题样式,通过@State@Prop响应式更新UI。资源文件使用darklight目录区分主题资源,系统自动匹配。

在HarmonyOS Next中,实现深色模式适配主要依靠资源管理和响应式UI框架。以下是核心方法:

  1. 资源目录管理:在resources目录下分别创建darklight子目录,存放对应模式的资源文件(如颜色、图片)。系统会根据当前模式自动加载对应目录的资源。

  2. 颜色资源定义:在resource/color中定义颜色资源,例如在light目录下定义background_color为浅色,在dark目录下定义同名资源为深色。在UI中引用$color('background_color')即可自动适配。

  3. ArkUI状态管理:使用@StorageLink@LocalStorageLink绑定颜色变量,结合modifier中的.backgroundColor()等方法动态设置。可通过window.getWindowMode()获取当前深浅模式,触发UI更新。

  4. 媒体查询:使用mediaQuery API监听颜色模式变化,例如:

    mediaQuery.matchMedia('(prefers-color-scheme: dark)').on('change', (result) => {
      // 根据result.matches更新UI状态
    })
    
  5. 组件级适配:对于自定义组件,可通过@Styles装饰器定义不同模式下的样式集,或使用if/else条件渲染不同UI结构。

关键点:避免在代码中硬编码颜色值,始终引用资源ID;测试时可通过“设置 > 显示和亮度 > 深色模式”或开发板的模拟切换功能验证适配效果。

回到顶部