[鸿蒙2025领航者闯关] HarmonyOS深色模式实现
在 HarmonyOS 应用开发中,如何实现符合官方 UX 规范的深色模式适配?: 应用需正确适配深色模式,状态栏、卡片、文字颜色需符合鸿蒙应用 UX 设计规范。
·
问题描述
在 HarmonyOS 应用开发中,如何实现符合官方 UX 规范的深色模式适配?开发者常遇到的问题:
- 切换深色模式后状态栏颜色不变
- 页面卡片在深色背景下显示异常
- 颜色硬编码导致无法动态切换
- 深色模式下文字对比度不足
华为应用市场审核要求: 应用需正确适配深色模式,状态栏、卡片、文字颜色需符合鸿蒙应用 UX 设计规范。
关键字:深色模式、主题切换、状态栏适配、动态颜色
解决方案
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
})
}
}
3. 运行效果
浅色模式:
┌────────────────────────────────┐
│ ●●●●●●●●●●●● 10:30 ← 白色状态栏,黑色图标
├────────────────────────────────┤
│ 主页 │ ← 米白背景
│ │
│ ┌──────────────────────┐ │
│ │ 物品名称 │ │ ← 白色卡片
│ │ 数量: 10个 │ │
│ └──────────────────────┘ │
└────────────────────────────────┘
深色模式:
┌────────────────────────────────┐
│ ●●●●●●●●●●●● 10:30 ← 黑色状态栏,白色图标
├────────────────────────────────┤
│ 主页 │ ← 深灰背景
│ │
│ ┌──────────────────────┐ │
│ │ 物品名称 │ │ ← 深色卡片
│ │ 数量: 10个 │ │
│ └──────────────────────┘ │
└────────────────────────────────┘
关键要点
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
文字对比度:
- 主文字:
#F0F0F0on#1A1A1A≈ 13.9:1 ✅ - 次要文字:
#C8C8C8on#1A1A1A≈ 9.4:1 ✅ - 辅助文字:
#999999on#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: 为什么状态栏颜色没变?
检查两个地方:
- EntryAbility 中是否设置
- 主题切换时是否更新
// 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 = '#666666'; // 中灰
// ✅ 对比度充足
static readonly TEXT_PRIMARY = '#F0F0F0'; // 浅灰
Q4: 如何跟随系统深色模式?
可以通过监听系统配置变化:
import { ConfigurationConstant } from '@kit.AbilityKit';
onConfigurationUpdate(newConfig: Configuration): void {
if (newConfig.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK) {
AppColors.setDarkMode(true);
} else {
AppColors.setDarkMode(false);
}
}
审核要点对照
根据华为应用市场 UX 规范:
✅ 状态栏适配:
- 浅色模式: 白色背景 + 黑色内容 ✓
- 深色模式: 黑色背景 + 白色内容 ✓
✅ 页面背景:
- 浅色模式: 浅色背景 ✓
- 深色模式: 深色背景 ✓
✅ 卡片背景:
- 浅色模式: 白色卡片 ✓
- 深色模式: 深灰卡片 ✓
✅ 文字对比度:
- 主要文字对比度 > 7:1 ✓
- 次要文字对比度 > 4.5:1 ✓
✅ 整体协调性:
- 无刺眼的强对比 ✓
- 颜色层次清晰 ✓
参考资料
更多推荐




所有评论(0)