相信大家在开发中都会接触到主题切换功能,这是提升用户体验的必备功能了,尤其是夜间模式与日间模式的自动切换,能有效适配不同使用场景与用户偏好。鸿蒙系统(HarmonyOS)为开发者提供了完善的 API 体系,支持应用轻松实现动态主题切换,既可以跟随系统配置自动调整,也能允许用户手动切换。这里我将从核心 API 解析、具体实现步骤、关键注意事项三个维度,详解鸿蒙应用动态主题切换的开发流程。

 

一、鸿蒙动态主题切换的核心 API 解析

鸿蒙系统通过轻量化的 API 组合,实现主题状态的监听、资源匹配与 UI 更新,核心涉及三类关键能力:

1. Configuration 类:主题状态的 “感知器”

Configuration是鸿蒙系统管理设备配置信息的核心类,通过getColorMode()方法可实时获取当前系统主题模式,返回值包含三种状态:

  • ColorMode.LIGHT:日间模式(默认)
  • ColorMode.DARK:夜间模式
  • ColorMode.AUTO:跟随系统自动切换(根据时间或系统设置动态调整)

当系统主题发生变化时,Configuration会同步更新状态,为应用提供准确的主题判断依据。在 ArkTS 中,可通过context.config快速获取当前配置信息。

2. onConfigurationUpdate 回调:主题变化的 “触发器”

在 Stage 模型的Ability或Page中,重写onConfigurationUpdate(config: Configuration)方法,可监听系统配置(包括主题)的变化。当用户手动切换系统主题,或系统根据时间自动切换主题时,该方法会被触发,开发者可在此处编写主题更新逻辑,实现 UI 的实时适配。

3. 资源管理:主题资源的 “调度员”

鸿蒙系统通过资源目录的差异化设计(如base与night目录),实现主题资源的自动匹配。在 ArkTS 中,无需手动调用ResourceManager,直接通过$r('app.color.xxx')引用资源时,系统会根据当前主题模式,自动从base(日间)或night(夜间)目录中读取对应资源,简化开发流程。

二、动态主题切换的完整实现步骤

以 “日间 / 夜间模式自动切换” 为例,结合 “应用内手动切换” 功能,基于 ArkTS(Stage 模型)分五步完成开发:

1. 准备差异化主题资源

在项目main_pages同级的resources目录下,创建两套资源目录,分别存放日间与夜间模式的资源(需保证资源名称一致,目录结构相同):

  • 默认(日间)资源:resources/base/element,在color.json中定义主题色,示例:
{
  "color": [
    {
      "name": "text_color",
      "value": "#18181B" // 深灰色(日间文本)
    },
    {
      "name": "bg_color",
      "value": "#FFFFFF" // 白色(日间背景)
    }
  ]
}
  • 夜间资源:resources/night/element,复制base目录的color.json结构,重新定义同名资源,示例:
{
  "color": [
    {
      "name": "text_color",
      "value": "#F5F5F7" // 浅灰色(夜间文本)
    },
    {
      "name": "bg_color",
      "value": "#1C1C1E" // 深黑色(夜间背景)
    }
  ]
}

通过 “同名资源、不同目录” 的设计,系统会根据当前主题自动匹配资源,避免硬编码导致的主题切换失效问题。

2. 在 Ability 中监听主题变化

Ability作为应用的全局入口,负责主题状态的监听与全局分发。在EntryAbility中重写onConfigurationUpdate方法,实现主题变化的监听,并通过事件总线(或全局状态)通知所有页面更新 UI:

import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import hilog from '@ohos.hilog';
import UIAbility from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import { Configuration, ColorMode } from '@ohos.app.ability.Configuration';
import emitter from '@ohos.events.emitter'; // 事件总线,用于通知页面更新

export default class EntryAbility extends UIAbility {
  // 应用启动时初始化主题
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
    this.initTheme(); // 初始化当前主题
  }

  // 监听系统主题变化
  onConfigurationUpdate(newConfig: Configuration): void {
    super.onConfigurationUpdate(newConfig);
    const currentMode = newConfig.colorMode; // 获取当前主题模式
    // 通过事件总线发送主题变化事件
    const event: emitter.Event = {
      eventId: 1001, // 自定义事件ID,用于页面识别
      data: {
        themeMode: currentMode
      }
    };
    emitter.emit(event);
  }

  // 初始化主题:获取当前系统主题并发送事件
  private initTheme(): void {
    const currentConfig = this.context.config; // 获取当前配置
    const event: emitter.Event = {
      eventId: 1001,
      data: {
        themeMode: currentConfig.colorMode
      }
    };
    emitter.emit(event);
  }

  // ... 其他生命周期方法(onWindowStageCreate等)省略
}

3. 在 Page 页面中实现 UI 适配

Page页面负责具体的 UI 渲染,需通过事件总线监听主题变化,并根据主题模式调整组件样式。以Index页面为例,实现文本、背景色的动态切换:

import router from '@ohos.router';
import emitter from '@ohos.events.emitter';
import { ColorMode } from '@ohos.app.ability.Configuration';

@Entry
@Component
struct Index {
  // 主题模式状态:默认日间模式
  @State themeMode: ColorMode = ColorMode.LIGHT;

  // 页面加载时监听主题变化事件
  aboutToAppear(): void {
    // 订阅主题变化事件
    const onThemeChange = (event: emitter.EventData) => {
      this.themeMode = event.data.themeMode as ColorMode; // 更新主题状态
    };
    emitter.on('themeChange', 1001, onThemeChange); // 绑定事件ID 1001
  }

  // 页面销毁时取消事件订阅
  aboutToDisappear(): void {
    emitter.off('themeChange', 1001); // 避免内存泄漏
  }

  build() {
    Row({ space: 20 }) {
      Column({ space: 20 }) {
        // 动态主题文本:根据themeMode切换颜色
        Text('鸿蒙动态主题示例')
          .fontSize(24)
          .fontColor($r('app.color.text_color')) // 引用主题资源
          .backgroundColor($r('app.color.bg_color')) // 引用主题资源
          .padding(20)
          .borderRadius(10)

        // 手动切换主题按钮
        Button('切换夜间模式')
          .width(200)
          .height(50)
          .onClick(() => {
            this.switchTheme(ColorMode.DARK); // 切换为夜间模式
          })

        Button('切换日间模式')
          .width(200)
          .height(50)
          .onClick(() => {
            this.switchTheme(ColorMode.LIGHT); // 切换为日间模式
          })
      }
      .width('100%')
    }
    .height('100%')
    .backgroundColor($r('app.color.bg_color')) // 页面背景跟随主题
  }

  // 手动切换主题方法
  private switchTheme(targetMode: ColorMode): void {
    // 获取当前应用配置
    const context = getContext(this) as any;
    const config = context.config;
    config.colorMode = targetMode; // 设置目标主题模式
    // 更新配置,触发主题切换
    context.updateConfiguration(config, (err: Error) => {
      if (err) {
        console.error('切换主题失败:', err.message);
        return;
      }
      console.log('主题切换成功,模式:', targetMode);
    });
  }
}

4. 配置文件声明主题支持

在module.json5的abilities节点中,添加supportedModes配置,声明应用支持的主题模式,确保系统能正确识别并触发主题切换:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": ["phone", "tablet"],
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntrance": "./src/main/ets/entryability/EntryAbility.ts",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:icon",
        "label": "$string:EntryAbility_label",
        "visible": true,
        "supportedModes": ["auto", "light", "dark"] // 支持自动、日间、夜间模式
      }
    ]
  }
}

5. 主题状态持久化(可选)

若需实现 “应用重启后保留上次主题” 的功能,可通过Preferences存储主题模式,在应用启动时读取并恢复:

import preferences from '@ohos.data.preferences';

// 保存主题模式到Preferences
private async saveThemeMode(mode: ColorMode): Promise<void> {
  const context = getContext(this) as any;
  const pref = await preferences.getPreferences(context, 'theme_pref');
  await pref.put('saved_theme_mode', mode);
  await pref.flush(); // 持久化保存
}

// 从Preferences读取主题模式
private async getSavedThemeMode(): Promise<ColorMode> {
  const context = getContext(this) as any;
  const pref = await preferences.getPreferences(context, 'theme_pref');
  return await pref.get('saved_theme_mode', ColorMode.LIGHT) as ColorMode;
}

// 在Ability的onCreate中恢复主题
async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  const savedMode = await this.getSavedThemeMode();
  const config = this.context.config;
  config.colorMode = savedMode;
  this.context.updateConfiguration(config); // 恢复上次主题
}

三、关键注意事项与优化建议

资源引用规范:所有主题相关的颜色、图片、尺寸等资源,必须通过$r('app.xxx.xxx')引用(如$r('app.color.text_color')),而非直接写死数值(如#FFFFFF),否则系统无法自动匹配主题资源。

事件总线管理:使用emitter时,需在页面aboutToDisappear中调用emitter.off取消订阅,避免内存泄漏;若应用页面较多,建议封装全局事件管理工具,统一处理主题事件。

多页面同步:若应用包含多个Page,需确保所有页面都订阅主题变化事件,或通过AppStorage/LocalStorage实现全局状态共享,避免部分页面主题不更新的问题。

低版本适配:鸿蒙 API 9 及以上版本支持ColorMode.AUTO,若需适配 API 8 及以下版本,建议添加版本判断,降级为 “跟随系统默认模式”:

import systemParameter from '@ohos.systemParameter';
const apiVersion = systemParameter.get('hw_sc.build.platform.version');
if (apiVersion < 9) {
  // 降级处理:使用系统默认主题
  this.themeMode = ColorMode.LIGHT;
}

性能优化:主题切换时,避免频繁刷新整个页面,可通过@State/@Prop等状态装饰器精准控制需要更新的组件,减少 UI 重绘开销。

总结

最后,简单总结一下,鸿蒙系统通过Configuration、事件总线与差异化资源目录的组合,为动态主题切换提供了简洁高效的实现方案。开发者只需通过 “资源准备→主题监听→UI 适配” 的三步核心逻辑,即可实现 “跟随系统自动切换” 与 “应用内手动切换” 的双重功能。这一特性不仅能提升应用的用户体验,还能让应用更好地融入鸿蒙生态的统一设计风格,为用户打造更个性化的使用场景。无论是社交类、工具类还是阅读类应用,动态主题切换都能成为提升用户粘性的重要功能。

Logo

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

更多推荐