HarmonyOS APP<玩转React>开源教程八:主题系统实现

第8次:主题系统实现

主题系统是现代应用的标配功能,让用户可以根据喜好或环境选择合适的显示模式。本次课程将实现完整的深色/浅色主题切换功能,包括跟随系统、手动切换和持久化保存。


效果

深浅色主题对比

学习目标

  • 设计完整的主题模式(AUTO/LIGHT/DARK)
  • 配置主题颜色体系
  • 实现系统颜色模式检测
  • 完成主题切换与持久化
  • 学会组件主题适配

8.1 主题模式设计

三种主题模式

模式 说明 使用场景
AUTO 跟随系统 默认模式,自动适应
LIGHT 强制浅色 用户偏好浅色
DARK 强制深色 用户偏好深色/夜间

主题状态管理

typescript 复制代码
// 主题相关的全局状态
AppStorage.setOrCreate('isDarkMode', false);      // 当前是否深色
AppStorage.setOrCreate('themeMode', 'auto');      // 主题模式

状态流转

复制代码
用户选择 AUTO → 检测系统模式 → 设置 isDarkMode
用户选择 LIGHT → isDarkMode = false
用户选择 DARK → isDarkMode = true

8.2 主题颜色配置

颜色体系设计

一个完整的主题需要定义以下颜色:

typescript 复制代码
export interface ThemeColors {
  // 背景色
  background: string;           // 页面背景
  cardBackground: string;       // 卡片背景
  modalBackground: string;      // 弹窗背景

  // 文字色
  textPrimary: string;          // 主要文字
  textSecondary: string;        // 次要文字
  textDisabled: string;         // 禁用文字
  textInverse: string;          // 反色文字

  // 品牌色
  primary: string;              // 主色调
  primaryLight: string;         // 主色调浅
  primaryDark: string;          // 主色调深

  // 功能色
  success: string;              // 成功
  warning: string;              // 警告
  error: string;                // 错误
  info: string;                 // 信息

  // 边框和分隔
  border: string;               // 边框
  divider: string;              // 分隔线

  // 阴影
  shadowColor: string;          // 阴影颜色
}

浅色主题配置

typescript 复制代码
export const LightThemeColors: ThemeColors = {
  // 背景
  background: '#f8f9fa',
  cardBackground: '#ffffff',
  modalBackground: '#ffffff',

  // 文字
  textPrimary: '#1a1a2e',
  textSecondary: '#495057',
  textDisabled: '#adb5bd',
  textInverse: '#ffffff',

  // 品牌色
  primary: '#61DAFB',
  primaryLight: '#8ae4fc',
  primaryDark: '#21a0c4',

  // 功能色
  success: '#28a745',
  warning: '#ffc107',
  error: '#dc3545',
  info: '#17a2b8',

  // 边框
  border: '#dee2e6',
  divider: '#e9ecef',

  // 阴影
  shadowColor: 'rgba(0, 0, 0, 0.1)'
};

深色主题配置

typescript 复制代码
export const DarkThemeColors: ThemeColors = {
  // 背景
  background: '#1a1a2e',
  cardBackground: '#282c34',
  modalBackground: '#2d3748',

  // 文字
  textPrimary: '#ffffff',
  textSecondary: '#d1d5db',
  textDisabled: '#6b7280',
  textInverse: '#1a1a2e',

  // 品牌色
  primary: '#61DAFB',
  primaryLight: '#8ae4fc',
  primaryDark: '#21a0c4',

  // 功能色
  success: '#51cf66',
  warning: '#fcc419',
  error: '#ff6b6b',
  info: '#339af0',

  // 边框
  border: '#3d3d5c',
  divider: '#3d3d5c',

  // 阴影
  shadowColor: 'rgba(0, 0, 0, 0.3)'
};

8.3 系统颜色模式检测

获取系统颜色模式

typescript 复制代码
import { resourceManager } from '@kit.LocalizationKit';

/**
 * 检测系统是否为深色模式
 */
function getSystemIsDarkMode(context: Context): boolean {
  try {
    const resMgr = context.resourceManager;
    const config = resMgr.getConfigurationSync();

    // ColorMode.DARK = 0, ColorMode.LIGHT = 1
    return config.colorMode === resourceManager.ColorMode.DARK;
  } catch (error) {
    console.error('[ThemeUtil] Failed to get system color mode:', error);
    return false;
  }
}

监听系统主题变化

typescript 复制代码
// 在 Ability 中监听配置变化
onConfigurationUpdate(newConfig: Configuration): void {
  const isDark = newConfig.colorMode === resourceManager.ColorMode.DARK;
  const themeMode = AppStorage.get<string>('themeMode');

  // 只有 AUTO 模式才跟随系统
  if (themeMode === 'auto') {
    AppStorage.set('isDarkMode', isDark);
  }
}

8.4 完整的 ThemeUtil 实现

更新 entry/src/main/ets/common/ThemeUtil.ets

typescript 复制代码
/**
 * 主题工具类
 * 管理应用深浅主题切换
 */
import { resourceManager } from '@kit.LocalizationKit';
import { StorageUtil } from './StorageUtil';
import { StorageKeys } from './Constants';

/**
 * 主题模式枚举
 */
export enum ThemeMode {
  AUTO = 'auto',
  LIGHT = 'light',
  DARK = 'dark'
}

/**
 * 主题颜色接口
 */
export interface ThemeColors {
  background: string;
  cardBackground: string;
  textPrimary: string;
  textSecondary: string;
  textDisabled: string;
  primary: string;
  success: string;
  warning: string;
  error: string;
  border: string;
  divider: string;
  shadowColor: string;
}

/**
 * 浅色主题
 */
export const LightTheme: ThemeColors = {
  background: '#f8f9fa',
  cardBackground: '#ffffff',
  textPrimary: '#1a1a2e',
  textSecondary: '#495057',
  textDisabled: '#adb5bd',
  primary: '#61DAFB',
  success: '#28a745',
  warning: '#ffc107',
  error: '#dc3545',
  border: '#dee2e6',
  divider: '#e9ecef',
  shadowColor: 'rgba(0,0,0,0.1)'
};

/**
 * 深色主题
 */
export const DarkTheme: ThemeColors = {
  background: '#1a1a2e',
  cardBackground: '#282c34',
  textPrimary: '#ffffff',
  textSecondary: '#d1d5db',
  textDisabled: '#6b7280',
  primary: '#61DAFB',
  success: '#51cf66',
  warning: '#fcc419',
  error: '#ff6b6b',
  border: '#3d3d5c',
  divider: '#3d3d5c',
  shadowColor: 'rgba(0,0,0,0.3)'
};

/**
 * 获取系统颜色模式
 */
function getSystemIsDarkMode(context: Context): boolean {
  try {
    const resMgr = context.resourceManager;
    const config = resMgr.getConfigurationSync();
    return config.colorMode === resourceManager.ColorMode.DARK;
  } catch (error) {
    console.error('[ThemeUtil] Failed to get system color mode');
    return false;
  }
}

/**
 * 初始化主题
 */
export async function initTheme(context: Context): Promise<void> {
  try {
    // 从存储加载主题模式
    const savedMode = await StorageUtil.getString(
      StorageKeys.THEME_MODE,
      ThemeMode.AUTO
    );

    const themeMode = savedMode as ThemeMode;
    let isDark: boolean;

    switch (themeMode) {
      case ThemeMode.LIGHT:
        isDark = false;
        break;
      case ThemeMode.DARK:
        isDark = true;
        break;
      case ThemeMode.AUTO:
      default:
        isDark = getSystemIsDarkMode(context);
        break;
    }

    // 设置全局状态
    AppStorage.setOrCreate('isDarkMode', isDark);
    AppStorage.setOrCreate('themeMode', themeMode);

    console.info(`[ThemeUtil] Initialized: mode=${themeMode}, isDark=${isDark}`);
  } catch (error) {
    console.error('[ThemeUtil] Init failed:', error);
    AppStorage.setOrCreate('isDarkMode', false);
    AppStorage.setOrCreate('themeMode', ThemeMode.AUTO);
  }
}

/**
 * 切换主题(浅色/深色切换)
 */
export async function toggleTheme(): Promise<void> {
  const currentIsDark = AppStorage.get<boolean>('isDarkMode') ?? false;
  const newIsDark = !currentIsDark;
  const newMode = newIsDark ? ThemeMode.DARK : ThemeMode.LIGHT;

  AppStorage.set('isDarkMode', newIsDark);
  AppStorage.set('themeMode', newMode);

  // 持久化保存
  await StorageUtil.setString(StorageKeys.THEME_MODE, newMode);

  console.info(`[ThemeUtil] Toggled: mode=${newMode}, isDark=${newIsDark}`);
}

/**
 * 设置主题模式
 */
export async function setThemeMode(mode: ThemeMode, context: Context): Promise<void> {
  let isDark: boolean;

  switch (mode) {
    case ThemeMode.LIGHT:
      isDark = false;
      break;
    case ThemeMode.DARK:
      isDark = true;
      break;
    case ThemeMode.AUTO:
    default:
      isDark = getSystemIsDarkMode(context);
      break;
  }

  AppStorage.set('isDarkMode', isDark);
  AppStorage.set('themeMode', mode);

  // 持久化保存
  await StorageUtil.setString(StorageKeys.THEME_MODE, mode);

  console.info(`[ThemeUtil] Set mode: ${mode}, isDark=${isDark}`);
}

/**
 * 获取当前主题模式
 */
export function getThemeMode(): ThemeMode {
  const mode = AppStorage.get<string>('themeMode');
  if (mode === ThemeMode.LIGHT || mode === ThemeMode.DARK || mode === ThemeMode.AUTO) {
    return mode as ThemeMode;
  }
  return ThemeMode.AUTO;
}

/**
 * 获取当前主题颜色
 */
export function getThemeColors(isDarkMode: boolean): ThemeColors {
  return isDarkMode ? DarkTheme : LightTheme;
}

/**
 * 处理系统主题变化
 */
export function handleSystemThemeChange(isDark: boolean): void {
  const themeMode = getThemeMode();

  // 只有 AUTO 模式才响应系统变化
  if (themeMode === ThemeMode.AUTO) {
    AppStorage.set('isDarkMode', isDark);
    console.info(`[ThemeUtil] System theme changed: isDark=${isDark}`);
  }
}

8.5 组件主题适配

基础适配模式

typescript 复制代码
@Component
struct ThemedCard {
  @StorageLink('isDarkMode') isDarkMode: boolean = false;

  // 计算属性获取主题色
  get theme(): ThemeColors {
    return this.isDarkMode ? DarkTheme : LightTheme;
  }

  build() {
    Column() {
      Text('标题')
        .fontColor(this.theme.textPrimary)

      Text('描述')
        .fontColor(this.theme.textSecondary)
    }
    .backgroundColor(this.theme.cardBackground)
    .border({ width: 1, color: this.theme.border })
  }
}

封装主题感知组件

typescript 复制代码
/**
 * 主题感知文本组件
 */
@Component
struct ThemedText {
  @Prop text: string = '';
  @Prop type: 'primary' | 'secondary' | 'disabled' = 'primary';
  @Prop size: number = 14;
  @StorageLink('isDarkMode') isDarkMode: boolean = false;

  get color(): string {
    const theme = this.isDarkMode ? DarkTheme : LightTheme;
    switch (this.type) {
      case 'secondary': return theme.textSecondary;
      case 'disabled': return theme.textDisabled;
      default: return theme.textPrimary;
    }
  }

  build() {
    Text(this.text)
      .fontSize(this.size)
      .fontColor(this.color)
  }
}

// 使用
ThemedText({ text: '主要文字', type: 'primary' })
ThemedText({ text: '次要文字', type: 'secondary' })

封装主题感知容器

typescript 复制代码
/**
 * 主题感知卡片容器
 */
@Component
struct ThemedCard {
  @StorageLink('isDarkMode') isDarkMode: boolean = false;
  @BuilderParam content: () => void;

  get theme(): ThemeColors {
    return this.isDarkMode ? DarkTheme : LightTheme;
  }

  build() {
    Column() {
      this.content()
    }
    .padding(16)
    .backgroundColor(this.theme.cardBackground)
    .borderRadius(12)
    .shadow({
      radius: 8,
      color: this.theme.shadowColor,
      offsetY: 4
    })
  }
}

// 使用
ThemedCard() {
  Column() {
    Text('卡片内容')
  }
}

8.6 实操:完成主题系统集成

步骤 1:更新 EntryAbility

typescript 复制代码
import { UIAbility, AbilityConstant, Want, Configuration } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { resourceManager } from '@kit.LocalizationKit';
import { StorageUtil } from '../common/StorageUtil';
import { initTheme, handleSystemThemeChange } from '../common/ThemeUtil';

export default class EntryAbility extends UIAbility {
  async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
    // 初始化存储
    await StorageUtil.init(this.context);

    // 初始化主题
    await initTheme(this.context);
  }

  onConfigurationUpdate(newConfig: Configuration): void {
    // 监听系统主题变化
    const isDark = newConfig.colorMode === resourceManager.ColorMode.DARK;
    handleSystemThemeChange(isDark);
  }

  async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
    windowStage.loadContent('pages/Index');
  }
}

步骤 2:创建设置页面

创建 pages/SettingsPage.ets

typescript 复制代码
import { router } from '@kit.ArkUI';
import { ThemeMode, setThemeMode, getThemeMode, LightTheme, DarkTheme } from '../common/ThemeUtil';

@Entry
@Component
struct SettingsPage {
  @StorageLink('isDarkMode') isDarkMode: boolean = false;
  @State currentMode: ThemeMode = ThemeMode.AUTO;

  get theme() {
    return this.isDarkMode ? DarkTheme : LightTheme;
  }

  aboutToAppear(): void {
    this.currentMode = getThemeMode();
  }

  build() {
    Column() {
      // 顶部导航
      Row() {
        Text('←')
          .fontSize(24)
          .fontColor(this.theme.textPrimary)
          .onClick(() => router.back())

        Text('设置')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor(this.theme.textPrimary)
          .margin({ left: 16 })
      }
      .width('100%')
      .height(56)
      .padding({ left: 16, right: 16 })
      .backgroundColor(this.theme.cardBackground)

      // 主题设置
      Column() {
        Text('主题设置')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor(this.theme.textPrimary)
          .width('100%')
          .margin({ bottom: 16 })

        // 跟随系统
        this.ThemeOption('跟随系统', '自动适应系统主题', ThemeMode.AUTO)

        // 浅色模式
        this.ThemeOption('浅色模式', '始终使用浅色主题', ThemeMode.LIGHT)

        // 深色模式
        this.ThemeOption('深色模式', '始终使用深色主题', ThemeMode.DARK)
      }
      .width('100%')
      .padding(16)
      .margin({ top: 16 })
      .backgroundColor(this.theme.cardBackground)
      .borderRadius(12)
    }
    .width('100%')
    .height('100%')
    .padding({ left: 16, right: 16 })
    .backgroundColor(this.theme.background)
  }

  @Builder
  ThemeOption(title: string, desc: string, mode: ThemeMode) {
    Row() {
      Column() {
        Text(title)
          .fontSize(16)
          .fontColor(this.theme.textPrimary)
        Text(desc)
          .fontSize(12)
          .fontColor(this.theme.textSecondary)
          .margin({ top: 4 })
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)

      Radio({ value: mode, group: 'theme' })
        .checked(this.currentMode === mode)
        .onChange((isChecked: boolean) => {
          if (isChecked) {
            this.currentMode = mode;
            setThemeMode(mode, getContext(this));
          }
        })
    }
    .width('100%')
    .padding({ top: 12, bottom: 12 })
    .border({
      width: { bottom: 1 },
      color: this.theme.divider
    })
  }
}

步骤 3:在首页添加设置入口

typescript 复制代码
// 在 HeaderBar 中添加设置按钮
@Builder
HeaderBar() {
  Row() {
    // ... 其他内容

    Text('⚙️')
      .fontSize(24)
      .onClick(() => {
        router.pushUrl({ url: 'pages/SettingsPage' });
      })
  }
}

步骤 4:更新路由配置

main_pages.json 中添加:

json 复制代码
{
  "src": [
    "pages/Index",
    "pages/SettingsPage"
  ]
}

本次课程小结

通过本次课程,你已经:

✅ 设计了完整的主题模式(AUTO/LIGHT/DARK)

✅ 配置了浅色和深色主题颜色体系

✅ 实现了系统颜色模式检测

✅ 完成了主题切换与持久化保存

✅ 学会了组件的主题适配方法

✅ 创建了主题设置页面


课后练习

  1. 添加更多主题:实现蓝色主题、绿色主题等

  2. 主题预览:在设置页面添加主题预览效果

  3. 过渡动画:为主题切换添加平滑过渡动画


下次预告

第9次:首页 Tab 导航实现

我们将实现应用的核心导航结构:

  • Tabs 组件详解
  • 自定义 TabBar 样式
  • Tab 切换事件处理
  • 底部导航栏设计

构建专业的应用导航体验!

相关推荐
fei_sun1 小时前
【鸿蒙智能硬件】(六)使用鸿蒙app展示环境监测数据
华为·harmonyos
Are_You_Okkk_1 小时前
不只是辅助编程:AI研发框架如何重构团队研发体系?
人工智能·重构·开源·ai编程
FIT2CLOUD飞致云2 小时前
安全漏洞修复,图表功能增强,DataEase开源BI工具v2.10.20 LTS版本发布
开源·数据可视化·dataease·bi·数据大屏
fanxianshi2 小时前
2026 年 3 月行业动态与开源生态全景报告
人工智能·深度学习·神经网络·机器学习·计算机视觉·开源·语音识别
猫头虎2 小时前
如何解决openclaw安装skills报错command not foud:clawhub问题怎么解决?
langchain·开源·prompt·github·aigc·ai编程·内容运营
懒洋洋在睡觉3 小时前
鸿蒙 6.0横屏显示时画面旋转错误
华为·图形渲染·harmonyos
键盘鼓手苏苏3 小时前
Flutter 组件 reaxdb_dart 适配鸿蒙 HarmonyOS 实战:响应式 NoSQL 数据库,构建高性能本地持久化与分布式状态同步架构
flutter·harmonyos·鸿蒙·openharmony·reaxdb_dart
Qiuner3 小时前
Claude 缺失的增强套件:claude-nexus!
开源·claude
亚历克斯神3 小时前
Flutter for OpenHarmony: Flutter 三方库 mongo_dart 助力鸿蒙应用直连 NoSQL 数据库构建高效的数据流转系统(纯 Dart 驱动方案)
android·数据库·flutter·华为·nosql·harmonyos