鸿蒙-沉浸式状态栏

740 阅读6分钟

一 概述

鸿蒙移除了三键导航(导航栏)换成了导航条,但是还存在获取导航栏高度、隐藏导航栏和设置导航栏高度以及内容颜色的API,所以封装一个ImmersionBar的util使用 ,如果想看修改状态栏背景颜色的,请在真机下看,模拟器不生效

1.1 方案一: Navigation 以及 NavDestination 伪沉浸式

Navigation现在是鸿蒙推荐的页面以及路由方案,它本身就是一个沉浸式,但是内容会避免 ,API Version 11开始默认支持安全区避让特性,但是定制化比较低,只是Navigation会包括状态栏和导航条(导航栏),比如我们设置Navigation的 backgroundColor(Color.Red),但是如果设置成了真正的沉浸式,那么Navigation的Content内容就会铺满屏幕

1.2 方案二: window.setWindowSystemBarEnable,隐藏状态栏或者导航栏,相当于真正全屏模式

  • 隐藏状态栏或者导航栏,window.setWindowSystemBarEnable([])
  • 显示状态栏或者导航栏 window.setWindowSystemBarEnable(['navigation', 'status'])
  • status :控制着 导航条 和 状态栏
  • navigation:控制着导航栏,但是现在鸿蒙没有导航栏。。。

1.3 方案三: setWindowLayoutFullScreen 沉浸式,不是设置全屏

设置主窗口或子窗口的布局是否为沉浸式布局,这个方法 会保留状态栏和导航条(导航栏),感觉跟他的名字不太符合他的名字(如果想实现真正的全屏,请使用 setWindowSystemBarEnable ),api12 之后推荐使用 setImmersiveModeEnabledState

,然后配合着更改状态栏和导航栏背景颜色以及内容颜色,来实现内容

let windowClass = await window.getLastWindow(getContext(this))
windowClass.setWindowLayoutFullScreen(true)
  .then(() => {
    Logger.info('设置成功')
  }).catch((e: BusinessError) => {
  Logger.info(e)
})

1.4 方案四: setImmersiveModeEnabledState

使用 setImmersiveModeEnabledState(沉浸式) 配合 setWindowSystemBarProperties(设置状态栏导航栏颜色)

let windowClass = await window.getLastWindow(getContext(this))
windowClass.setImmersiveModeEnabledState(true)

二 封装 ImmersionBar 的工具类

先创建一个Config类

  • fullScreen:是否为全屏布局,即隐藏状态栏和导航条(导航栏),如果为 true ,那么相当于其他的无效,其他配置都会被无视
  • immersion: 是否是沉浸式
  • statusBarColor: 设置状态的背景颜色
  • statusBarContentColor: 状态栏栏文字颜色
  • navigationBarColor: 设置导航栏背景颜色,但是鸿蒙去掉了导航栏,增加了导航条,(先保留着这个属性吧)
  • navigationBarContentColor: 导航栏文字颜色,但是鸿蒙去掉了导航栏,增加了导航条(先保留着这个属性吧)
export interface ImmersionConfig {
  // 是否为全屏布局,即隐藏状态栏和导航条(导航栏),
  // 如果为 true ,那么相当于其他的无效,其他配置都会被无视
  fullScreen?: boolean
  // 是否是沉浸式
  immersion?: boolean
  // 设置状态的背景颜色
  statusBarColor?: string
  // 状态栏栏文字颜色
  statusBarContentColor?: string
  // 设置导航栏背景颜色,但是鸿蒙去掉了导航栏,增加了导航条,(先保留着这个属性吧)
  navigationBarColor?: string
  // 导航栏文字颜色,但是鸿蒙去掉了导航栏,增加了导航条(先保留着这个属性吧)
  navigationBarContentColor?: string
}

ImmersionBar 工具类

有一个坑点,大部分一个App就一个window,如果一处设置配置之后,比如沉浸式,全部都是沉浸式,需要在移除这个页面的时候,再还原成以前的配置,所以我们需要记录上一个的配置信息。比如我们A(非沉浸式)->B(沉浸式),我们需要在B的 onPageShow 的里面先拿到上一个的配置,在设置沉浸式,在B的onPageHide时候,再还原回来原来的配置

  • with(window?: window.Window),绑定当前的window,如果不调用,默认使用的是getLastWindow
  • getStatusHeight:获取状态栏的高度,单位是vp,全屏下是0
  • getNavigationHeight:导航条+导航栏 的高度,单位是vp,时候咱们不关心到底是导航栏还是导航条,我们只想做沉浸式,可以使用此方法,全屏下是0
  • getNavigationIndicatorHeight:获取导航条的高度,单位是vp,全屏下是0
  • getNavigationBarHeight:获取导航栏的高度,单位是vp,鸿蒙去掉了导航栏,加入了导航条。也暂时放这里,但是还有获取导航栏高度、设置导航栏颜色、设置导航栏是否隐藏的api,估摸着 可能为老版手机升级到鸿蒙做的适配,全屏下是0
  • setImmersion 设置沉浸式,如果只想简单的设置沉浸式,可以直接调用这个
  • removeImmersion 移除沉浸式
  • init(config?: ImmersionConfig),根据 ImmersionConfig 设置是否全屏,是否沉浸式,状态栏和导航栏背景颜色以及字体颜色
  • getCurrentConfig(),获取当前的Config
class ImmersionBar {
  private window?: window.Window
  private config: ImmersionConfig = {}

  with(window?: window.Window) {
    this.window = window
    return this;
  }

  /**
   * 拿到当前的配置信息,还原的时候,直接设置这个就行
   * @returns
   */
  getCurrentConfig(): ImmersionConfig {
    return this.config;
  }

  /**
   * @returns 获取状态栏的高度,单位是vp
   */
  async getStatusHeight(): Promise<number> {
    await this.checkWindow()
    let type = window.AvoidAreaType.TYPE_SYSTEM;
    let avoidArea = this.window?.getWindowAvoidArea(type);
    return px2vp(avoidArea?.topRect.height)
  }

  /**
   * 鸿蒙去掉了导航栏,加入了导航条。也暂时放这里把,
   * 但是还有获取导航栏高度、设置导航栏颜色、设置导航栏是否隐藏的api,估摸着 可能为老版手机升级到鸿蒙做的适配
   * @returns 获取导航栏的高度,单位是vp
   */
  async getNavigationBarHeight(): Promise<number> {
    await this.checkWindow()
    let type = window.AvoidAreaType.TYPE_SYSTEM;
    let avoidArea = this.window?.getWindowAvoidArea(type);
    return px2vp(avoidArea?.bottomRect.height)
  }

  /**
   * 获取导航条的高度
   * @returns 获取导航条的高度,单位是vp
   */
  async getNavigationIndicatorHeight(): Promise<number> {
    await this.checkWindow()
    let avoidAreaNavi = this.window?.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
    return px2vp(avoidAreaNavi?.bottomRect.height)
  }

  /**
   * 获取 导航条+导航栏 的高度
   * @returns 获取 导航条+导航栏 的高度,单位是vp
   */
  async getNavigationHeight(): Promise<number> {
    await this.checkWindow()
    let indicatorHeight = await this.getNavigationIndicatorHeight()
    let navigationBar = await this.getNavigationBarHeight()
    return indicatorHeight + navigationBar;
  }

  /**
   * 设置沉浸式
   */
  async setImmersion(): Promise<void> {
    await this.checkWindow()
    this.config.immersion = true
    this.window?.setImmersiveModeEnabledState(true)
  }

  /**
   * 移除沉浸式
   */
  async removeImmersion(): Promise<void> {
    await this.checkWindow()
    this.config.immersion = false
    this.window?.setImmersiveModeEnabledState(false)
  }

  async init(config?: ImmersionConfig): Promise<void> {
    await this.checkWindow()
    if (config) {
      // 设置当前的config
      this.config = this.config
      if (config.fullScreen != undefined) {
        let names: Array<'status' | 'navigation'> = [];
        if (!config.fullScreen) {
          names.push('status')
          names.push('navigation')
        }
        this.window?.setWindowSystemBarEnable(names)
      }
      // 是否沉浸式
      if (config.immersion != undefined) {
        this.window?.setImmersiveModeEnabledState(config.immersion)
      }
      // 状态栏颜色
      await this.window?.setWindowSystemBarProperties(config as window.SystemBarProperties)
    }
  }

  private async checkWindow(): Promise<void> {
    if (!this.window) {
      this.window = await window.getLastWindow(getContext(this))
    }
  }
}

export interface ImmersionConfig {
  // 是否为全屏布局,即隐藏状态栏和导航条(导航栏),
  // 如果为 true ,那么相当于其他的无效,其他配置都会被无视
  fullScreen?: boolean
  // 是否是沉浸式
  immersion?: boolean
  // 设置状态的背景颜色
  statusBarColor?: string
  // 状态栏栏文字颜色
  statusBarContentColor?: string
  // 设置导航栏背景颜色,但是鸿蒙去掉了导航栏,增加了导航条,(先保留着这个属性吧)
  navigationBarColor?: string
  // 导航栏文字颜色,但是鸿蒙去掉了导航栏,增加了导航条(先保留着这个属性吧)
  navigationBarContentColor?: string
}

export default new ImmersionBar()

使用的demo

Column({ space: 5 }) {
  Image($r('app.media.lovely1')).width('100%').height(200)
  Button('全屏').onClick(() => {
    ImmersionBar.init({ fullScreen: true })
  }).width('80%')
  Button('非全屏').onClick(() => {
    ImmersionBar.init({ fullScreen: false })
  }).width('80%')
  Button('沉浸式').onClick(() => {
    ImmersionBar.init({ immersion: true })
  }).width('80%')
  Button('非沉浸式').onClick(() => {
    ImmersionBar.init({ immersion: false })
  }).width('80%')
  Button('沉浸式 并且设置 状态栏颜色').onClick(() => {
    ImmersionBar.init({
      immersion: true,
      statusBarColor: '#FF0000',
      statusBarContentColor: '#ff0048ff'
    })
  }).width('80%')
  Button('获取状态栏、导航条(导航栏)高度').onClick(() => {
    // 如果是在全屏模式下面,那么下面着四个个降全是0
    ImmersionBar.getStatusHeight().then((height: number) => {
      Logger.info(`状态栏的高度 = ${height} 单位vp`)
    })
    ImmersionBar.getNavigationBarHeight().then((height: number) => {
      Logger.info(`导航栏的高度 = ${height} 单位vp`)
    })
    ImmersionBar.getNavigationIndicatorHeight().then((height: number) => {
      Logger.info(`导航条的高度 = ${height} 单位vp`)
    })
    ImmersionBar.getNavigationHeight().then((height: number) => {
      // 有时候咱们不关心到底是导航栏还是导航条,我们只想做沉浸式,可以使用此方法
      Logger.info(`导航条+导航栏的高度 = ${height} 单位vp`)
    })
  }).width('80%')
  Button('使用with绑定window').onClick(() => {
    window.getLastWindow(getContext(this)).then((window) => {
      ImmersionBar.with(window).setImmersion()
    })

  }).width('80%')
}
.height('100%')
.width('100%')
.backgroundColor(Color.Gray)

源码位置gitee

参考