HarmonyOS 鸿蒙Next区域避让开发指导

HarmonyOS 鸿蒙Next区域避让开发指导

一、概述

在移动应用开发中,不同设备的屏幕形态各异,如刘海屏、全面屏、折叠屏等,同时系统状态栏、导航栏、软键盘等元素也会占据屏幕空间。为了确保应用内容在各种设备和场景下都能正常显示,不被遮挡,RN、Flutter和H5提供了一系列区域避让的机制和接口。此外,随着折叠屏设备逐渐普及,为了让应用在折叠屏设备上提供良好的用户体验,需要对布局进行特殊处理,RN和Flutter为此推出了折叠屏布局专用组件:FolderStack和FoldSplitContainer。本文将详细介绍各个框架区域避让和折叠屏布局组件的实现原理、适配指导以及具体的场景案例。

使用场景

典型应用全屏窗口UI元素包括状态栏、应用界面和底部导航条,其中状态栏和导航条,通常在全屏沉浸式布局下称为避让区,避让区之外的区域称为安全区。界面元素在不同设备上存在差异,下面是不同设备上状态栏、挖孔区、导航栏的位置差异,包括:直板机、PAD、PC、折叠屏(小折叠、双折、三折)。

说明

下图中标记区域含义:1为状态栏、2为挖孔区、3为导航栏。

图1 直板机界面元素示意图

直板机界面元素示意图

图2 PAD界面元素示意图

PAD界面元素示意图

图3 PC界面元素示意图

PC界面元素示意图

图4 折叠屏–小折叠 界面元素示意图(左图展开态 右图折叠态)

折叠屏--小折叠 界面元素示意图

图5 折叠屏–双折叠 界面元素示意图(左图折叠态 右图展开态)

折叠屏--双折叠 界面元素示意图

图6 折叠屏–三折叠 界面元素示意图(左图折叠态 右图二屏折叠态 下图三屏全展开态)

折叠屏--三折叠 界面元素示意图

面对上述不同设备的避让区差异,可以按照如下三种场景实现应用沉浸式效果:

1.1 安全区布局场景

布局系统保持安全区内布局,确保应用内容不会延伸到状态栏、导航栏区域。

1.2 安全区布局+背景沉浸模式

布局系统保持安全区内布局,然后延伸绘制内容(如背景色、背景图)到状态栏和导航条区域,实现沉浸式效果。

1.3 全屏布局+避让场景

布局系统保持全屏布局,通过相关接口获取避让区域位置、大小等信息,调整元素位置,确保不会被避让区遮挡。

二、实现原理

2.1 RN 区域避让实现原理

区域避让主要借助AvoidArea接口提供的能力实现,该接口由@hadss/react_native_avoid_area库提供。该接口包含几个核心方法,用于处理与区域避让相关的操作。在不同平台上,这些方法的实现和使用存在差异。

getWindowAvoidArea

此方法接收一个AvoidAreaType避让区域类型的参数,通过该参数可以获取当前应用窗口内容需要规避的区域。这些区域可能包括系统栏、刘海屏、手势操作区、软键盘等与窗口内容重叠时需要避让的区域。接口返回信息中包含对应避让区域是否可见、位置信息及宽高信息,应用可以通过这些信息调整内容布局,避免内容被遮挡。

接口返回信息结构如下:

{
    "visible": true, // 是否遮挡布局 
    "leftRect": { // 左侧避让区域 
        "left": 0, // 距离左侧的边距 
        "top": 0, // 距离上方的边距 
        "width": 0, // 避让区域宽度 
        "height": 0 // 避让区域高度 
    }, 
    "topRect": { // 上方避让区域 
        // 结构与leftRect相同 
    }, 
    "rightRect": { // 右侧避让区域 
        // 结构与leftRect相同 
    }, 
    "bottomRect": { // 下方避让区域 
        // 结构与leftRect相同 
    } 
}

接口使用示例如下:

// AvoidArea避让区域信息,AvoidAreaType避让区域类型
import { AvoidArea, AvoidAreaType } from "@hadss/react_native_avoid_area/src/turbo/NativeAvoidModule"; 
// AvoidArea API
import { Avoid } from '@hadss/react_native_avoid_area/src/index'
// 获取避让区域
let avoidArea = Avoid.getWindowAvoidArea(type);

addAvoidAreaListener

该方法用于添加系统规避区变化事件的监听。当系统规避区发生变化时,如软键盘弹出或收起、设备旋转等,会触发相应的回调函数,应用可以在回调中更新布局。使用示例如下:

// AvoidArea避让区域信息,AvoidAreaType避让区域类型
import { AvoidArea, AvoidAreaType } from "@hadss/react_native_avoid_area/src/turbo/NativeAvoidModule"; 
// AvoidArea API
import { Avoid } from '@hadss/react_native_avoid_area/src/index'
// 添加系统规避区变化事件的监听
Avoid.addAvoidAreaListener(data => { 
  // 开发者基于AvoidAreaType自行逻辑处理 
});

removeAvoidAreaListener

用于移除之前添加的系统规避区变化事件监听,避免不必要的回调触发,节省系统资源。使用示例如下:

// AvoidArea API
import { Avoid } from '@hadss/react_native_avoid_area/src/index'; 
// 移除系统规避区变化事件的监听
Avoid.removeAvoidAreaListener();

2.2 Flutter 区域避让实现原理

区域避让主要借助 AvoidAreaApi 这个对外提供能力的接口来实现。该接口包含几个核心方法,用于处理与区域避让相关的操作。

getWindowAvoidArea

此方法接收一个AvoidAreaType类型的参数,通过该参数可以获取当前应用窗口内容需要规避的区域。这些区域可能包括系统栏、刘海屏、手势操作区、软键盘等与窗口内容重叠时需要避让的区域。例如,在处理刘海屏设备时,应用可以通过该方法获取刘海屏区域的信息,从而调整内容布局,避免内容被刘海遮挡。

// packages/avoid_area/ohos/src/main/ets/hadss/avoid_area/AvoidAreaPlugin.ets 
getWindowAvoidArea(type: window.AvoidAreaType, result: MethodResult) { 
  const windowAvoidArea = this.windowClass?.getWindowAvoidArea(type ?? window.AvoidAreaType.TYPE_SYSTEM); 
  result.success(JSON.stringify(windowAvoidArea)); 
}

addAvoidAreaListener

该方法用于添加系统规避区变化事件的监听。当系统规避区发生变化时,如软键盘弹出或收起、设备旋转等,会触发相应的回调函数,应用可以在回调中更新布局。

// packages/avoid_area/ohos/src/main/ets/hadss/avoid_area/AvoidAreaPlugin.ets 
addAvoidAreaListener(eventSink?: EventSink) { 
  this.windowClass?.on('avoidAreaChange', (avoidAreaOptions: window.AvoidAreaOptions) => { 
    eventSink?.success(JSON.stringify(avoidAreaOptions)) 
  }) 
}

removeAvoidAreaListener

用于移除之前添加的系统规避区变化事件监听,避免不必要的回调触发,节省系统资源。

// packages/avoid_area/ohos/src/main/ets/hadss/avoid_area/AvoidAreaPlugin.ets 
removeAvoidAreaListener() { 
  this.windowClass?.off('avoidAreaChange'); 
}

2.3 Flutter 折叠屏布局组件实现原理

FoldSplitContainer

定义了 FoldSplitContainer 组件,用于实现折叠屏二分栏、三分栏在展开态、悬停态以及折叠态的区域控制。

  • 状态监听:通过 FolderStackPlugin.folderStateEvents 监听折叠屏状态变化,当状态变化时更新界面。
  • 布局构建:根据不同的折叠屏状态(展开、半折叠、折叠)调用不同的布局构建方法,如 _buildExpandedLayout、_buildHoverModeLayout 和 _buildFoldedRegionLayout。
  • 比例转换:通过 _ratioToFlex 方法将比例转换为整数 flex 因子,用于 Flex 组件的布局。

FolderStack

定义了 FolderStack 组件,继承于 Stack 控件,新增了折叠屏悬停能力,通过识别 upperItems 自动避让折叠屏折痕区后移到上半屏。

  • 状态监听:同样通过 FolderStackPlugin.folderStateEvents 监听折叠屏状态变化。
  • 布局处理:当设备处于半折叠且折痕方向为水平时,将 upperItems 中的子组件堆叠到上半屏,其他组件堆叠在下半屏。
  • 上下屏:通过折痕区域范围算出上下屏占用比例,然后使用 Flex 组件的布局。
  • **文件实现了折叠屏状态的监听功能,当折叠屏状态变化时,将状态信息通过 EventSink 发送给 Flutter 端。

接口桥接和公用封装主要在 folder_options.dart 和 folder_plugin.dart 中实现。

2.4 H5 区域避让实现原理

区域避让主要通过原生接口提供的能力实现,该接口包含几个核心方法,用于处理与区域避让相关的操作。

avoidAreaListener

该方法用于添加系统规避区变化事件的监听。通过监听 “avoidAreaChange” 参数,当系统规避区发生变化时,如软键盘弹出或收起、设备旋转等,会触发相应的回调函数,在回调中通过 runJavaScript() 的方法将变化的数据传递给 H5 页面。

async avoidAreaListener(): Promise<void> { 
  const windowClass = await this.webClassProxy.getMainWindow(); 
  try { 
    windowClass.on('avoidAreaChange', (data) => { 
      this.webController.runJavaScript(` 
        window.dispatchEvent(new CustomEvent('statusBarChange',{ detail: ${JSON.stringify(data)} })); 
      `); 
      Logger.debug('Succeeded in enabling the listener for system avoid area changes. type:' + 
      JSON.stringify(data.type) + ', area: ' + JSON.stringify(data.area)); 
    }); 
  } catch (exception) { 
    Logger.error(`Failed to enable the listener for system avoid area changes. Cause code: ${exception.code}, message: ${exception.message}`); 
  } 
}

registerJavaScriptProxy

注入 JavaScript 对象到 window 对象中,并在 window 对象中调用该对象的方法。实现 H5 页面调用原生方法。

Web({ src: CommonConstants.H5_URL, controller: this.webController }) 
  .onControllerAttached(() => { 
    this.webController.registerJavaScriptProxy(this.webClassProxy, 'webClass', 
      ['getAvoidArea', 'pxToVp', 'isFoldAndTablet', 'getWindowStatusType'], 
      [], 
      `{"javascriptProxyPermission":{"urlPermissionList":[{"scheme":"http","host":"${CommonConstants.HOST}","port":"${CommonConstants.PORT}","path":""}]}}`); 
    this.webController.refresh(); 
  })

getAvoidArea

Plugins 类中的 getAvoidArea() 接口负责获取系统默认避让区域信息。此方法接收一个 AvoidAreaType 类型的参数,通过该参数可以获取当前应用窗口内容需要规避的区域。这些区域可能包括系统栏、刘海屏、手势操作区、软键盘等与窗口内容重叠时需要避让的区域。通过原生接口获取窗口实例,然后调用窗口的 getWindowAvoidArea 方法获取相应类型的避让区域,接口返回信息中包含对应避让区域是否可见、位置信息及宽高信息,并将结果返回给 H5 端。

async getAvoidArea(type: window.AvoidAreaType) { 
  const windowClass = await this.getMainWindow(); 
  let avoidArea = windowClass.getWindowAvoidArea(type); 
  return avoidArea; 
}

三、适配指导

3.1 安全区布局场景

图7 AvoidArea实现安全布局

AvoidArea实现安全布局

RN适配指导

安全区布局场景主要是为了确保应用内容不会被系统状态栏、导航栏等遮挡。通过上述的 AvoidArea API 获取的避让区域相关信息,包括状态栏、导航栏以及挖孔区,给外层容器设置 padding,防止内部组件被避让区遮挡,从而实现安全布局效果。示例代码如下:

const FullScreenView = () => { 
    const [topPadding, setTopPadding] = useState(0); 
    const [bottomPadding, setBottomPadding] = useState(0); 
    const pixelRatio = PixelRatio.get() ? PixelRatio.get() : 1; 
    useEffect(() => { 
        const getAvoidArea = () => { 
            // 获取状态栏高度,设置上padding 
            let type = AvoidAreaType.TYPE_SYSTEM; 
            let avoidArea = Avoid.getWindowAvoidArea(type); 
            let topHeight = avoidArea.topRect.height / pixelRatio; 
            setTopPadding(topHeight); 
            // 获取导航栏高度,设置下padding 
            type = AvoidAreaType.TYPE_NAVIGATION_INDICATOR; 
            avoidArea = Avoid.getWindowAvoidArea(type); 
            let bottomHeight = avoidArea.bottomRect.height / pixelRatio; 
            setBottomPadding(bottomHeight); 
        }; 
        getAvoidArea(); 
        const listener = Avoid.addAvoidAreaListener(data => { 
        // 监听避让区域变化,逻辑处理 
        }); 
        return () => { 
            Avoid.removeAvoidAreaListener(); 
        }; 
    }, []); 
    return ( 
        <View style={[styles.container, { paddingTop: topPadding, paddingBottom: bottomPadding }]}> 
            <View style={styles.contentContainer}> 
                <Text style={[styles.textStyle, styles.otherTextStyle]}>title</Text> 
                <Text style={[styles.textStyle, styles.contentTextStyle]}>content</Text> 
                <Text style={[styles.textStyle, styles.otherTextStyle]}>footer</Text> 
            </View> 
        </View> 
    ); 
}; 
export default FullScreenView;

RN也提供了 SafeAreaView 安全布局组件,该组件可以自动将内容放置在安全区域内。在 HarmonyOS 上,这种方式存在底部导航栏没有自动避让的问题,因此建议使用上述的 AvoidArea API 方式实现安全区布局场景。

Flutter适配指导

安全区布局场景主要是为了确保应用内容不会被系统状态栏、导航栏等遮挡。Flutter 提供了 SafeArea 组件,该组件可以自动将内容放置在安全区域内。同时,也可以结合 AvoidAreaApi 获取的避让区域信息进行更精确的布局。

SafeArea( 
    child: Container( 
        // 这里可以放置应用的主要内容 
        child: Text('这是安全区布局内的内容'), 
    ), 
);

H5适配指导

该场景有两种实现方法:

  1. webview 在安全区布局,H5 页面不需要做额外处理

  2. webview 全屏布局,H5 获取状态栏、导航栏高度,设置 padding

(1)原生侧开启全屏布局

在 HarmonyOS 工程的 EntryAbility.ets 文件中的 onWindowStageCreate() 生命周期内,使用 setWindowLayoutFullScreen() 实现界面元素延伸到状态栏和导航区域;

示例代码如下:

windowObj: window.Window | undefined = undefined; 
onWindowStageCreate(windowStage: window.WindowStage): void { 
  windowStage.getMainWindow((err: BusinessError<void>, windowObj) => { 
    this.windowObj = windowObj; 
    // 设置全屏 
    windowObj.setWindowLayoutFullScreen(true); 
  }) 
}

(2)注入 JavaScript 对象

使用 registerJavaScriptProxy() 接口将原生侧方法注入到 H5 页面中,H5 页面可以调用原生方法获取状态栏和导航区域高度。

Web({ src: CommonConstants.H5_URL, controller: this.webController }) 
  .onControllerAttached(() => { 
    this.webController.registerJavaScriptProxy(this.webClassProxy, 'webClass', 
      ['getAvoidArea', 'pxToVp', 'isFoldAndTablet', 'getWindowStatusType'], 
      [], 
      `{"javascriptProxyPermission":{"urlPermissionList":[{"scheme":"http","host":"${CommonConstants.HOST}","port":"${CommonConstants.PORT}","path":""}]}}`); 
    this.webController.refresh(); 
  })

(3)H5 页面示例代码

页面通过调用原生方法获取状态栏和导航条高度,并设置在外层容器中,实现状态栏和导航条的避让。

<template> 
  <div class="contain" 
    :style="{ 
      paddingTop: `${paddingTop}px`, 
      paddingBottom: `${paddingBottom}px` 
    }"> 
    <div class="header"></div> 
    <div class="body"></div> 
    <div class="footer"></div> 
  </div> 
</template> 

<script setup lang="ts"> 
import { onMounted, onBeforeUnmount } from 'vue'; 

const paddingTop = 0; 
const paddingBottom = 0; 
onMounted(() => { 
  if ((window as any).webClass) { 
    // 获取状态栏安全区高度 
    const paddingTop = await (window as any).webClass.getAvoidArea(AvoidAreaType.TYPE_SYSTEM).area.topRect.height; 
    // 获取导航条安全区高度 
    const paddingBottom = await (window as any).webClass.getAvoidArea(AvoidAreaType.TYPE_NAVIGATION_INDICATOR).area.bottomRect.height; 
  } 
}); 
</script>

3.2 安全区布局+背景沉浸模式

图8 AvoidArea实现安全区布局+背景沉浸模式

AvoidArea实现安全区布局+背景沉浸模式

RN适配指导

在安全区域布局的基础上,延伸绘制内容(如背景色、背景图)到状态栏和导航条区域,实现沉浸式效果。上述示例代码中 FullScreenView 已经实现了安全区布局,我们可以在这个组件的基础上延伸绘制内容。在 FullScreenView 的外层套一层 View 容器,用于设置背景,这样就可以实现背景沉浸模式。示例代码如下:

return ( 
    <View style={styles.backgroundContainer}> 
      <FullScreenView/> 
    </View> 
  ); 
  const styles = StyleSheet.create({ 
    backgroundContainer: { 
        height: '100%', 
        width:'100%', 
        backgroundColor: 'gray', 
    } 
  })

Flutter适配指导

在背景沉浸模式下,需要使用 Stack 组件,背景全屏展示,然后使用 SafeArea 包裹安全区域。

Stack( 
  children: [ 
    Image.asset( 
      'assets/background.png', 
      fit: BoxFit.cover, 
      width: double.infinity, 
      height: double.infinity, 
    ), 
    SafeArea( 
      child: Container( 
        child: Text('这是安全区布局内的内容'), 
      ), 
    ), 
  ], 
)

H5适配指导

在安全区域布局的基础上,延伸绘制内容(如背景色、背景图)到状态栏和导航条区域,实现沉浸式效果。实现方式与安全区布局相似,将 webview 设置全屏沉浸式,H5 背景全屏、内容设置 padding。

  1. 原生侧开启全屏布局

在工程的 EntryAbility.ets 文件中的 onWindowStageCreate() 生命周期内,使用 setWindowLayoutFullScreen() 实现界面元素延伸到状态栏和导航区域。

示例代码如下:

windowObj: window.Window | undefined = undefined; 
onWindowStageCreate(windowStage: window.WindowStage): void { 
  windowStage.getMainWindow((err: BusinessError<void>, windowObj) => { 
    this.windowObj = windowObj; 
    // 设置全屏 
    windowObj.setWindowLayoutFullScreen(true); 
  }) 
}
  1. 注入 JavaScript 对象

使用 registerJavaScriptProxy() 接口将原生侧方法注入到 H5 页面中,H5 页面可以调用原生方法获取状态栏和导航区域高度。

Web({ src: CommonConstants.H5_URL, controller: this.webController }) 
      .onControllerAttached(() => { 
        this.webController.registerJavaScriptProxy(this.webClassProxy, 'webClass', 
          ['getAvoidArea', 'pxToVp', 'isFoldAndTablet', 'getWindowStatusType'], 
          [], 
          `{"javascriptProxyPermission":{"urlPermissionList":[{"scheme":"http","host":"${CommonConstants.HOST}","port":"${CommonConstants.PORT}","path":""}]}}`); 
        this.webController.refresh(); 
      })
  1. H5 页面示例代码

页面通过调用原生方法获取状态栏和导航条高度,并设置在外层容器中,实现状态栏和导航条的避让。

<template> 
  <div class="contain"> 
    <div class="header" 
      :style="{ 
        paddingTop: `${paddingTop}px` 
      }"> 
    </div> 
    <div class="body"></div> 
    <div class="footer" 
      :style="{ 
        paddingBottom: `${paddingBottom}px` 
      }"> 
    </div> 
  </div> 
</template> 

<script setup lang="ts"> 
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'; 

const paddingTop = 0; 
const paddingBottom = 0; 
onMounted(() => { 
  if ((window as any).webClass) { 
    // 获取状态栏安全区高度 
    const paddingTop = await (window as any).webClass.getAvoidArea(AvoidAreaType.TYPE_SYSTEM).area.topRect.height; 
    // 获取导航条安全区高度 
    const paddingBottom = await (window as any).webClass.getAvoidArea(AvoidAreaType.TYPE_NAVIGATION_INDICATOR).area.bottomRect.height; 
  } 
}); 
</script>

3.3 全屏布局+避让场景

图9 AvoidArea实现全屏布局+避让场景

AvoidArea实现全屏布局+避让场景

RN适配指导

  1. 原生侧开启全屏布局

在 HarmonyOS 工程的 EntryAbility.ets 文件中的 onWindowStageCreate() 生命周期内:

1、setWindowLayoutFullScreen() 实现界面元素延伸到状态栏和导航区域; 2、再通过 setWindowSystemBarEnable() 将主窗口状态栏和底部导航条隐藏,实现页面的全屏布局。

示例代码如下:

async onWindowStageCreate(windowStage: WindowStage) { 
    windowStage.getMainWindow((err: BusinessError, data) => { 
    let windowClass: Window | undefined = undefined; 
    windowClass = data; 
    // 开启全屏 
    let orientation = window.Orientation.AUTO_ROTATION_RESTRICTED; 
    windowClass.setPreferredOrientation(orientation); 
    // 隐藏状态栏、导航条 
    let names: Array<'status' | 'navigation'> = []; 
    windowClass.setWindowSystemBarEnable(names) 
  }) 
}
  1. 避让刘海屏/挖孔区

在 RN 工程中通过 AvoidArea API 的接口 getWindowAvoidArea() 获取挖孔区避让数据,计算并调整子元素的位置,规避挖孔区。示例代码如下:

let type = AvoidAreaType.TYPE_CUTOUT; 
//获取刘海屏/挖孔区位置信息 
let avoidArea = Avoid.getWindowAvoidArea(type); 
//...获取避让区信息后计算并调整子元素的位置,规避挖孔区

Flutter适配指导

在全屏布局中,需要隐藏系统状态栏和导航栏,并添加避让区域变化监听。根据避让区域的变化动态调整布局,确保应用在各种情况下都能正常显示。

  1. 隐藏状态栏和导航栏
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays:[]);
  1. 添加避让区域变化监听,根据避让区域的变化动态调整布局
/// samples/avoid_area_sample/lib/game_page.dart 
AvoidAreaApi.addAvoidAreaListener(event => { 
  var result = json.decode(event); 
  AvoidArea avoidArea = AvoidArea.fromJson(result["area"]); 
  switch(AvoidAreaType.values[result["type"]]){
    // 状态栏 
    case AvoidAreaType.system: 
      avoidAreaSystem = avoidArea; 
      break; 
    // 刘海区域 
    case AvoidAreaType.cutout: 
      avoidAreaCutout = avoidArea; 
      break; 
    // 键盘区域 
    case AvoidAreaType.keyboard: 
      avoidAreaKeyboard = avoidArea; 
      break; 
    // 手势区域 
    case AvoidAreaType.systemGesture: 
      avoidAreaSystemGesture = avoidArea; 
      break; 
    // 底部导航栏 
    case AvoidAreaType.navigationIndicator: 
      avoidAreaNavigationIndicator = avoidArea; 
      break; 
  } 
  update();
});

H5适配指导

在全屏布局中,需要隐藏系统状态栏和导航栏,并添加避让区域变化监听。根据避让区域的变化动态调整布局,确保应用在各种情况下都能正常显示。

  1. 原生侧开启全屏布局

在 HarmonyOS 工程的 EntryAbility.ets 文件中的 onWindowStageCreate() 生命周期内:

1、setWindowLayoutFullScreen() 接口实现界面元素延伸到状态栏和导航区域; 2、再通过 setWindowSystemBarEnable() 接口将主窗口状态栏和底部导航条隐藏,实现页面的全屏布局。

示例代码如下:

windowObj: window.Window | undefined = undefined; 
onWindowStageCreate(windowStage: window.WindowStage): void { 
  windowStage.getMainWindow((err: BusinessError<void>, windowObj) => { 
    this.windowObj = windowObj; 
    // 隐藏状态栏、导航条 
    windowObj.setSpecificSystemBarEnabled('status', false); 
    windowObj.setSpecificSystemBarEnabled('navigationIndicator', false); 
    // 设置全屏 
    windowObj.setWindowLayoutFullScreen(true); 
  }) 
}
  1. 注入 JavaScript 对象

使用 registerJavaScriptProxy() 接口将原生侧方法注入到 H5 页面中,H5 页面可以调用原生方法获取状态栏和导航区域高度。

Web({ src: CommonConstants.H5_URL, controller: this.webController }) 
  .onControllerAttached(() => { 
    this.webController.registerJavaScriptProxy(this.webClassProxy, 'webClass', 
      ['getAvoidArea', 'pxToVp', 'isFoldAndTablet', 'getWindowStatusType'], 
      [], 
      `{"javascriptProxyPermission":{"urlPermissionList":[{"scheme":"http","host":"${CommonConstants.HOST}","port":"${CommonConstants.PORT}","path":""}]}}`); 
    this.webController.refresh(); 
  })
  1. 在 H5 页面中计算刘海屏/挖孔区位置

页面通过调用原生方法获取刘海屏区域位置,通过计算转换成相对位置和避让的数值。

static async getAvoidAreaLocation(): Promise<AreaAndMargin> { 
  let location = AreaLocation.none; 
  if ((window as any).webClass) { 
    const avoidArea = await (window as any).webClass.getAvoidArea(); 
 
    //根据取避让区信息计算并调整子元素的位置,并返回AreaLocation和距离,下面是避让区域处于顶部的情况 
    if (this.cameraIsHere(avoidArea.topRect)) { 
      const left = AreaUtils.px2vp(avoidArea.topRect.left); 
      const right = AreaUtils.px2vp(avoidArea.topRect.left + avoidArea.topRect.width); 
      const center = window.screen.width / 2; 
      let marginValue = left; 
      if (left <= center && center <= right) { 
        location = AreaLocation.topCenter; 
      } else if (center < left) { 
        location = AreaLocation.rightTop; 
        marginValue = window.screen.width - AreaUtils.px2vp(avoidArea.topRect.left); 
      } else { 
        location = AreaLocation.leftTop; 
      } 
      return { areaLocation: location, marginValue: marginValue }; 
    } 
    //底部以及左右两边的情况也相应做处理 
    ......     
  } 
  return { areaLocation: location, marginValue: 0 }; 
}
  1. H5 页面示例代码

页面通过调用原生方法获取避让区域,并监听避让区域变化事件,根据回调设定不同区域的样式,实现避让。

<template> 
  <div class="contain"> 
    <div class="header" 
      :style="{ 
        marginTop: `${topMargin}px` 
      }"> 
    </div> 
    <div class="body"></div> 
    <div class="footer" 
      :style="{ 
        marginBottom: `${bottomMargin}px` 
      }"> 
    </div> 
  </div> 
</template> 

<script setup lang="ts"> 
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'; 
import { AreaUtils } from '../utils/AreaUtils';

更多关于HarmonyOS 鸿蒙Next区域避让开发指导的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

牛,学到了

更多关于HarmonyOS 鸿蒙Next区域避让开发指导的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


鸿蒙Next的区域避让开发主要使用ArkTS/TypeScript实现。核心API是AreaAvoidance模块,提供registerAvoidanceArea方法注册避让区域,参数包括区域范围(矩形坐标)和避让优先级。系统会自动调整布局避免遮挡注册区域。

关键开发步骤:

  1. 导入模块:import areaAvoidance from '@ohos.areaAvoidance'
  2. 定义避让区域:let area = { left:0, top:0, width:100, height:200 }
  3. 注册区域:areaAvoidance.registerAvoidanceArea(area, areaAvoidance.Priority.HIGH)

系统会根据优先级动态调整其他组件位置。可通过unregisterAvoidanceArea取消注册。

在HarmonyOS Next中实现区域避让功能,主要涉及三种场景:安全区布局、安全区布局+背景沉浸模式、全屏布局+避让场景。以下是关键实现要点:

  1. 安全区布局:
  • 使用AvoidArea API获取系统状态栏、导航栏高度
  • 通过设置padding确保内容不被遮挡
  • RN示例:通过getWindowAvoidArea获取避让区域,设置外层容器padding
  1. 背景沉浸模式:
  • 在安全区布局基础上延伸背景到系统栏区域
  • Flutter实现:使用Stack组件全屏背景+SafeArea包裹内容区
  1. 全屏布局避让:
  • 隐藏系统栏后动态计算避让区域
  • H5方案:通过原生接口获取避让区域数据,动态调整元素位置
  1. 折叠屏适配:
  • 使用专用组件FolderStack和FoldSplitContainer
  • 根据折叠状态(展开/半折叠/折叠)自动调整布局
  • 支持上下分屏、三分栏等特殊布局

关键API:

  • getWindowAvoidArea:获取指定类型避让区域
  • addAvoidAreaListener:监听避让区域变化
  • removeAvoidAreaListener:移除监听

建议参考官方示例代码实现具体功能,特别注意不同设备形态(直板机/折叠屏)的差异处理。

回到顶部