【HarmonyOS】View点击穿透,层叠View点击事件控制

【HarmonyOS】View点击穿透,层叠View点击事件控制
一、什么是点击穿透很多开发者第一次听到点击穿透都会理解成上面的 View 不处理点击让下面的 View 收到点击。例如┌──────────────────────┐ │ Mask(View) │ ├──────────────────────┤ │ Button │ └──────────────────────┘点击 Mask希望 Button 被点击。这就是最典型的Click Through点击穿透很多 Loading、蒙层、引导页都会用到这种效果。常见应用场景例如Loading 蒙层Loading ──────────── Button有两种需求方案一Loading 出现后禁止点击 Button。方案二Loading 只是动画允许点击 Button。这就是点击穿透。水印例如WaterMark ──────────── List希望可以滑动 List而不是水印拦截事件。悬浮提示例如Toast ──────────── 地图Toast 不应该影响地图拖动。引导层例如遮罩 ──────────── Button希望Button 可以继续点击。二、HarmonyOS 点击事件流程用户点击屏幕后并不是直接调用onClick()。实际上中间会经历多个阶段。用户点击 ↓ PointerEvent ↓ HitTest ↓ TouchEvent ↓ GestureRecognizer ↓ ClickEvent ↓ onClick()整个过程如下阶段作用PointerEvent采集触摸坐标HitTest寻找命中的 ViewTouchEvent分发触摸事件GestureRecognizer识别点击、长按等手势ClickEvent生成点击事件onClick开发者收到事件因此点击穿透的核心就在于 HitTest。三、什么是 HitTestHitTest命中测试就是根据用户点击的位置找到真正应该接收事件的组件。例如Stack ├── Image ├── Text └── Button用户点击Button区域HitTest第一步检查 Button如果命中停止继续查找。所以Button 收到点击。如果 Button 不存在Stack ├── Image └── Text点击Image区域Image 收到事件。所以HitTest 永远都是从最上层开始查找。四、Stack 为什么最容易出现点击穿透问题因为 Stack 本身就是层叠布局例如Stack() { Image($r(app.media.bg)) Text(HarmonyOS) .fontSize(28) Button(登录) } .width(100%) .height(300)显示效果Button Text ImageButton 在最上层。点击Button。Image 根本不会收到事件。五、事件命中的顺序假设Stack() { Image() Column() Button() }HitTest不是代码顺序。而是显示顺序。实际Button ↓ Column ↓ Image谁最上面谁先检测。六、Button 为什么挡住下面的 Image例如Button(点击)覆盖 Image。HitTest点击 ↓ Button ↓ 命中成功 ↓ 停止查找Image根本不知道有人点击。七、为什么 enabled(false) 不能点击穿透很多人会写Button(登录) .enabled(false)希望Button 不可点击。下面Image 可以收到事件。实际完全不会。为什么因为Button仍然参与HitTest。只是不会触发onClick()它仍然挡住下面。流程Hit ↓ Button ↓ Disabled ↓ 消费事件所以下面不会收到点击。这是 HarmonyOS 最容易踩坑的地方之一。八、opacity(0) 为什么还能点击例如Text(Hello) .opacity(0) .onClick(() { console.info(点击) })页面已经完全透明。很多人认为应该不能点。实际上可以。为什么因为opacity 只影响绘制 不影响 HitTest组件虽然透明。但是仍然存在 RenderNode。所以仍然参与命中测试。正确理解透明RenderNode 存在所以还能点击。九、visibility 与点击的关系例如.visibility(Visibility.Visible)正常显示可点击。如果.visibility(Visibility.Hidden)特点不显示保留布局通常仍参与布局计算如果.visibility(Visibility.None)特点不参与布局不参与 HitTest因此真正想让 View 完全失效应该考虑Visibility.None具体行为请结合当前 API 版本验证。十、如何实现真正的点击穿透假设Mask ↓ Button目标点击Mask。Button 收到事件。可以采用以下几种方案方案一避免使用覆盖层如果只是展示提示、动画、水印等优先调整布局让装饰元素不要覆盖可交互区域这是最简单、性能也最好的方案。方案二动态移除遮罩例如if (this.showMask) { // 显示遮罩 }需要点击穿透时直接隐藏或移除遮罩。这样HitTest 就不会命中遮罩。方案三合理控制 Overlay例如LoadingOverlay ↓ PageLoading 结束立即关闭 Overlay。不要长期保留透明蒙层。方案四拆分交互区域例如Stack ├── Image ├── Decoration └── ButtonDecoration不要覆盖 Button。这样无需点击穿透。十一、zIndex 对点击有没有影响有。例如Image() .zIndex(1) Button() .zIndex(10)HitTest优先Button ↓ Image因此zIndex 不仅影响显示顺序也会影响命中顺序。十二、企业开发最佳实践建议遵循以下原则1、不要依赖透明实现点击穿透错误.opacity(0)正确移除组件或者不要参与布局。2、不要把整个页面放进一个 Stack层级越复杂HitTest越复杂。建议分层。3、Loading 使用统一组件例如LoadingManager统一开启关闭避免透明蒙层遗留。4、合理使用 zIndex不要99999满天飞。统一规划0~99 页面 100 Overlay 200 Dialog 300 Popup维护更加容易。