HarmonyOS 鸿蒙Next 悬浮窗拖动与悬浮窗内的Web组件事件冲突如何解决 鸿蒙场景化案例
【问题现象】
加载HarmonyOS文档的悬浮窗可以拖动,但是不能点击Web组件里的按钮。
下文将以Row组件和Web组件嵌套举例:给Row组件添加自定义拖动手势,Web组件不添加自定义手势,但是有触摸事件。
**预期效果:**既可以拖动Row组件,又能点击Web组件中的按钮。
**实际效果:**可以拖动窗口,但是点击Web组件中的按钮没反应。
问题代码如下:
build() {
Row() {
// 定义Web
Web({ src: 'https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-basic-components-web-V5',
controller: this.controller
})
.onPageEnd(()=>{
this.controller.setScrollable(false)
})
.width(CommonConstants.DEFAULT_WINDOW_SIZE1).height(CommonConstants.DEFAULT_WINDOW_SIZE1)
}
.backgroundColor(Color.Transparent)
// 自身响应触摸测试
.hitTestBehavior(HitTestMode.Block)
// 移动手势
.gesture(
PanGesture(this.panOption)
.onActionStart((event: GestureEvent) => {})
.onActionUpdate((event: GestureEvent) => {
this.windowPosition.x += event.offsetX;
this.windowPosition.y += event.offsetY;
let top = CommonConstants.DEFAULT_HEIGHT;
let bottom = display.getDefaultDisplaySync().height - this.subWindow.getWindowProperties().windowRect.height - top;
if (this.windowPosition.y < top) {
this.windowPosition.y = top;
} else if (this.windowPosition.y > bottom) {
this.windowPosition.y = bottom;
}
this.subWindow.moveWindowTo(this.windowPosition.x, this.windowPosition.y);
})
.onActionEnd((event: GestureEvent) => {
if (this.windowPosition.x >= display.getDefaultDisplaySync().width / 2) {
this.windowPosition.x = display.getDefaultDisplaySync().width -
this.subWindow.getWindowProperties().windowRect.width;
} else if (event.offsetX < display.getDefaultDisplaySync().width / 2) {
this.windowPosition.x = 0;
}
let top = CommonConstants.DEFAULT_HEIGHT;
let bottom = display.getDefaultDisplaySync().height - this.subWindow.getWindowProperties().windowRect.height
- top;
if (this.windowPosition.y < top) {
this.windowPosition.y = top;
} else if (this.windowPosition.y > bottom) {
this.windowPosition.y = bottom;
}
this.subWindow.moveWindowTo(this.windowPosition.x, this.windowPosition.y);
}),
)
}
【背景知识】
在HarmonyOS开发中,触摸事件(onTouch事件)是用户与设备交互的基础,也是所有手势事件组成的基础,手势均由触摸事件组成。当组件被父布局(Stack、Row等任何容器组件)封装住时,对于父、子组件设置的响应手势极容易发生冲突。
- 触摸事件:当手指在组件上按下、滑动、抬起时触发。
- 自定义手势判定:为组件提供自定义手势判定能力。
- 手势事件冲突解决方案:多个组件嵌套时同时绑定手势事件,或者同一个组件同时绑定多个手势时,都极有可能发生手势冲突。
- 绑定手势方法:为组件绑定不同类型的手势事件,并设置事件的响应方法。
- 触摸测试控制:设置组件的触摸测试类型。
【定位思路】
当前手势绑定在了Row组件上,而Web组件位于Row组件的内部,整体结构如下图所示:
不难看出是最外层组件,B是A的子组件,两个组件叠加在一起,很容易出现手势冲突问题。
因此需排查以下几点内容:
**(**1)父组件手势优先级高于子组件
父组件具有自定义手势,方法为gesture,子组件未设置自定义手势,但是具有系统级的触摸事件。从手势优先级上来说,一定是子组件高于父组件,正常响应情况应该为:Web组件能响应触控事件,而Row组件失去了拖动响应。而非错误现象表现出的仅有父组件有触控响应,因此不是优先级设置问题。
(2**)阻塞触摸事件**
根据问题代码可知,Row组件为了提高自己的优先级绑定了方法hitTestBehavior(HitTestMode.Block),该方法会阻塞子节点进行触摸测试,因此内层Web组件不会响应onTouch事件。
【解决方案】
根据上述思路,方法hitTestBehavior(HitTestMode.Block)会使子组件的onTouch事件被阻塞,因此不能使用该方法。
代码示例如下:
Row() {
Web({
...
})
...
}
// .hitTestBehavior(HitTestMode.Transparent)
.gesture(
...
)
GestureMask.IgnoreInternal则会屏蔽子组件的手势,而非触碰事件。如子组件为List组件时,内置的滑动手势同样会被屏蔽。 若父子组件区域存在部分重叠,则只会屏蔽父子组件重叠的部分。
因此当前场景下应该使用GestureMask.IgnoreInternal方法。
代码示例如下:
.gesture( PanGesture(this.panOption) // ... GestureMask.IgnoreInternal)
处理效果:
【总结】
在复杂的应用界面中,多个组件嵌套时同时绑定手势事件,或者同一个组件同时绑定多个手势,都有可能导致手势事件产生冲突,达不到用户的预期效果,因此要分析好事件响应链,排布好手势响应的优先级。
手势分为系统手势与自定义手势两类,一般情况下,除非显式声明允许多个手势同时成功,否则同一时间只会有一个手势响应。
常见的手势冲突问题还有:滚动容器嵌套滚动容器事件冲突、系统手势和自定义手势之间冲突、使用组合手势同时绑定多个同类型手势冲突等等。
下面将举例和Web相关的另一个常见冲突问题:List**、Scroller等滚动容器嵌套Web组件导致滑动事件冲突**。
解决方案:
使用nestedScroll属性设置Web组件的嵌套滚动方式,NestedScrollMode设置成SELF_FIRST时,Web组件滚动到页面边缘后,父组件继续滚动。NestedScrollMode设置为PARENT_FIRST时,父组件先滚动,滚动至边缘后通知Web组件继续滚动。
Web(...)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
Web组件嵌套滚动:Web组件嵌套滚动的典型应用场景为,在一个页面中,有多个独立的区域需要进行滚动,当用户滚动Web区域内容时,可带动其他滚动区域进行滚动,以达到上下滑动页面的用户体验。
更多常见手势冲突问题请见链接内容。