HarmonyOS 深色模式适配实战:从颜色资源到状态栏完整方案
本文介绍了深色模式的适配方法,主要包括颜色资源和媒体资源适配。深色模式通过资源目录机制实现自动切换,需在base和dark目录下定义同名资源。颜色适配需确保元素对比度,可通过系统资源或自定义主题实现;媒体资源适配则可通过SVG图标的fillColor属性或资源目录更换图片。正确适配后,应用在不同模式下都能保持良好视觉效果。
一、深色模式到底是啥玩意儿
深色模式,也有人叫暗色模式,跟日常用的浅色模式相对应。这玩意儿最早是人机交互领域研究出来的,可不是简单把背景变黑、文字变白那么回事。
它是一整套配色主题。深色模式比浅色模式更柔和,能减少亮度对眼睛的刺激和疲劳。还有个好处:能降低应用功耗,续航表现更好。
应用适配深色模式,得遵循基本的 UX 设计原则:页面内容要易读、舒适、一致。适配过程主要包含几块:
- 颜色资源适配(字体颜色、元素背景色)
- 媒体资源适配(图片图标)
- 状态栏适配
- Web 页面适配(如果用了 Web 组件)
二、适配原理:资源目录自动切换
系统切换到深色模式后,应用里有些内容会自动切换到深色主题,比如状态栏、弹窗背景色、系统控件。但这些自动切换可能导致页面效果错乱。
咋解决?靠资源目录机制。
HarmonyOS 为深色模式预留了 dark 目录。这目录应用创建时默认不存在,得手动在 src/main/resources 里创建。深色模式的资源放 dark 目录,浅色模式的放 base 目录。
关键点:base 和 dark 目录里定义的资源要同名。比如 base/element/color.json 里定义 text_color 为黑色,dark/element/color.json 里定义 text_color 为白色。切换深浅色时,用了 $r('app.color.text_color') 的元素自动切换颜色,不用写逻辑判断。

深浅色切换不会改变页面结构,只是展示不同的配色和配图,让切换后依然自然美观。
应用向用户提供深浅色切换有两种方式:
- 跟随系统:用
setColorMode()把ColorMode设成COLOR_MODE_NOT_SET,应用自动感知系统颜色模式切换 - 应用内手动控制:深色模式设
COLOR_MODE_DARK,浅色模式设COLOR_MODE_LIGHT
三、颜色资源适配:弹窗文字看不清咋解决
颜色资源适配就是把页面元素颜色抽离到限定词目录,让应用在不同深浅色下用不同颜色值。没正确适配的话,深色模式下元素对比度过低,用户识别困难。
看个错误示例:

浅色模式下正常,切换深色模式后,弹窗内文字和背景色对比度低于 5:1,看不清内容。
问题在哪?自定义弹窗没手动指定背景色,系统默认对背景做了深浅色适配,但弹窗内容(特别是自定义内容)没法自动适配。深色模式下背景变深色,内容颜色不变,对比度过低。
解决方案有两种:
方式一:用系统资源(优先建议)
用受支持的系统资源,会自动适配深色模式。系统资源列表可以查官方文档。
方式二:自定义主题
要定制深浅色下不同颜色表现,就得自定义。步骤如下:
第一步:定义浅色模式颜色
在 src/main/resources/base/element/color.json 定义:
{
"color": [
{
"name": "text_color",
"value": "#000000"
}
]
}
第二步:定义深色模式颜色
在 src/main/resources/dark/element/color.json 定义(目录不存在要手动创建):
{
"color": [
{
"name": "text_color",
"value": "#FFFFFF"
}
]
}
第三步:代码里引用
用 $r 加载自定义颜色资源,系统自动在深浅色变化时加载对应目录的资源:
@Entry
@Component
struct Index {
private customDialogComponentId: number = 0;
private promptAction = this.getUIContext().getPromptAction();
@Builder
customDialogPositiveExample() {
Column() {
Text($r('app.string.authorization_succeeds'))
.fontColor($r('app.color.text_color'))
Text($r('app.string.authorization_code'))
.fontColor($r('app.color.text_color'))
Row({ space: 8 }) {
Button($r('app.string.copy'), { buttonStyle: ButtonStyleMode.TEXTUAL })
.fontColor($r('app.color.text_color'))
Button($r('app.string.confirm'), { buttonStyle: ButtonStyleMode.TEXTUAL })
}
}
}
build() {
// ...
}
}
适配后效果:


四、媒体资源适配:图标深色模式下看不见
媒体资源适配就是在深浅色下用不同颜色表现的图片或图标。看个错误示例(黄虚线框出未适配内容):


问题:图标没做深色适配,颜色始终不变。深色模式下图标颜色和背景对比度过低,看不清。
解决方案有两种:
方式一:SVG 图标用 fillColor 属性
适配简单图标且是 SVG 类型,结合颜色资源适配,用 Image 组件的 fillColor 属性。用 Symbol 的话用 SymbolGlyph 的 fontColor 属性。不同深浅色下设不同填充色就行。
方式二:非 SVG 用资源目录
图片或非 SVG 图标,用资源目录适配:
第一步:放浅色资源
在 src/main/resources/base/media 目录放浅色模式图片,按需重命名。
第二步:放深色资源
在 src/main/resources/dark/media 目录放深色模式图片(目录不存在要创建),资源名称要和浅色的一致。
第三步:代码引用
@Component
struct Home {
build() {
Scroll() {
Column() {
Stack({ alignContent: Alignment.TopStart }) {
Image($r('app.media.bell'))
.width('100%')
.borderRadius(12)
.objectFit(ImageFit.Cover)
}
}
}
}
}
适配后效果:


五、状态栏适配:状态栏文字不可见咋解决
状态栏适配就是在深浅色下用不同的状态栏背景色和字体颜色。
没启用沉浸式布局时,默认浅色模式状态栏白底黑字,深色模式黑底白字。
启用沉浸式后,状态栏背景色和应用背景色一致。状态栏文字默认浅色模式黑色,深色模式白色。如果浅色模式用了偏暗背景,或深色模式用了偏亮背景,状态栏背景和文字对比度过低,显示异常。

问题:页面背景色固定黑色,系统切换浅色模式后,状态栏文字默认黑色。背景和文字颜色一致,对比度过低,文字不可见。
解决方案有两种:
方式一:背景色做深浅色适配
用颜色资源适配方案对应用背景色适配,适配时要考虑状态栏文字在深浅色下的默认表现:
@Entry
@Component
struct Index {
build() {
Navigation(this.navPathStack) {
// ...
}
.backgroundColor($r('app.color.app_background_color'))
.hideTitleBar(true)
}
}
方式二:动态设置状态栏字体颜色
背景色没法适配,或适配后对比度还是低,就动态设置状态栏字体颜色。
第一步:维护深浅色状态
在 EntryAbility 的 onCreate() 把当前 colorMode 放 AppStorage,在 onConfigurationUpdate() 回调动态更新:
export default class EntryAbility extends UIAbility {
onCreate(_want: Want, _launchParam: AbilityConstant.LaunchParam): void {
AppStorage.setOrCreate<ConfigurationConstant.ColorMode>('currentColorMode', this.context.config.colorMode);
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onConfigurationUpdate(newConfig: Configuration): void {
const currentColorMode: ConfigurationConstant.ColorMode | undefined = AppStorage.get('currentColorMode');
if (currentColorMode !== newConfig.colorMode) {
AppStorage.setOrCreate<ConfigurationConstant.ColorMode>('currentColorMode', newConfig.colorMode);
}
}
}
第二步:监听变化并设置颜色
页面内监听深浅色状态变化,动态设置状态栏文本颜色:
@Entry
@Component
struct Index {
@StorageProp('currentColorMode') @Watch('onCurrentColorModeChange') currentColorMode: ConfigurationConstant.ColorMode =
ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET;
private windowObj: window.Window | null = null;
aboutToAppear(): void {
window.getLastWindow(this.getUIContext().getHostContext(), (err: BusinessError, data) => {
if (err.code) {
hilog.error(0x0000, 'Index', `getLastWindow failed. code=${err.code}, message=${err.message}`);
return;
}
this.windowObj = data;
})
}
onCurrentColorModeChange(): void {
if (!this.windowObj) {
return;
}
try {
if (this.currentColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT) {
this.windowObj?.setWindowSystemBarProperties({
statusBarContentColor: '#000000'
})
} else if (this.currentColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK) {
this.windowObj?.setWindowSystemBarProperties({
statusBarContentColor: '#FFFFFF'
})
}
} catch (error) {
let err = error as BusinessError;
hilog.error(0x0000, 'Index', `setWindowSystemBarProperties failed, error code=${err.code}, message=${err.message}`);
}
}
build() {
// ...
}
}
适配后效果:

六、Web 页面适配
Web 页面内容不会自动跟随系统颜色模式切换。
要适配,得在 Web 页面内通过媒体查询设置深色模式样式,并通过 Web 组件的 darkMode() 属性控制是否启用深色模式。具体实现参考官方文档的 Web 深色模式适配。
七、踩坑总结
坑一:自定义弹窗不跟随切换
可能原因:弹窗没设置背景色,系统默认对背景做了深色适配,但弹窗内容没法自动适配。
解决:对弹窗内容做适配,用颜色资源方案。
坑二:媒体资源显示"文件已存在"
适配媒体资源时弹出"资源在另外目录已存在"。
解决:直接点"continue",不会导致适配错误。这是 IDE 提醒同名资源已存在,不影响功能。
八、适配流程总结
- 创建
dark目录(src/main/resources/dark) - 在
base/element/color.json定义浅色颜色 - 在
dark/element/color.json定义深色颜色(同名资源) - 代码用
$r引用颜色资源 - SVG 图标用
fillColor,非 SVG 用dark/media目录 - 状态栏动态设置字体颜色(如果背景对比度低)
- Web 页面单独处理
记住一点:资源定义要同名。base 和 dark 目录下的资源名称一致,系统才能自动切换。不用写逻辑判断,省事。
更多推荐



所有评论(0)