HarmonyOS鸿蒙实战应用8:随手账本——暗色模式与主题切换
引言
现代应用都有"白天用浅色、晚上用深色"的需求。本篇实现:颜色资源化、深色资源自动适配、应用内三档主题切换(跟随系统/浅色/深色)并持久化。
一、颜色资源化
目前页面里全是硬编码颜色('#F5F5F5' 背景、'#333' 文字、Color.White 卡片),这些颜色不会跟随系统深色模式变化。正确的做法是把颜色定义到资源文件里,页面通过 $r() 引用。
1.1 浅色资源(base)
// resources/base/element/color.json
{
"color": [
{ "name": "start_window_background", "value": "#FFFFFF" },
{ "name": "page_bg", "value": "#F5F5F5" },
{ "name": "card_bg", "value": "#FFFFFF" },
{ "name": "text_primary", "value": "#333333" },
{ "name": "text_secondary", "value": "#999999" },
{ "name": "expense", "value": "#FF4444" },
{ "name": "income", "value": "#44BB44" },
{ "name": "brand", "value": "#6C63FF" },
{ "name": "chip_bg", "value": "#F0F0F0" },
{ "name": "divider", "value": "#DDDDDD" }
]
}
1.2 深色资源(dark)
工程自动生成了 resources/dark 目录——只要在 dark/element/color.json 里定义同名颜色,系统切到深色模式时会自动加载这一份,页面代码一行都不用改。
// resources/dark/element/color.json
{
"color": [
{ "name": "start_window_background", "value": "#000000" },
{ "name": "page_bg", "value": "#121212" },
{ "name": "card_bg", "value": "#1E1E1E" },
{ "name": "text_primary", "value": "#E6E6E6" },
{ "name": "text_secondary", "value": "#8A8A8A" },
{ "name": "expense", "value": "#FF6B6B" },
{ "name": "income", "value": "#5FCF5F" },
{ "name": "brand", "value": "#8B84FF" },
{ "name": "chip_bg", "value": "#333333" },
{ "name": "divider", "value": "#3A3A3A" }
]
}
二、页面使用资源色
把硬编码颜色替换为 $r('app.color.xxx'),以账单卡片为例:
// components/BillCard.ets
Row() {
// ...分类 emoji、名称、备注
Text(`${this.bill.type === BillType.EXPENSE ? '-' : '+'}¥${this.bill.amount.toFixed(2)}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(this.bill.type === BillType.EXPENSE
? $r('app.color.expense')
: $r('app.color.income'))
}
.width('100%')
.padding(16)
.backgroundColor($r('app.color.card_bg'))
.borderRadius(12)
需要注意:
- 背景用
card_bg/page_bg,文字用text_primary/text_secondary - 金额用语义色
expense(支出)/income(收入) - 按钮上的白字(如紫色按钮的
fontColor(Color.White))保持不变——深色模式下按钮字色仍然是白色 - 三元表达式里混合使用也没问题,例如筛选 chip:选中态
$r('app.color.brand')+ 白字,未选中态$r('app.color.chip_bg')+$r('app.color.text_primary')
替换完所有页面(首页、新增、编辑、统计、导出、卡片组件)后,切换深浅色,整个应用自动换肤。深色模式下首页的效果——背景、卡片、文字、金额全部切换:

三、主题切换原理
鸿蒙通过 setColorMode 控制应用配色模式:
// EntryAbility.onCreate 中
this.context.getApplicationContext().setColorMode(
ConfigurationConstant.ColorMode.COLOR_MODE_DARK
);
getApplicationContext().setColorMode() 是应用级设置,设置后立即生效——正在显示的所有页面都会按新模式重新加载资源颜色,无需手动刷新页面状态,这就是资源化的好处。深色模式下的统计页同样自动适配:

四、主题持久化
用户选过的主题要记住,下次启动直接生效。复用 Preferences(和第 16 篇同一个 easy_account 文件,只是换一个 key):
// utils/ThemeManager.ts
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
const THEME_KEY = 'theme_mode';
// 读取持久化的主题模式,默认跟随系统
export function loadThemeMode(context: common.Context): string {
try {
const prefs = preferences.getPreferencesSync(context, 'easy_account');
return prefs.getSync(THEME_KEY, 'auto') as string;
} catch (e) {
return 'auto';
}
}
// 保存主题模式
export function saveThemeMode(context: common.Context, mode: string): void {
try {
const prefs = preferences.getPreferencesSync(context, 'easy_account');
prefs.putSync(THEME_KEY, mode);
prefs.flushSync();
} catch (e) {
}
}
启动时在 EntryAbility.onCreate 读取并应用:
const mode = loadThemeMode(this.context);
const colorMode = mode === 'light' ? ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT :
mode === 'dark' ? ConfigurationConstant.ColorMode.COLOR_MODE_DARK :
ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET;
this.context.getApplicationContext().setColorMode(colorMode);
五、切换 UI
首页顶栏加一个"主题"入口,点击弹出操作面板(ActionSheet),提供三档选择:
// pages/HomePage.ets
private showThemeSheet() {
this.getUIContext().showActionSheet({
title: '主题模式',
autoCancel: true,
confirm: { value: '取消', action: () => {
} },
sheets: [
{ title: '跟随系统', action: () => this.applyTheme('auto') },
{ title: '浅色模式', action: () => this.applyTheme('light') },
{ title: '深色模式', action: () => this.applyTheme('dark') }
]
});
}
private applyTheme(mode: string) {
const appCtx = (getContext(this) as common.UIAbilityContext)
.getApplicationContext();
if (mode === 'light') {
appCtx.setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT);
} else if (mode === 'dark') {
appCtx.setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_DARK);
} else {
appCtx.setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
}
saveThemeMode(getContext(this), mode);
}
点击首页顶栏"主题",弹出三档选择面板:

顶栏入口(与"统计""导出"并列):
@Builder
HeaderBar() {
Row() {
// ...月份切换
Row() {
Text('统计').fontSize(14).fontColor($r('app.color.brand'))
Text('导出').fontSize(14).fontColor($r('app.color.brand'))
Text('主题').fontSize(14).fontColor($r('app.color.brand'))
}
}
}
深色模式下导出页(月度报告数值配色同样自动适配):

总结
本篇实现了:
- 颜色资源化:硬编码颜色全部迁入
base/dark双份资源 - 自动换肤:页面用
$r()引用资源色,深浅色自动适配 - 三档主题:跟随系统 / 浅色 / 深色,
setColorMode一键切换 - 持久化:主题选择存 Preferences,重启后仍然生效
下篇实现列表骨架屏加载效果。
更多推荐




所有评论(0)