Harmonyos next:沉浸式模式

315 阅读4分钟

官方介绍:沉浸式模式通常指让应用的界面更加专注于内容,不希望用户被无关元素干扰。在移动端应用中,全屏窗口元素包括状态栏、应用界面和导航栏沉浸式页面开发常通过将应用页面延伸到状态栏和导航栏的方式,来达到以下目的:

  • 使页面和避让区域的色调统一,为用户提供更好的视觉体验。
  • 最大程度利用屏幕可视区域,使页面获得更大的布局空间。
  • 提供完全沉浸的体验,让用户沉浸其中,不被其他事物所干扰。

在开发过程中页面往往有背景色,而且页面的背景色和状态栏和导航栏的页面大概率是不匹配的,看起来很不和谐。此时用沉浸式模式就可以比较方便的解决这个问题。当然沉浸式能解决的问题还有上面提到的避免干扰,沉浸体验等。

一、适配方案

1.窗口全屏模式

2.扩展组件安全区域

这两种方案在维度上有区别,需要根据具体的业务场景来使用:

窗口全屏会让整个ability上的页面都被影响,是全局性质的;扩展安全区域则只针对当前控件页面,不影响其他控件。

二、窗口全屏模式

实现如下,拿到window对象开启全屏,拿到状态栏、导航栏高度,然后通知给其他页面

  public static requestFullScreen(windowStage: window.WindowStage, context: Context,
    isLayoutFullScreen: boolean): void {
    windowStage.getMainWindow((err: BusinessError, data: window.Window) => {
      if (err.code) {
        Logger.error(TAG, 'Failed to obtain the main window. Cause: ' + JSON.stringify(err));
        return;
      }
      let windowClass: window.Window = data;
      AppStorage.setOrCreate<window.Window>('currentWindowClass', windowClass)
      Logger.info(TAG, 'Succeeded in obtaining the main window. Data: ' + JSON.stringify(data));
​
      // Realize the immersive effect
      // let isLayoutFullScreen = true;
      try {
        // Get status bar height.
        let area: window.AvoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
        let naviBarArea: window.AvoidArea =
          windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
        Logger.info(TAG,
          'Succeeded get the window navigation indicator HEIGHT: ' + px2vp(naviBarArea.bottomRect.height) + ' area: ' +
          JSON.stringify(naviBarArea));
        WindowUtil.getDeviceSize(context, area, naviBarArea, isLayoutFullScreen);
        AppStorage.setOrCreate<boolean>('CurrentIsLayoutFullScreen', isLayoutFullScreen)
        if (area.topRect.height > 0) {
          let promise: Promise<void> = windowClass.setWindowLayoutFullScreen(isLayoutFullScreen);
          promise.then(() => {
            Logger.info(TAG, 'Succeeded in setting the window layout to full-screen mode.');
          }).catch((err: BusinessError) => {
            Logger.error(TAG, 'Failed to set the window layout to full-screen mode. Cause:' + JSON.stringify(err));
          });
        }
      } catch {
        Logger.error(TAG, 'Failed to set the window layout to full-screen mode. ');
      }
      return windowClass
    });
  }
  static getDeviceSize(context: Context, area: window.AvoidArea, naviBarArea: window.AvoidArea,
    isLayoutFullScreen: boolean): void {
    // Get device height.
    window.getLastWindow(context).then((data: window.Window) => {
      let properties = data.getWindowProperties();
      AppStorage.setOrCreate<number>('statusBarHeight', px2vp(area.topRect.height));
      AppStorage.setOrCreate<number>('naviIndicatorHeight', px2vp(naviBarArea.bottomRect.height));
      AppStorage.setOrCreate<number>('deviceHeight', px2vp(properties.windowRect.height));
      AppStorage.setOrCreate<number>('deviceWidth', px2vp(properties.windowRect.width));
    });
  }

通过windowClass.setWindowLayoutFullScreen(isLayoutFullScreen)设置开启窗口全屏

通过getWindowAvoidArea获取到状态栏和导航栏的高度

let area: window.AvoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
let naviBarArea: window.AvoidArea =
  windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);

然后通过appStorage传递出去,让其他页面可以监听是否开了全屏,状态栏高度多少,导航栏高度多少。

其他页面在根组件对应适配,把状态栏和导航栏的padding设置出来,防止app的页面和系统状态栏重叠

    .padding({
        // app是作为组件使用
        top: this.isLayoutFullScreen  ? this.statusBarHeight : 0,
        bottom: this.isLayoutFullScreen ? this.naviIndicatorHeight : 0
      })

三、扩展组件安全区域

expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])

给组件设置对应属性,可以让页面边缘延伸到状态栏和导航栏。

和全屏窗口不一样的是,页面的元素不会直接顶上去,而是边缘会继续渲染到状态栏、导航栏里面,有点像边缘强行被拉宽到状态栏、导航栏里面(类似canvas的一种渲染模式)。

因此不需要在根组件额外设置padding。

需要注意的是,有时组件是嵌套的,只在子组件设置expandSafeArea不会生效,需要同时给父组件设置才会生效。如下面代码:

Column() {
                  Scroll() {
                    Image($r('app.media.xxx'))
                  }.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
                  .scrollBar(BarState.Off)
                }.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])

四、sdk适配场景

当我们做的SDK需要提供给外部使用时,就需要同时考虑这两种沉浸模式适配。

假设sdk内的页面组件只是通过expandSafeArea来实现沉浸式的,因为这样预想的是只对sdk页面生效,不影响app的其他页面。然后在壳子工程测试的时候效果也是正常的沉浸式页面。

集成sdk的app可能使用了全屏窗口,也可能没有。如果没有开全屏窗口,那和壳子工程测试一样,完全没有问题。但是如果app是有了全屏窗口,这时候sdk的页面就必然会和状态栏重叠在一起。

这时候一般两种解决方向,app处理或者sdk适配。

正常情况下如果sdk没有适配,就需要app处理,比如进入sdk时关闭窗口全屏,退出sdk时回复窗口全屏。

当然sdk如果适配了这种情况,app接入就会简单一些。

适配方案其实也很简单 ,expandSafeArea和padding都设置就完事儿了。

.padding({
  // app是作为组件使用
  top: this.isLayoutFullScreen ? this.statusBarHeight : 0,
  bottom: this.isLayoutFullScreen ? this.naviIndicatorHeight : 0
})
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])

sdk提供设置全屏的接口给app,参数传是否全屏、状态栏和导航栏高度。

sdk在组件页面两种都设置,当非全屏时,padding失效,expand生效,整体沉浸式生效。

当全屏时,padding生效,expand生效,整体沉浸式生效。