鸿蒙页面实战 - 如何实现沉浸式效果的页面

321 阅读3分钟

引子

在当前的移动端设计中,开发一个沉浸式效果的页面还是一个比较常见的需求,沉浸式效果是一种设计理念,旨在让用户更深层次地“沉浸”在页面或应用体验中,从而增加使用的专注感和愉悦度。沉浸式效果通过视觉设计、动画、交互设计等手段,模糊了用户与应用之间的界限,让内容更贴近用户,增加体验的真实性和直观性。在移动端通常的做法就是通过全屏显示来减少视觉干扰,去除多余的导航栏、状态栏和其他 UI 元素,以提供更纯粹的体验。

比如掘金app的我的页面或者 QQ 的空间页面等。沉浸式效果类似下图:

WechatIMG381.jpg

代码准备

首先,我们新建一个项目。给项目添加一张图片,然后将下面的代码复制到 Index.ets 文件中:

@Entry
@Component
struct Index {
  build() {
    Column() {
      Image($r('app.media.top'))
        .width('100%')
      Blank().height(100)
      Text("这里是内容区这里是内容区这里是内容区这里是内容区这里是内容区这里是内容区这里是内容区这里是内容区")
    }
    .backgroundColor(Color.Blue)
    .height('100%')
    .width('100%')
  }
}

效果图如下:

截屏2024-11-08 14.26.42.png

可以看到正常情况下,即使我们设置组件宽高占比 100% 也是无法占满整个屏幕的,头部空出的空白为状态栏,底部空出的空白为导航条。下面我们来将该页面改为沉浸式效果。

沉浸式效果

在鸿蒙系统中,如果想实现类似于 QQ 空间沉浸式效果是非常简单的,只需要将窗口设置为全屏即可,在 EntryAbility 中的 onWindowStageCreate(windowStage: window.WindowStage): void 函数中添加如下代码:

onWindowStageCreate(windowStage: window.WindowStage): void {
  // Main window is created, set main page for this ability
  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

  windowStage.loadContent('pages/Index', (err) => {
    if (err.code) {
      hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
      return;
    }

    // 1. 设置窗口全屏
    const windowClass = windowStage.getMainWindowSync(); // 获取应用主窗口
    let isLayoutFullScreen = true;
    windowClass.setWindowLayoutFullScreen(isLayoutFullScreen);
    hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
  });
}

上述代码即可实现如下的效果:

截屏2024-11-08 15.07.09.png

如果你想开发的沉浸式效果,组件需要避让状态栏和导航栏,则还需要多写几步。

避让状态栏和导航栏的沉浸式效果

避让的沉浸式效果除了将窗口设置为全屏之外,还需要下面的三步:

  • 获取布局避让遮罩的区域高度
  • 注册监听函数,动态获取避让区高度
  • 为需要实现沉浸式效果的组件设置相应的间距

第一步,还是在这个函数里,添加获取避让区高度的代码:

// 2. 获取布局避让遮挡的区域
let type = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR; // 以导航条避让为例
let avoidArea = windowClass.getWindowAvoidArea(type);
const bottomRectHeight = avoidArea.bottomRect.height; // 获取到导航条区域的高度
AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight);

type = window.AvoidAreaType.TYPE_SYSTEM; // 以状态栏避让为例
avoidArea = windowClass.getWindowAvoidArea(type);
const topRectHeight = avoidArea.topRect.height; // 获取状态栏区域高度
AppStorage.setOrCreate('topRectHeight', topRectHeight);

第二步,依然是这个函数,注册监听函数,动态获取避让区域的高度:

// 3. 注册监听函数,动态获取避让区域数据
windowClass.on('avoidAreaChange', (data) => {
  if (data.type === window.AvoidAreaType.TYPE_SYSTEM) {
    AppStorage.setOrCreate('topRectHeight', data.area.topRect.height);
  } else if (data.type == window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) {
    AppStorage.setOrCreate('bottomRectHeight', data.area.bottomRect.height);
  }
});

第三步,回到 Index.ets 文件,给当前的 Column 组件设置 padding 属性:

@Entry
@Component
struct Index {
  @StorageProp('bottomRectHeight')
  bottomRectHeight: number = 0;
  @StorageProp('topRectHeight')
  topRectHeight: number = 0;
  build() {
    Column() {
      ...
    }
    .backgroundColor(Color.Blue)
    .height('100%')
    .width('100%')
    .padding({ top: px2vp(this.topRectHeight), bottom: px2vp(this.bottomRectHeight) })
  }
}

避让状态栏和导航栏的沉浸式效果图如下: 截屏2024-11-08 15.08.55.png