资讯动态

告别手动注册窗口监听:用 @Env 获取 WINDOW_AVOID_AREA【鸿蒙心迹】

发布时间:2026/10/27 1:49:18 来源:尧图企业网站定制
你是不是也在想——“鸿蒙这么火我能不能学会”答案是当然可以这个专栏专为零基础小白设计不需要编程基础也不需要懂原理、背术语。我们会用最通俗易懂的语言、最贴近生活的案例手把手带你从安装开发工具开始一步步学会开发自己的鸿蒙应用。不管你是学生、上班族、打算转行还是单纯对技术感兴趣只要你愿意花一点时间就能在这里搞懂鸿蒙开发并做出属于自己的App关注本专栏《零基础学鸿蒙开发》一起变强每一节内容我都会持续更新配图代码解释全都有欢迎点个关注不走丢我是小白酷爱学习我们一起上路 全文目录先说场景一、先把涉及的接口版本对清楚二、手动监听写法它做了哪些事三、Env 写法能省掉什么四、两种写法拆开比较五、几个值得单独关注的点六、排查优先顺序开发经验总结先说场景做全面屏适配时有一个绕不过去的问题页面内容要怎么知道顶部状态栏和底部导航条占了多少空间最直接的答案是获取窗口规避区Avoid Area。规避区描述的是屏幕上被系统 UI 占据的区域包括状态栏、导航条、刘海屏开孔等应用的内容布局应当主动回避这些区域或者在需要背景延伸时至少要知道这些区域的尺寸再做对齐。问题在于获取这个信息的方式在 HarmonyOS API 的演进过程中发生了变化。API 12 之前开发者通常在 Ability 里手动注册窗口事件监听通过AppStorage把尺寸数据跨层透传到页面组件。这套机制能用但代码量可观涉及到监听注册、状态存储、跨层绑定三个环节维护成本不低。API 12 起SystemProperties新增了WINDOW_AVOID_AREA属性配合 API 11 引入的Env装饰器可以在组件内直接声明一个与规避区自动同步的状态变量框架负责其余的事情。这篇文章只比较这两种写法看清楚各自做了什么、省掉了什么再说清楚用哪个更合适。一、先把涉及的接口版本对清楚在动手之前有几个版本约束需要确认。手动监听方式涉及的核心接口window.getAvoidArea(type: AvoidAreaType)从 API 9 起支持属于kit.ArkUI中的window模块。window.on(avoidAreaChange, callback)同为 API 9 起支持。window.AvoidAreaType枚举类型TYPE_SYSTEM对应状态栏TYPE_NAVIGATION_INDICATOR对应底部导航条。Env 方式涉及的接口Env装饰器API 11 起引入定义在kit.ArkUI的Environment和SystemProperties中。SystemProperties.WINDOW_AVOID_AREAAPI 12 起新增类型为AvoidAreaOptions。AvoidAreaOptions包含topRect、bottomRect、leftRect、rightRect、avoidAreaType五个字段每个Rect字段为RectOptions类型含left、top、width、height。以上信息来源华为开发者联盟 SystemProperties 文档、Env 装饰器文档、Display Cutout 适配最佳实践。如果工程的compileSdkVersion低于 12SystemProperties.WINDOW_AVOID_AREA不可用只能走手动监听。二、手动监听写法它做了哪些事手动监听的完整链路横跨两个文件EntryAbility.ets和页面组件。第一步在 Ability 里拿到窗口实例并注册监听。// EntryAbility.etsimport{AbilityConstant,UIAbility,Want}fromkit.AbilityKit;import{window}fromkit.ArkUI;exportdefaultclassEntryAbilityextendsUIAbility{onWindowStageCreate(windowStage:window.WindowStage):void{windowStage.loadContent(pages/Index,(err){if(err.code){return;}});// 拿到主窗口实例letwindowClasswindowStage.getMainWindowSync();// 页面加载时先取一次当前规避区AppStorage.setOrCreate(topAvoidArea,windowClass.getAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect);AppStorage.setOrCreate(bottomAvoidArea,windowClass.getAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR).bottomRect);// 注册监听后续规避区变化时更新windowClass.on(avoidAreaChange,(data){if(data.typewindow.AvoidAreaType.TYPE_SYSTEM){AppStorage.setOrCreate(topAvoidArea,data.area.topRect);}elseif(data.typewindow.AvoidAreaType.TYPE_NAVIGATION_INDICATOR){AppStorage.setOrCreate(bottomAvoidArea,data.area.bottomRect);}});}}第二步在页面组件里通过 AppStorage 读取数据。// Index.etsimport{window}fromkit.ArkUI;EntryComponentstruct Index{StorageProp(topAvoidArea)topAvoidArea:window.Rect{left:0,top:0,width:0,height:0};StorageProp(bottomAvoidArea)bottomAvoidArea:window.Rect{left:0,top:0,width:0,height:0};build(){Column(){Row().width(100%).height(this.topAvoidArea.height).backgroundColor(Color.Blue).opacity(0.3)Text(主内容区).layoutWeight(1).textAlign(TextAlign.Center).fontSize(24)Row().width(100%).height(this.bottomAvoidArea.height).backgroundColor(Color.Blue).opacity(0.3)}.width(100%).height(100%).expandSafeArea([SafeAreaType.SYSTEM],[SafeAreaEdge.TOP,SafeAreaEdge.BOTTOM])}}三、Env 写法能省掉什么用Env(SystemProperties.WINDOW_AVOID_AREA)之后上面那两个文件可以压缩成一个。Ability 里不需要做任何与规避区有关的操作。页面组件里直接声明变量// Index.ets使用 Env 方式import{Environment,SystemProperties}fromkit.ArkUI;EntryComponentstruct Index{// 声明后框架自动从 Environment 同步规避区变化时组件自动重新渲染Env(SystemProperties.WINDOW_AVOID_AREA)avoidArea:AvoidAreaOptions{topRect:{left:0,top:0,width:0,height:0},bottomRect:{left:0,top:0,width:0,height:0},leftRect:{left:0,top:0,width:0,height:0},rightRect:{left:0,top:0,width:0,height:0},avoidAreaType:AvoidAreaType.TYPE_SYSTEM};build(){Column(){Row().width(100%).height(this.avoidArea.topRect.height).backgroundColor(Color.Blue).opacity(0.3)Text(主内容区).layoutWeight(1).textAlign(TextAlign.Center).fontSize(24)Row().width(100%).height(this.avoidArea.bottomRect.height).backgroundColor(Color.Blue).opacity(0.3)}.width(100%).height(100%).expandSafeArea([SafeAreaType.SYSTEM],[SafeAreaEdge.TOP,SafeAreaEdge.BOTTOM])}}真正需要关注的地方只有两个Env(SystemProperties.WINDOW_AVOID_AREA)的参数是SystemProperties上的枚举键不是字符串字面量。变量的初始值必须写完整的AvoidAreaOptions结构四个方向的RectOptions都要给默认值否则在规避区数据还没从框架传入之前组件读到的会是undefined可能导致高度渲染为 NaN。avoidAreaType字段的初始值写AvoidAreaType.TYPE_SYSTEM只是一个占位实际值会由框架覆盖不影响运行结果但写得准确一些可以减少阅读代码时的歧义。四、两种写法拆开比较从代码结构角度看差异体现在四个维度。初始化时机手动监听需要在onWindowStageCreate时主动调用一次getAvoidArea获取初始值再调用on(avoidAreaChange, ...)注册监听。如果只注册了监听而没有主动取初始值页面首次渲染时AppStorage里是空的可能出现布局闪烁。Env方式不需要操心这件事框架在组件初始化时会把当前值作为初始状态注入随后变化时自动触发更新。状态管理位置手动监听的数据通过AppStorage存储整个应用共享一份key 是字符串拼错了编译不报错。Env的数据存在Environment里key 来自SystemProperties的属性名有类型约束。跨文件耦合手动监听方式Ability 写监听页面写StorageProp两边都要改才能维护。Env方式整个逻辑收在组件内Ability 里不留痕迹。灵活性这是手动监听方式唯一占优的场合。如果需要同时处理多个不同类型的规避区比如分别响应TYPE_SYSTEM、TYPE_NAVIGATION_INDICATOR、TYPE_CUTOUT三种类型手动监听里data.type的判断天然支持Env的WINDOW_AVOID_AREA目前封装的是通用规避区数据没有提供按类型分拆的多变量声明方式。如果业务需要精细区分不同类型手动监听仍然是更直接的选择。对比维度手动监听Env支持 API912WINDOW_AVOID_AREA初始值获取需手动调用框架自动注入状态存储AppStorage手动维护Environment框架管理文件范围Ability 页面仅页面组件多类型规避区分拆支持不直接支持代码量较多较少五、几个值得单独关注的点WINDOW_AVOID_AREA与KEYBOARD_AVOID_AREA不是同一个属性SystemProperties在 API 14 起还增加了KEYBOARD_AVOID_AREA软键盘规避区和NAVIGATION_INDICATOR_AVOID_AREA导航条规避区两个独立属性。WINDOW_AVOID_AREA对应的是系统通用规避区两者服务于不同场景不要混用。如果项目需要单独响应软键盘弹出API 14 起可以考虑KEYBOARD_AVOID_AREA。Env只能在Component装饰的自定义组件中使用官方文档明确限定了这一点。如果试图在非组件的普通类或工具函数里用Env运行时不会生效。expandSafeArea是前提条件无论哪种方式如果没有调用.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])让背景延伸到系统区域规避区的高度值虽然能读到但主动对齐的意义就变小了——系统本身已经把内容推开了你再手动留出等高的间距反而是双重偏移。这两件事要配套理解。Env是单向同步Environment → Env是单向的组件内无法反向修改规避区的值也没有理由这样做。这与StorageProp的行为一致但理解清楚方向有助于排查数据不更新的问题如果值没变先检查框架是否收到了系统环境变更通知而不是检查组件侧的写法。六、排查优先顺序如果用了Env方式却发现规避区高度始终是初始值 0按以下顺序排查确认 API Level工程compileSdkVersion是否达到 12低于 12 时WINDOW_AVOID_AREA不存在变量会一直是初始值。确认 import 路径Environment和SystemProperties需要从kit.ArkUI导入不是单独的ohos.environment。确认初始值结构完整AvoidAreaOptions的四个方向Rect是否都给了默认值缺少字段可能导致渲染时读到undefined。确认Env写在Component装饰的组件内不能写在普通 class 或函数里。确认expandSafeArea已配置没有扩展安全区域的情况下系统默认已经为内容留出了偏移此时观察到的布局效果和规避区高度不直接对应容易产生混淆。如果手动监听方式发现初次渲染时数据是空的八成是onWindowStageCreate里只注册了监听、没有主动调用getAvoidArea获取初始值。监听只能覆盖「变化」事件不会在注册时触发一次回调推送当前状态。开发经验总结Env(SystemProperties.WINDOW_AVOID_AREA)从 API 12 起可用对应AvoidAreaOptions类型组件内声明即生效框架负责初始化和后续同步。手动监听方式window.on(avoidAreaChange, ...)从 API 9 起支持适合需要分类处理多种规避区类型的场景代价是要在 Ability 和页面两侧各维护代码。两种方式都需要配合.expandSafeArea()使用才能使背景延伸和规避区对齐产生实际意义。使用Env方式时初始值的结构完整性比用什么占位值更重要防的是首帧渲染时字段为undefined。KEYBOARD_AVOID_AREAAPI 14和WINDOW_AVOID_AREAAPI 12是独立属性软键盘场景不要混用。如果你的项目已经在用手动监听方式可以检查一下除了状态栏和导航条是否还有对TYPE_CUTOUT的处理逻辑如果只处理了两种常规类型迁移到Env方式基本没有障碍值得考虑一下。❤️ 如果本文帮到了你…请点个赞让我知道你还在坚持阅读技术长文请收藏本文因为你以后一定还会用上如果你在学习过程中遇到bug请留言我帮你踩坑

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑