一、概述

在移动应用开发中,不同设备的屏幕形态各异,如刘海屏、全面屏、折叠屏等,同时系统状态栏、导航栏、软键盘等元素也会占据屏幕空间。为了确保应用内容在各种设备和场景下都能正常显示,不被遮挡,RN(RN 是React Native 的简称,是一个使用JavaScript 和React 开发跨平台移动应用的框架)提供了一系列区域避让的机制和接口。本文将详细介绍 RN 区域避让的实现原理、适配指导以及具体的场景案例。

使用场景

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

说明

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

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

图1-2 PAD界面元素示意图

图1-3 PC界面元素示意图

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

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

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

1、安全区布局场景

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

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

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

3、全局布局+避让场景

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

 

二、区域避让实现原理

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

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

    AvoidAreaType避让类型枚举

    名称

    说明

    TYPE_SYSTEM

    0

    表示系统默认区域。通常表示状态栏区域,悬浮窗状态下的应用主窗中表示三点控制栏区域。

    TYPE_CUTOUT

    1

    表示刘海屏区域。

    TYPE_SYSTEM_GESTURE

    2

    表示手势区域。当前,各设备均无此类型避让区域。

    TYPE_KEYBOARD

    3

    表示软键盘区域。

    TYPE_NAVIGATION_INDICATO

    4

    表示底部导航条区域。

    接口返回信息结构如下:

{ 
    "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();

三、适配指导

下面从上述的三个主要场景来进行适配指导

1、安全区布局场景

安全区布局场景主要是为了确保应用内容不会被系统状态栏、导航栏等遮挡。通过上述的 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;

图1-7 AvoidArea实现安全布局

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

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

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

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

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

3、全局布局+避让场景

3.1 原生侧开启全局布局

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

1、setWindowLayoutFullScreen()实现界面元素延伸到状态栏和导航区域;

2、再通过setWindowSystemBarEnable()将主窗口状态栏和底部导航条隐藏,实现页面的全局布局。

示例代码如下:

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

3.2 避让刘海屏/挖孔区

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

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

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

四、场景案例

区域避让场景案例

1、通过调用 getWindowAvoidArea() 接口获取初始化时的避让区域信息,并通过 addAvoidAreaListener() 接口注册监听器,以便在状态变化时(如横竖屏转换)更新区域避让信息。

2、利用获取到的区域避让信息数据,计算所需避让的区域及其边距,并相应地更新 UI。

    const [orientation, setOrientation] = useState<string>(); 
    const [cutoutMargin, setCutoutMargin] = useState(0); 
    const pixelRatio = PixelRatio.get() ? PixelRatio.get() : 1; 
    useEffect(() => { 
        const getAvoidArea = () => { 
            // 刘海屏,挖孔区 
            let type = AvoidAreaType.TYPE_CUTOUT; 
            let avoidArea = Avoid.getWindowAvoidArea(type); 
            // 根据获取到的数据进行逻辑处理 
            getCutoutArea(avoidArea); 
        } 
        getAvoidArea(); 
        const area = Avoid.addAvoidAreaListener((data) => { 
            // 动态避让挖孔区 
            if (data.type === AvoidAreaType.TYPE_CUTOUT) { 
                // 根据获取到的数据进行逻辑处理 
                getCutoutArea(data.area); 
            } 
        }) 
        return () => { 
            Avoid.removeAvoidAreaListener(); 
        } 
    }, []) 
    const getCutoutArea = (avoidArea: AvoidArea) => { 
        const windowWidth = Dimensions.get('window').width; 
        const windowHeight = Dimensions.get('window').height; 
        if (avoidArea.topRect.height) { 
            setOrientation('topRect'); 
            let margin = windowWidth - avoidArea.topRect.left / pixelRatio; 
            setCutoutMargin(margin);//更新UI 
        } else if (avoidArea.leftRect.height) { 
           ... 
        }... 
    }

图1-10 sample示例图

 

RN区域避让的Sample示例代码地址:avoid_area_sample,开发者可以通过该地址查看完整的区域避让示例代码,并根据自己的需求进行修改和扩展。

 

五、折叠屏高阶组件使用指导

FolderStack 和 FoldSplitContainer 这两个组件是折叠屏布局的专用组件。下面对这两个组件进行使用指导。

5.1 FolderStack组件

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

<FolderStack 
     // 定义悬停态会被移到上半屏的子组件的id,组件id在此数组中的子组件悬停触发时自动避让折叠屏折痕区后移到上半屏,其它组件堆叠在下半屏区域。 
     upperItems={["upperitemsId"]} 
     // 是否使用默认动效。默认值:true,设置true表示使用默认动效,设置false表示不使用默认动效 
     enableAnimation={true} 
     // autoHalfFold 是否开启自动旋转。默认值:true,设置true表示开启自动旋转,设置false表示关闭自动旋转。 
     autoHalfFold={true} 
     // alignContent子组件在容器内的对齐方式。 
     alignContent='End' 
     // onFolderStateChange当折叠状态改变的时候回调,仅在横屏状态下生效。 
     onFolderStateChange={(foldStatus) => {}} 
     // onHoverStatusChange当悬停状态改变的时候回调。 
     onHoverStatusChange={(HoverStatus) => {}}> 
     {/* 子组件id与父组件upperItems一致则移动至上半屏显示区域 */} 
     <View style={[styles.videoContainer, { backgroundColor: 'rgb(0, 74, 175)' }]} id="upperitemsId"> 
     </View> 
     // ... 
     {/* 子组件id与父组件upperItems不一致则移动至下半屏显示区域 */} 
</FolderStack>

图1-11 FolderStack

5.2 FoldSplitContainer组件

FoldSplitContainer分栏布局,实现折叠屏二分栏、三分栏在展开态、悬停态以及折叠态的区域控制。

const TestSample = () => { 
    const primaryRender = () => ( 
    // ...FoldSplitContainer 主要区域 
    ); 
    const secondRender = () => ( 
    // ...FoldSplitContainer 次要区域 
    ); 
    const extraRender = () => ( 
    // ...FoldSplitContainer 扩展区域 
    ); 
    const expandedLayoutOptions: ExpandedRegionLayoutOptions = { 
    // ...折叠屏展开态配置 
    }; 
    const hoverModeRegionLayoutOptions: HoverModeRegionLayoutOptions = { 
    // ...折叠屏悬停态配置 
    }; 
   const foldedRegionLayoutOptions: FoldedRegionLayoutOptions = { 
    // ...折叠屏折叠态配置 
    }; 
    return ( 
       <View style={styles.container}> 
           // 折叠屏高阶组件 
           <FoldSplitContainer 
                //主要区域回调 
                primary={primaryRender()} 
                //次要区域回调 
                secondary={secondRender()} 
               //拓展区域回调 
                extra={extraRender()} 
               //展开态布局信息 
                expandedLayoutOptions={expandedLayoutOptions} 
               //悬停态布局信息 
                hoverModeLayoutOptions={hoverModeRegionLayoutOptions} 
               //悬停态布局信息 
                foldedLayoutOptions={foldedRegionLayoutOptions} 
               //当悬停状态改变的时候回调。 
                onHoverStatusChange={(HoverStatus) => {} 
            /> 
       </View> 
        ); 
};

图1-12 FoldSplitContainer

组件库地址为:@hadss/react_native_adaptive_layout,开发者可以通过该地址查看完整的组件接入指导,并根据自己的需求进行开发。

 

 

Logo

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

更多推荐