一、深色模式到底是啥玩意儿

深色模式,也有人叫暗色模式,跟日常用的浅色模式相对应。这玩意儿最早是人机交互领域研究出来的,可不是简单把背景变黑、文字变白那么回事。

它是一整套配色主题。深色模式比浅色模式更柔和,能减少亮度对眼睛的刺激和疲劳。还有个好处:能降低应用功耗,续航表现更好。

应用适配深色模式,得遵循基本的 UX 设计原则:页面内容要易读、舒适、一致。适配过程主要包含几块:

  • 颜色资源适配(字体颜色、元素背景色)
  • 媒体资源适配(图片图标)
  • 状态栏适配
  • Web 页面适配(如果用了 Web 组件)

二、适配原理:资源目录自动切换

系统切换到深色模式后,应用里有些内容会自动切换到深色主题,比如状态栏、弹窗背景色、系统控件。但这些自动切换可能导致页面效果错乱。

咋解决?靠资源目录机制。

HarmonyOS 为深色模式预留了 dark 目录。这目录应用创建时默认不存在,得手动在 src/main/resources 里创建。深色模式的资源放 dark 目录,浅色模式的放 base 目录。

关键点:basedark 目录里定义的资源要同名。比如 base/element/color.json 里定义 text_color 为黑色,dark/element/color.json 里定义 text_color 为白色。切换深浅色时,用了 $r('app.color.text_color') 的元素自动切换颜色,不用写逻辑判断。

在这里插入图片描述
深浅色切换不会改变页面结构,只是展示不同的配色和配图,让切换后依然自然美观。

应用向用户提供深浅色切换有两种方式:

  1. 跟随系统:用 setColorMode()ColorMode 设成 COLOR_MODE_NOT_SET,应用自动感知系统颜色模式切换
  2. 应用内手动控制:深色模式设 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 的话用 SymbolGlyphfontColor 属性。不同深浅色下设不同填充色就行。

方式二:非 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)
  }
}

方式二:动态设置状态栏字体颜色

背景色没法适配,或适配后对比度还是低,就动态设置状态栏字体颜色。

第一步:维护深浅色状态

EntryAbilityonCreate() 把当前 colorModeAppStorage,在 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 提醒同名资源已存在,不影响功能。

八、适配流程总结

  1. 创建 dark 目录(src/main/resources/dark
  2. base/element/color.json 定义浅色颜色
  3. dark/element/color.json 定义深色颜色(同名资源)
  4. 代码用 $r 引用颜色资源
  5. SVG 图标用 fillColor,非 SVG 用 dark/media 目录
  6. 状态栏动态设置字体颜色(如果背景对比度低)
  7. Web 页面单独处理

记住一点:资源定义要同名。basedark 目录下的资源名称一致,系统才能自动切换。不用写逻辑判断,省事。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐