🎯 剖析深色模式适配方案

⭐⭐⭐

📌 见解
深色模式又称为暗色模式或者夜间模式,提供一整套适配深色模式的应用配色主题。深色模式相较浅色模式更加柔和,能减少亮度对用户眼睛造成的刺激和疲劳,此外深色模式能在一定程度上降低应用功耗,提升续航表现

⚠️ 使用场景

适配项 适配内容 适配式
颜色资源适配 组件背景色,字体颜色等 1. 使用受支持的系统资源,系统色彩全量表
2. 使用color.json资源文件
媒体资源适配 应用内使用到的图片、图标等 1. SVG类型图标可使用fillColor()属性
2. 使用media资源录
状态栏适配 深浅模式下不同的状态栏表现,包括状态栏的背景色以及状态栏内时间等内容的字体颜色 1. 对应用背景色进行深浅色适配
2. 根据当前深浅色状态动态设置状态栏字体颜色
Web内容适配 应用内使用Web组件加载的Web页面 参考Web组件设置深色模式

🧩 拆解

🧱 颜色资源适配

// TODO: 日间颜色设置
// src/main/resources/base/element/color.json
{
  "color": [
    {
      "name": "start_window_background",
      "value": "#FFFFFF"
    },
    {
      "name": "text_color",
      "value": "#000000"
    }
  ]
}
// TODO: 夜间颜色设置--没有则根据路径创建
// src/main/resources/dark/element/color.json
{
  "color": [
    {
      "name": "start_window_background",
      "value": "#000000"
    },
    {
      "name": "text_color",
      "value": "#FFFFFF"
    }
  ]
}

// 1、自定义
 Text('汉堡黄🍔')
    .fontSize(20)
     .fontWeight(FontWeight.Bold)
     .fontColor($r('app.color.text_color')) // 自定义资源颜色

// 2、系统颜色
Text('汉堡黄🍔')
    .fontSize(20)
     .fontWeight(FontWeight.Bold)
     .fontColor($r('sys.color.font_primary')) // 系统颜色

🧱 媒体资源适配

// 1、若适配简单图标并且图标格式为SVG类型
// TODO: 自己添加下载一个svg格式的icon
// 阿里矢量图标库:https://www.iconfont.cn/fonts/index?spm=a313x.7781069.1998910419.16
// src/main/resources/base/media/mock.svg

// 日间颜色---mockImg_color
{
  "color": [
    {
      "name": "start_window_background",
      "value": "#FFFFFF"
    },
    {
      "name": "text_color",
      "value": "#000000"
    },
    {
      "name": "mockImg_color",
      "value": "#000000"
    }
  ]
}

// 夜间颜色---mockImg_color
{
  "color": [
    {
      "name": "start_window_background",
      "value": "#000000"
    },
    {
      "name": "text_color",
      "value": "#FFFFFF"
    },
    {
      "name": "mockImg_color",
      "value": "#FFFFFF"
    }
  ]
}

// 页面使用
Image($r('app.media.mock'))
    .fillColor($r('app.color.mockImg_color'))
    .width(40)
    .aspectRatio(1)

// 2、若使用Symbol则使用SymbolGlyph的fontColor属性
Text() {
    SymbolSpan($r('sys.symbol.ohos_trash'))
        .fontWeight(FontWeight.Medium)
        .fontSize(40)
        .fontColor([$r('app.color.mockImg_color')])
}

// 3、若需要适配图片或适配图标,但图标不为SVG类型
// 日间:src/main/resources/base/media/like.png
// 夜间:src/main/resources/dark/media/like.png
// 页面使用
Image($r('app.media.like'))
    .width(40)
    .aspectRatio(1)

🧱 状态栏适配

// 1、未启用沉浸式,那么默认情况下,浅色模式下状态栏为白底黑字,深色模式下状态栏为黑底白字,采用自定义资源或者系统资源配置背景色即可

// 2、启用沉浸式而状态栏文字会默认在浅色模式下保持黑色,而在深色模式下保持白色,如果日间模式下使用的黑色背景会导致状态栏的日期电量等文本内容无法看清
// 方案一:若背景色可以做深浅色适配,则采用颜色资源适配的方案对应用背景色进行适配即可
.backgroundColor($r('app.color.start_window_background')) // 自定义资源颜色
// 方案二:若背景色只能做深色或者浅色,则采用监听当前颜色模式变化来改变状态栏文字颜色
// src/main/ets/entryability/EntryAbility.ets
// 当UIAbility实例创建完成时,系统会触发该回调
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    AppStorage.setOrCreate<ConfigurationConstant.ColorMode>('currentColorMode', this.context.config.colorMode)
}
// 当系统环境变量发生变化时,系统会触发该回调。
onConfigurationUpdate(newConfig: Configuration): void {
    const currentColorMode: ConfigurationConstant.ColorMode | undefined = AppStorage.get('currentColorMode')
    if (currentColorMode !== newConfig.colorMode) {
      AppStorage.setOrCreate<ConfigurationConstant.ColorMode>('currentColorMode', newConfig.colorMode)
    }
}

/**
 * 表示系统默认区域。通常表示状态栏区域,悬浮窗状态下的应用主窗中表示三点控制栏区域
 */
const statusBarType = window.AvoidAreaType.TYPE_SYSTEM
/**
 * 表示底部导航区域。根据用户设置,可表现为导航条或三键导航栏
 */
const navBarType = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR

interface AvoidArea {
  topRectHeight: number
  bottomRectHeight: number
}

private context = this.getUIContext()?.getHostContext() as common.UIAbilityContext
private windowClass: window.Window = this.context.windowStage.getMainWindowSync()
@State avoidArea: AvoidArea = { topRectHeight: 0, bottomRectHeight: 0 }

@StorageProp('currentColorMode')
@Watch('onCurrentColorModeChange')
 /**
   * COLOR_MODE_NOT_SET    -1    未设置颜色模式。
   * COLOR_MODE_DARK    0    深色模式。
   * COLOR_MODE_LIGHT    1    浅色模式。
*/
currentColorMode: ConfigurationConstant.ColorMode | undefined = undefined

/**
  * 颜色模式改变
*/
onCurrentColorModeChange(): void {
  if (this.currentColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT) {
    this.windowClass?.setWindowSystemBarProperties({
      statusBarContentColor: '#FFFFFF'
    })
  } else if (this.currentColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK) {
    this.windowClass?.setWindowSystemBarProperties({
      statusBarContentColor: '#FFFFFF'
    })
  }
}

/**
  * 使用Window.setWindowLayoutFullScreen()方法设置窗口全屏模式
  * 通过Window.getWindowAvoidArea()方法获取状态栏和导航栏高度,并用状态变量avoidArea记录
  * 使用avoidAreaChange事件监听避让区域的变化,变化时更新状态变量avoidArea
*/
aboutToAppear() {
  this.onCurrentColorModeChange()
  // 设置窗口全屏模式沉浸式
  this.windowClass.setWindowLayoutFullScreen(true)

  // 设置状态栏和导航条的高度
   const statusBarArea = this.windowClass.getWindowAvoidArea(statusBarType)
  this.avoidArea.topRectHeight = statusBarArea.topRect.height
  const navBarArea = this.windowClass.getWindowAvoidArea(navBarType)
  this.avoidArea.bottomRectHeight = navBarArea.bottomRect.height
  // window monitor
  this.windowClass.on('avoidAreaChange',this.onAvoidAreaChange)
}

onAvoidAreaChange = () =>  (data: window.AvoidAreaOptions) => {
  if (data.type === statusBarType) {
    this.avoidArea.topRectHeight = data.area.topRect.height
  } else if (data.type === navBarType) {
    this.avoidArea.bottomRectHeight = data.area.bottomRect.height
  }
}

aboutToDisappear(): void {
  this.windowClass.setWindowLayoutFullScreen(false)
  this.windowClass.off('avoidAreaChange', this.onAvoidAreaChange)
}

Column() {

}
.padding({
  top: this.avoidArea.topRectHeight,
  bottom: this.avoidArea.bottomRectHeight
})
.backgroundColor(Color.Black)

🧱 🔥🔥🔥完整实战

// 1、添加日间颜色:src/main/resources/base/element/color.json
{
  "color": [
    {
      "name": "start_window_background",
      "value": "#FFFFFF"
    },
    {
      "name": "text_color",
      "value": "#000000"
    },
    {
      "name": "mockImg_color",
      "value": "#000000"
    }
  ]
}

// 2、添加日间icon:src/main/resources/base/media
// like.png | mock.svg

// 3、添加夜间颜色:src/main/resources/dark/element/color.json
{
  "color": [
    {
      "name": "start_window_background",
      "value": "#000000"
    },
    {
      "name": "text_color",
      "value": "#FFFFFF"
    },
    {
      "name": "mockImg_color",
      "value": "#FFFFFF"
    }
  ]
}

// 4、添加夜间icon:src/main/resources/base/media
// like.png | mock.svg

// 5、设置主题颜色模式:src/main/ets/entryability/EntryAbility.ets
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  AppStorage.setOrCreate<ConfigurationConstant.ColorMode>('currentColorMode', this.context.config.colorMode)
}
onConfigurationUpdate(newConfig: Configuration): void {
  const currentColorMode: ConfigurationConstant.ColorMode | undefined = AppStorage.get('currentColorMode')
  if (currentColorMode !== newConfig.colorMode) {
    AppStorage.setOrCreate<ConfigurationConstant.ColorMode>('currentColorMode', newConfig.colorMode)
  }
}

// 6、页面调试
import { common, ConfigurationConstant } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

/**
 * 表示系统默认区域。通常表示状态栏区域,悬浮窗状态下的应用主窗中表示三点控制栏区域
 */
const statusBarType = window.AvoidAreaType.TYPE_SYSTEM
/**
 * 表示底部导航区域。根据用户设置,可表现为导航条或三键导航栏
 */
const navBarType = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR

interface AvoidArea {
  topRectHeight: number
  bottomRectHeight: number
}

@Entry
@Component
struct Index {
  private context = this.getUIContext()?.getHostContext() as common.UIAbilityContext
  private windowClass: window.Window = this.context.windowStage.getMainWindowSync()
  @State avoidArea: AvoidArea = { topRectHeight: 0, bottomRectHeight: 0 }

  @StorageProp('currentColorMode')
  @Watch('onCurrentColorModeChange')
  /**
   * COLOR_MODE_NOT_SET    -1    未设置颜色模式。
   * COLOR_MODE_DARK    0    深色模式。
   * COLOR_MODE_LIGHT    1    浅色模式。
   */
  currentColorMode: ConfigurationConstant.ColorMode | undefined = undefined

  /**
   * 颜色模式改变
   */
  onCurrentColorModeChange(): void {
    if (this.currentColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT) {
      this.windowClass?.setWindowSystemBarProperties({
        statusBarContentColor: '#FFFFFF'
      })
    } else if (this.currentColorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK) {
      this.windowClass?.setWindowSystemBarProperties({
        statusBarContentColor: '#FFFFFF'
      })
    }
  }

  /**
   * 使用Window.setWindowLayoutFullScreen()方法设置窗口全屏模式
   * 通过Window.getWindowAvoidArea()方法获取状态栏和导航栏高度,并用状态变量avoidArea记录
   * 使用avoidAreaChange事件监听避让区域的变化,变化时更新状态变量avoidArea
   */
  aboutToAppear() {
    this.onCurrentColorModeChange()
    // 设置窗口全屏模式沉浸式
    this.windowClass.setWindowLayoutFullScreen(true)

    // 设置状态栏和导航条的高度
    const statusBarArea = this.windowClass.getWindowAvoidArea(statusBarType)
    this.avoidArea.topRectHeight = statusBarArea.topRect.height
    const navBarArea = this.windowClass.getWindowAvoidArea(navBarType)
    this.avoidArea.bottomRectHeight = navBarArea.bottomRect.height
    // window monitor
    this.windowClass.on('avoidAreaChange',this.onAvoidAreaChange)
  }

  onAvoidAreaChange = () =>  (data: window.AvoidAreaOptions) => {
    if (data.type === statusBarType) {
      this.avoidArea.topRectHeight = data.area.topRect.height
    } else if (data.type === navBarType) {
      this.avoidArea.bottomRectHeight = data.area.bottomRect.height
    }
  }

  aboutToDisappear(): void {
    this.windowClass.setWindowLayoutFullScreen(false)
    this.windowClass.off('avoidAreaChange', this.onAvoidAreaChange)
  }

  build() {
      Column() {
      Column({ space: 20 }) {
        Text('汉堡黄🍔')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          // .fontColor($r('sys.color.font_primary')) // 系统颜色
          .fontColor($r('app.color.text_color')) // 自定义资源颜色

        Image($r('app.media.mock'))
          .fillColor($r('app.color.mockImg_color'))
          .width(40)
          .aspectRatio(1)

        Image($r('app.media.like'))
          .width(40)
          .aspectRatio(1)

        Text() {
          SymbolSpan($r('sys.symbol.ohos_trash'))
            .fontWeight(FontWeight.Medium)
            .fontSize(40)
            .fontColor([$r('app.color.mockImg_color')])
        }
      }

    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor(Color.Black) // 这里测试沉浸式设置固定的颜色查看状态栏背景变化
    // .backgroundColor($r('app.color.start_window_background')) // 自定义资源颜色
    // .backgroundColor($r('sys.color.comp_background_primary')) // 系统颜色
    .padding({
      top: this.avoidArea.topRectHeight,
      bottom: this.avoidArea.bottomRectHeight
    })

  }
}

📝 案例主要是常用使用小案例,具体按实际业务场景变动即可,换汤不换药

🌸🌼🌺

Logo

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

更多推荐