一、深色模式
深色模式(Dark Mode)又称之为暗色模式,是与日常应用使用过程中的浅色模式(Light Mode)相对应的一种UI主题。深色模式满足更多个性化的需求,基于人因研究设计了深色模式下舒适的颜色范围。深色模式开启后,浅色主题应用界面背景会变成深色,而文字、图标等前景会变成浅色。深色模式的界面上内容更加突出、在 OLED 屏幕的设备上更加省电、能够给用户带来视觉舒适感和沉浸式体验。
二、设计原则
由于深色模式下屏幕总发光量更少,部分用户倾向于在暗环境使用深色模式;但同时,也有相当比例的用户出于对深色风格的喜爱或出于节能省电的需求(深色模式下OLED屏幕功耗更低)而全天开启深色模式。
因此,在深色模式的设计中需要考虑在全天不同环境下清晰易读,同时特别关注在暗环境的使用舒适性,另外还需要保持与浅色模式在层级感知、色彩语义方面的一致性。
1、易读性
深色模式下为了保证清晰易读,文本、图标等前景与深色背景之间仍需要满足最小对比度要求。
场景 | 推荐 | 一般 | 不建议 |
---|---|---|---|
大字号(17fp或15fp以上粗体)、辅助文本(如列表二级文本,或其他对识别效率要求不高的场景)、功能性图标 | 大于3:1 | 1.9:1 – 3:1 | 小于1.9:1 |
非大字号主要文本(如长文本正文、列表一级文本) | 浅色模式大于4.5:1 深色模式大于5:1 | 浅色模式3:1-4.5:1 深色模式大于3:1-5:1 | 小于3:1 |
建议表示活动状态的可交互控件 | 控件背板与背景之间对比度不小于2.2:1 | \ | \ |
对于同时使用多个多彩色用于表示不同状态或用于相邻位置时,在适应对比度要求的基础上,还需要满足色彩差异要求:
场景 | 要求 |
---|---|
一般要求 | 两个需要区分的颜色或相邻颜色,色彩差异△Euv >= 20 |
无障碍要求 | 考虑色盲群体无障碍需要,使用色盲模拟器后,两个要区分的颜色或相邻颜色,色彩差异△Euv >= 20 |
2、舒适性
由于部分用户时长在暗环境选择使用深色模式,深色模式建议遵从以下原则以尽量避免刺眼等舒适性问题。
要求 | 推荐 | 一般 | 不合格 |
---|---|---|---|
文字对比度上限 | 文字需要谨慎使用大于17.6:1的高对比度 | 15.7:1-17.6:1 | 大于17.6:1 |
小图片、图标背板 | 对比度建议不大于15.7:1 | \ | \ |
浅色控件使用与适配 | 同时满足以下三条要求: -深色模式下应用应切换为深色背景 -应用内避免黑白跳转的页面 -页面上不建议使用大面积白色图片或背景 | 深色模式下应用切换为深色背景,但存在黑白跳转的页面或大面积白色图片 | 深色模式下应用仍为浅色界面 |
3、一致性
由于多数应用设计以及用户使用的默认模式为浅色模式,建议在深色模式的设计中需要注意与浅色模式保持一致性,主要包括以下几方面
-
层级的一致性 浅色模式下明度不是唯一的表达层级的视觉线索,而在深色模式尤其是在黑色背景上,用户对投影的感知程度降低,通常使用明度表达层级,因此不同层级之间需要有一定的明度区分,并与浅色模式感知一致
-
色彩语义的一致性 深色模式下,表示警示、通话等具有语义信息的颜色需要保持一致,可以在保持色相一致的前提下对明度进行调整
-
同类控件风格一致性 深色模式下,在用应用或不同页面中的同类控件,视觉风格(颜色、形状等)保持一致
三、实现原理
当系统切换到深色模式后,应用内可能会出现部分内容切换到深色主题的情况,例如状态栏、弹窗背景色、系统控件等,会导致应用内页面效果错乱。
为应对上述情况,需要对应用进行深色模式下的内容适配,目前该适配主要依靠资源目录。当系统对应的设置项发生变化后(如系统语言、深浅色模式等),应用会自动加载对应资源目录下的资源文件。
系统为深色模式预留了dark目录,该目录在应用创建时默认不存在,在进行深色模式适配时,需要开发者在src/main/resources中手动地创建出dark目录,将深色模式所需的资源放置到该目录下。对于浅色模式所需的资源,可以放入默认存在的src/main/resources/base目录下。
说明
在进行资源定义时,需要在base目录与dark目录中定义同名的资源。例如在base/element/color.json文件中定义text_color为黑色,在dark/element/color.json文件中定义text_color为白色,那么当深浅色切换时,应用内使用了$('app.color.text_color ')作为颜色值的元素会自动切换到对应的颜色,而无需使用其他逻辑判断进行控制。
一般情况下深浅色模式切换不会导致应用界面产生结构上的变化,而是页面结构一致但是采用不同的主题配色、配图等,使得整个应用在切换到深色模式后依然保持自然美观,以下为深色模式适配的UX示例。
从上图中可以看到,在应用进行深色模式适配过程中主要的适配项有颜色资源适配、媒体资源适配、状态栏适配
适配项 | 适配内容 | 适配方式 |
---|---|---|
颜色资源适配 | 组件背景色,字体颜色等 | 使用受支持的系统资源 使用color.json资源文件 |
媒体资源适配 | 应用内使用到的图片、图标等 | SVG类型图标可使用fillColor()属性 使用media资源目录 |
状态栏适配 | 深浅模式下不同的状态栏表现,包括状态栏的背景色以及状态栏内时间等内容的字体颜色 | 对应用背景色进行深浅色适配 根据当前深浅色状态动态设置状态栏字体颜色 |
四、适配方案
1、颜色资源适配
颜色资源适配是将页面元素的颜色抽离到限定词目录中,让应用在不同的深浅色模式下使用不同限定词目录中的颜色值,从而达成应用页面元素在深浅色下不同的颜色表现。
方式一:使用系统资源(优先建议)。
方式二:使用自定义主题,若开发者需要定制在深浅色模式下不同的颜色表现,就需要使用自定义主题,以下为具体实现步骤参考。
1.1、在src/main/resources/base/element/color.json文件中定义页面元素在浅色模式下的颜色值,此处定义了弹窗内文字在浅色模式下颜色为黑色。
{
"color": [
{
"name": "text_color",
"value": "#000000"
}
]
}
1.2、在src/main/resources/dark/element/color.json文件中定义页面元素在深色模式下的颜色值(若有不存在的目录或文件需自行创建),此处定义了弹窗内文字在深色模式下颜色为白色。
{
"color": [
{
"name": "text_color",
"value": "#FFFFFF"
}
]
}
1.3、在代码中引用自定义的颜色资源值,使用$r加载自定义颜色资源,系统将自动在应用深浅色变化时,加载对应限定词目录下的资源文件,从而改变页面元素的颜色完成深浅色适配。
build() {
Column({space:30}) {
Text('授权成功')
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_color')) // 引用适配了深色模式的资源值
Text('授权码:441132')
.fontColor($r('app.color.text_color')) // 引用适配了深色模式的资源值
Row({ space: 30 }) {
Button('复制', { buttonStyle: ButtonStyleMode.TEXTUAL })
.fontColor($r('app.color.text_color')) // 引用适配了深色模式的资源值
Button('确定', { buttonStyle: ButtonStyleMode.TEXTUAL })
}
}
.padding(20)
1.4、颜色资源完成深色模式适配效果示例
2、媒体资源适配
媒体资源适配即在深浅模式下采用不同颜色表现的图片或图标等媒体资源,从而达成更好的用户体验,以下为应用内的图标未适配深色模式的效果示例,未适配内容以黄虚线框出。
方式一:若适配简单图标并且图标格式为SVG类型,那么只需要结合颜色资源适配并使用Image组件的fillColor属性(若使用Symbol则使用SymbolGlyph的fontColor属性),在不同的深浅色下设置为不同的填充色即可完成深色模式的适配。
方式二:若需要适配图片或适配图标但图标不为SVG类型,那么就需要使用资源目录的方式进行深色模式的适配,具体实现步骤参考如下。
2.1、在src/main/resources/base/media目录中放入浅色模式下的图片资源,并按需重命名。
2.2、在src/main/resources/dark/media目录中放入深色模式下的图片资源(若有不存在的目录需自行创建),并保证资源名称与上一步放入的资源名称一致。
2.3、在代码中使用Image组件加载对应的图片资源,此处放入资源名称为banner。
build() {
Column() {
Image($r("app.media.banner")) // 引用适配了深色模式的图标资源
.width('100%')
.borderRadius(12)
.objectFit(ImageFit.Cover)
}
.height('100%')
.width('100%')
}
2.4、应用内图标未适配深色模式效果示例
3、状态栏适配
状态栏适配即在深浅色模式下,采用不同的状态栏背景色与字体颜色。若应用未启用沉浸式,那么默认情况下,浅色模式下状态栏为白底黑字,深色模式下状态栏为黑底白字。当应用启用了沉浸式,状态栏背景色与应用背景色保持一致,而状态栏文字会默认在浅色模式下保持黑色,而在深色模式下保持白色,若应用在浅色模式下设置了深色背景或在深色模式下设置了浅色背景,都会导致状态栏背景色与状态栏字体颜色对比度过低,导致显示异常。错误效果示例如下图,应用设置了沉浸式并在浅色模式下具有纯黑色的背景色,导致状态栏的日期电量等文本内容无法看清。
方式一:若可以将背景色做深浅色适配,则采用颜色资源适配的方案对应用背景色进行适配,背景色适配时需考虑到状态栏文字在深浅色模式下的默认表现。
方式二:若背景色无法做深浅色适配,或做了深浅色适配,但是沉浸式颜色与默认的状态栏文字颜色对比度较低,这种情况下需要获取当前的深浅色并动态设置状态栏字体颜色。具体实现步骤参考如下。
3.1、在EntryAbility中获取并维护当前深浅色状态,在onCreate时将当前colorMode放在AppStorage中,并在配置变化的onConfigurationUpdate()回调中动态更新深浅色状态。
// src/main/ets/entryability/EntryAbility.ets
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);
}
}
}
3.2、在页面内监听深浅色模式状态变量的变化,并根据变化后的深浅色模式来动态设置状态栏文本颜色。
@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(getContext(this), (err: BusinessError, data) => {
this.windowObj = data;
})
}
onCurrentColorModeChange(): void {
if (!this.windowObj) {
return;
}
if (this.currentColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT) {
this.windowObj?.setWindowSystemBarProperties({
statusBarContentColor: '#FFFFFF'
})
} else if (this.currentColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK) {
this.windowObj?.setWindowSystemBarProperties({
statusBarContentColor: '#000000'
})
}
}
// ...
build() {
// ...
}
}
3.3、状态栏适配深色模式后效果
一键三连+关注,你的支持是我创作的动力。在这里,我乐于倾囊相授。