HarmonyOS 屏幕适配完全指南:从尺寸获取到旋转监听的全栈方案
文章目录一、前言二、屏幕适配需要解决的问题三、完整适配方案3.1 初始化阶段获取基础屏幕信息3.2 监听器注册3.3 布局更新逻辑3.4 Demo 中的完整 UI 演示3.5 页面销毁统一注销监听器四、屏幕适配的关键原则原则 1使用相对单位而非绝对像素原则 2先判断折叠屏再做适配原则 3挖孔区域高度用于安全内边距原则 4监听器使用完毕立即注销五、小结一、前言近期发现一款很有意思的HarmonyOS 三方库, 地址 pura/harmony-utils(V1.4.0) , 作者是桃花镇童长老, 我这里也是直接通过该作者公布的源码进行案例编写进行,写了到目前写了一部分demo ,感觉确实很有帮助,这里呢也是开始写一个系列的演示demo 供大家参考。如有帮助可以在OpenHarmony中进行下载安装进行使用哦案例demo导航展示↓↓↓↓↓↓接下来言归正传 ↓↓↓↓HarmonyOS 设备形态丰富多样普通手机、平板、折叠屏、2in1……每种设备都有不同的屏幕尺寸、分辨率、挖孔形状和折叠状态。如何让一个应用在所有设备上都能正确显示是 HarmonyOS 开发中的重要课题。DisplayUtil提供了一套完整的屏幕信息获取与监听 API。本文将这些能力整合起来给出一套完整的多屏适配方案。二、屏幕适配需要解决的问题问题对应 API说明获取屏幕宽高getWidth()/getHeight()布局计算基础获取屏幕方向getOrientation()横竖屏布局切换处理刘海/挖孔getCutoutRect()/getCutoutHeight()避让不可用区域折叠屏适配isFoldable()/getFoldStatus()/getFoldDisplayMode()折叠/展开/半折叠布局旋转事件响应addOnScreenOrientationChange()实时布局更新折叠事件响应onFoldStatusChange()折叠状态变化响应截屏保护isCaptured()/onCaptureStatusChange()隐私内容保护三、完整适配方案3.1 初始化阶段获取基础屏幕信息// 在 aboutToAppear 中进行屏幕信息初始化asyncaboutToAppear(){// 1. 获取屏幕尺寸this.screenWidthDisplayUtil.getWidth();this.screenHeightDisplayUtil.getHeight();// 2. 获取屏幕方向this.currentOrientationDisplayUtil.getOrientation();this.updateLayoutForOrientation(this.currentOrientation);// 3. 获取挖孔区域高度用于计算安全区域constcutoutHeightawaitDisplayUtil.getCutoutHeight();this.safeTopPaddingcutoutHeight;// 4. 检查折叠屏if(DisplayUtil.isFoldable()){this.isFoldDevicetrue;constfoldStatusDisplayUtil.getFoldStatus();this.updateLayoutForFoldStatus(foldStatus);}// 5. 注册监听器this.registerListeners();}3.2 监听器注册// 保存 callback 引用用于注销privateorientationCallback?:(orientation:display.Orientation)void;privatefoldStatusCallback?:(status:display.FoldStatus)void;registerListeners(){// 注册屏幕旋转监听this.orientationCallback(orientation:display.Orientation){this.currentOrientationorientation;this.updateLayoutForOrientation(orientation);};DisplayUtil.addOnScreenOrientationChange(this.orientationCallback);// 注册折叠状态监听仅折叠屏设备if(DisplayUtil.isFoldable()){this.foldStatusCallback(status:display.FoldStatus){this.updateLayoutForFoldStatus(status);};DisplayUtil.onFoldStatusChange(this.foldStatusCallback);}}3.3 布局更新逻辑// 根据屏幕方向更新布局updateLayoutForOrientation(orientation:display.Orientation){constisLandscapeorientationdisplay.Orientation.LANDSCAPE||orientationdisplay.Orientation.LANDSCAPE_INVERTED;this.columnCountisLandscape?3:2;this.itemAspectRatioisLandscape?0.6:1.0;}// 根据折叠状态更新布局updateLayoutForFoldStatus(status:display.FoldStatus){switch(status){casedisplay.FoldStatus.FOLD_STATUS_EXPANDED:// 完全展开使用大屏双列布局this.columnCount4;this.useLargeLayouttrue;break;casedisplay.FoldStatus.FOLD_STATUS_FOLDED:// 完全折叠使用小屏单列布局this.columnCount2;this.useLargeLayoutfalse;break;casedisplay.FoldStatus.FOLD_STATUS_HALF_FOLDED:// 悬停态帐篷模式上下分区布局this.useTentModetrue;break;}}3.4 Demo 中的完整 UI 演示DisplayUtilDemoPage.ets中展示了多种屏幕信息的实时展示屏幕尺寸的可视化展示Row(){Column(){Text(this.screenWidth.split( )[0]).fontSize(28).fontWeight(FontWeight.Bold).fontColor(#4080FF)Text(宽度 px).fontSize(11).fontColor(#888)}.layoutWeight(1).alignItems(HorizontalAlign.Center)Text(×).fontSize(20).fontColor(#CCC)Column(){Text(this.screenHeight.split( )[0]).fontSize(28).fontWeight(FontWeight.Bold).fontColor(#00C853)Text(高度 px).fontSize(11).fontColor(#888)}.layoutWeight(1).alignItems(HorizontalAlign.Center)Text(≈).fontSize(20).fontColor(#CCC)Column(){Text(this.screenSize.split( )[0]).fontSize(22).fontWeight(FontWeight.Bold).fontColor(#D63384)Text(对角线英寸).fontSize(11).fontColor(#888)}.layoutWeight(1).alignItems(HorizontalAlign.Center)}.width(100%).margin({bottom:6})折叠屏状态的可视化展示Row(){Column(){Text(this.isFoldableResult).fontSize(14).fontWeight(FontWeight.Bold).fontColor(this.isFoldableResult.includes(可折叠)?#00C853:#888)Text(isFoldable()).fontSize(10).fontColor(#888)}.layoutWeight(1).alignItems(HorizontalAlign.Center)Column(){Text(this.foldStatusLabel.includes(未知)?N/A:this.foldStatusLabel.split( )[0]).fontSize(14).fontWeight(FontWeight.Bold).fontColor(#4080FF)Text(getFoldStatus()).fontSize(10).fontColor(#888)}.layoutWeight(1).alignItems(HorizontalAlign.Center)Column(){Text(this.foldDisplayModeLabel.includes(未知)?N/A:this.foldDisplayModeLabel.split( )[0]).fontSize(14).fontWeight(FontWeight.Bold).fontColor(#D63384)Text(getFoldDisplayMode()).fontSize(10).fontColor(#888)}.layoutWeight(1).alignItems(HorizontalAlign.Center)}.width(100%).margin({bottom:10})3.5 页面销毁统一注销监听器// 页面销毁时必须注销所有监听器防止内存泄漏aboutToDisappear():void{if(this.orientationCallback!undefined){DisplayUtil.removeOnScreenOrientationChange(this.orientationCallback);this.orientationCallbackundefined;}if(this.foldStatusCallback!undefined){DisplayUtil.offFoldStatusChange(this.foldStatusCallback);this.foldStatusCallbackundefined;}if(this.foldAngleCallback!undefined){DisplayUtil.offFoldAngleChange(this.foldAngleCallback);this.foldAngleCallbackundefined;}if(this.captureStatusCallback!undefined){DisplayUtil.offCaptureStatusChange(this.captureStatusCallback);this.captureStatusCallbackundefined;}}四、屏幕适配的关键原则原则 1使用相对单位而非绝对像素ArkUI 中推荐使用vp虚拟像素而非px物理像素进行布局。DisplayUtil.getWidth()返回物理像素在布局中使用时需要换算constdDisplayUtil.getDefaultDisplaySync();constwidthInVpd.width/d.densityPixels;// 转换为 vp原则 2先判断折叠屏再做适配if(DisplayUtil.isFoldable()){// 折叠屏特有逻辑}else{// 普通屏幕逻辑}原则 3挖孔区域高度用于安全内边距constcutoutHeightawaitDisplayUtil.getCutoutHeight();// cutoutHeight 0 说明有刘海/挖孔需要避让consttopPaddingMath.max(cutoutHeight,defaultStatusBarHeight);原则 4监听器使用完毕立即注销所有通过DisplayUtil注册的监听器必须在aboutToDisappear中注销这是防止内存泄漏的关键。五、小结DisplayUtil提供了一套完整的屏幕感知 APIDisplayUtil ├── 屏幕信息获取 │ ├── getDefaultDisplaySync() / getPrimaryDisplaySync() │ ├── getAllDisplays() │ ├── getWidth() / getHeight() │ ├── getOrientation() │ └── getDisplayState() │ ├── 特殊屏幕处理 │ ├── getCutoutRect() / getCutoutHeight() ← 刘海/挖孔屏 │ ├── isFoldable() ← 折叠屏判断 │ ├── getFoldStatus() ← 折叠状态 │ └── getFoldDisplayMode() ← 折叠显示模式 │ └── 事件监听 ├── addOnScreenOrientationChange() ← 旋转监听 ├── onFoldStatusChange() ← 折叠状态监听 ├── onFoldAngleChange() ← 折叠角度监听 ├── onCaptureStatusChange() ← 截屏监听 └── isCaptured() ← 截屏状态查询掌握这套 API配合合理的架构设计可以让你的应用在任何 HarmonyOS 设备上都能完美呈现。