HarmonyOS“面试通”深色模式主题资源管理与切换功能开发
·
一、方案概述
深色模式是现代移动应用的重要特性,能够降低屏幕亮度、减少眼睛疲劳,并节省设备电量。本方案为“面试通”应用设计了完整的深色模式主题管理系统,包含主题资源管理、动态切换、状态持久化和系统级适配等功能。
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏
嘻嘻嘻,关注我!!!黑马波哥
也可以关注我的抖音号: 黑马程序员burger 在直播间交流(17:00-21:00)
二、整体架构设计
2.1 主题管理架构图
2.2 功能流程图
三、主题资源定义与管理
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“面试通”应用提供了完整的深色模式主题管理系统,具有以下特点:
- 架构清晰:采用分层架构设计,分离关注点,便于维护和扩展
- 性能优异:通过资源预加载、缓存管理等技术优化主题切换性能
- 体验流畅:支持平滑的主题切换动画,提升用户体验
- 安全可靠:所有主题配置都经过持久化存储,避免数据丢失
- 易于扩展:支持自定义主题和动态主题生成,满足未来需求
- 符合标准:遵循HarmonyOS官方开发规范,确保代码质量
通过本方案的实施,“面试通”应用可以:
- 为用户提供舒适的夜间使用体验
- 节省设备电量,延长续航时间
- 提升应用的专业性和现代感
- 满足不同用户的个性化需求
- 为未来的主题商店等功能奠定基础
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏
嘻嘻嘻,关注我!!!黑马波哥
也可以关注我的抖音号: 黑马程序员burger 在直播间交流(17:00-21:00)
更多推荐



所有评论(0)