鸿蒙6.0应用开发——事件传递原理
在复杂的应用界面中,多个组件嵌套时同时绑定手势事件,或者同一个组件同时绑定多个手势,都有可能导致手势事件产生冲突,达不到用户的预期效果。本文从事件响应的机制入手,介绍手势触发的基本流程,以及如何响应手势事件,了解背后的执行原理,并用来解决冲突问题等。
【高心星出品】
文章目录
事件响应原理
概述
在复杂的应用界面中,多个组件嵌套时同时绑定手势事件,或者同一个组件同时绑定多个手势,都有可能导致手势事件产生冲突,达不到用户的预期效果。
本文从事件响应的机制入手,介绍手势触发的基本流程,以及如何响应手势事件,了解背后的执行原理,并用来解决冲突问题等。
事件响应链
在HarmonyOS开发中,触摸事件(onTouch事件)是用户与设备交互的基础,是所有手势事件组成的基础,有Down,Move,Up,Cancel四种触摸事件的类型。手势均由触摸事件组成,例如,点击为Down+Up,滑动为Down+一系列Move+Up。
触摸事件的分发由触摸测试(TouchTest)结果决定,其结果会直接决定哪些控件的事件加入事件响应链(事件响应成员组成的链表),并最终按照响应链顺序判定是否消费。因此了解触摸事件的响应链收集过程,有助于开发者处理手势事件冲突问题。
ArkUI事件响应链收集,根据右子树(按组件布局的先后层级)优先的后序遍历流程。下面通过一个示例来介绍响应链收集的流程,示例伪代码如下:
build() {
StackA() {
ComponentB() {
ComponentC()
}
ComponentD() {
ComponentE()
}
}
}
代码逻辑走读:
- 函数定义:代码以
build()函数开始,这是一个构建函数,通常用于定义用户界面的结构。 - 主容器:
StackA被定义为主容器,用于垂直堆叠子组件。 - 子组件
ComponentB:在StackA内部,ComponentB被定义,并进一步包含ComponentC。这表明ComponentB是一个容器,用于组织子组件ComponentC。 - 子组件
ComponentD:同样在StackA内部,ComponentD被定义,并包含ComponentE。这表明ComponentD也是一个容器,用于组织子组件ComponentE。 - 组件组织:通过
StackA的堆叠布局,ComponentB和ComponentD被组织在一起,形成最终的用户界面结构。
其中A是最外层组件,B和D是A的子组件,C是B的子组件,E是D的子组件。界面效果示例以及组件树结构图如下:

用户触摸的动作如果发生在组件C上,事件响应链收集的流程如下,根据右子树(按组件布局的先后层级)优先的后序遍历流程,因为触摸点不在右边的树上,所以事件会从左边树的C节点开始往上传,触摸事件(onTouch事件)是冒泡事件默认会向上一直传递下去,直到被消费或者丢弃,允许多个组件同时触发。最终收集到的响应链是C->B->A。

用户触摸的动作如果发生在组件E上,事件响应链收集的流程如下,根据右子树优先的后序遍历流程,所以事件会从右边树的D节点开始往上传。虽然触摸点在组件D和组件B的交集上,但组件D的hitTestBehavior属性默认为HitTestMode.Default,D组件收集到事件后会阻塞兄弟节点(组件B),所以没有收集组件A的左子树,最终收集到的响应链是E->D->A。

上面介绍的事件响应链是系统默认的行为,如果需要改变响应的成员,比如触摸组件E的时候,希望把事件传递给B,该怎么实现呢?开发者可以通过设置D组件的hitTestMode属性为HitTestMode.None或者HitTestMode.Transparent来实现,比如设置为HitTestMode.Transparent,那么组件D自身进行触摸测试,同时不阻塞兄弟及父组件。最终收集到的响应链是E->D->B->A。

又例如触摸E组件的时候,只希望E响应触摸事件,不让其它组件响应触摸事件。可以通过stopPropagation来阻止事件冒泡,阻止触摸事件往上传递;也可以通过设置E组件的hitTestMode属性为HitTestMode.Block来实现,那么最终收集到的响应链成员只有组件E。

手势响应
前面根据事件响应链收集,确定了响应链成员和事件响应的顺序。然而往往在处理一些业务的时候,需要给组件/不同组件添加更多的手势和事件,比如onClick、API手势gesture 等等,那么哪个事件会得到响应呢?这就需要了解手势响应的优先级了,本节将主要介绍手势的优先级和手势的控制。
手势响应优先级
手势按是否为系统手势,可以分为以下两类:
- 系统手势:系统控件默认实现的手势(系统内置手势),即调用某些通用事件内置的手势,比如拖拽,onClick;比如bindMenu内置的点击事件,bindContextMenu内置的长按手势。
- 自定义手势:通过绑定手势API,例如使用gesture声明的事件回调,绑定长按手势事件方法。
除了触摸事件(onTouch事件)外的所有手势与事件,均是通过基础手势或者组合手势实现的。例如,拖拽事件是由长按手势和滑动手势组成的一个顺序手势。
在默认情况下,这些手势为非冒泡事件,当父组件和子组件绑定同类型的手势时,父子组件绑定的手势事件会发生竞争,子组件会优先识别绑定的手势。
因此,除非显式声明允许多个手势同时成功,否则同一时间只会有一个手势响应。
- 当父子组件均绑定同一类手势时,子组件优先于父组件触发。
- 当同一个组件同时绑定多个手势时,先达到手势触发条件的手势优先触发。
- 当同一个组件绑定相同事件类型的系统手势和自定义手势时,系统手势会优先响应。比如自定义手势TapGesture和系统手势onClick都是单击事件,但是会优先响应onClick事件。
图1 手势响应优先级(从左至右,优先级由高到低)

手势响应控制
上面介绍了手势默认的优先级顺序,在父子组件嵌套时,父子组件均绑定了手势或事件,或者同一个组件同时绑定多个手势时,根据业务逻辑可能需要对手势是否需要响应、分发给谁响应、响应的顺序等做出控制。那么有哪些控制手段呢?下面列举了一些手势响应的控制方法。
手势绑定
绑定手势方法
设置绑定手势的方法可以实现在多层级场景下,当父组件与子组件绑定了相同的手势时,设置不同的绑定手势方法有不同的响应优先级。手势绑定支持常规手势绑定方法(gesture)、带优先级手势绑定方法(priorityGesture)、并行手势绑定方法(parallelGesture)。
| 绑定手势方法 | 功能规格 | 配参1 | 配参2 | 约束 |
|---|---|---|---|---|
| gesture | 绑定手势事件,父子组件交叠区域均绑定,响应子组件 | GestureType | GestureMask | 与通用事件抢占 |
| priorityGesture | 当父组件配置priorityGesture时,优先识别父组件priorityGesture绑定的手势。 | GestureType | GestureMask | 与通用事件抢占 |
| parallelGesture | 父组件绑定parallelGesture时,父子组件相同的手势事件都可以触发 | GestureType | GestureMask | 无 |
前面讲到的手势的优先级是默认的,在加入了priorityGesture和parallelGesture绑定方法后,手势的响应顺序如下图所示:
图2 手势响应优先级(从左至右,优先级由高到低)

GestureMask枚举说明
| 名称 | 描述 |
|---|---|
| Normal | 不屏蔽子组件的手势,按照默认手势识别顺序进行识别。 |
| IgnoreInternal | 屏蔽子组件的手势,包括子组件上的系统内置的手势,如子组件为List组件时,内置的滑动手势同样会被屏蔽。 若父子组件区域存在部分重叠,则只会屏蔽父子组件重叠的部分。 |
不同手势绑定配参方案规格
| 父手势 | 子手势 | GestureMask(父) | 交叠区域相同事件响应方 | 交叠区域不同事件响应方 |
|---|---|---|---|---|
| gesture | gesture | default | 子组件 | 各自响应 |
| gesture | gesture | IgnoreInternal | 父组件 | 父组件 |
| priorityGesture | gesture | default | 父组件 | 各自响应 |
| priorityGesture | gesture | IgnoreInternal | 父组件 | 父组件 |
| parallelGesture | gesture | default | 各自响应 | 各自响应 |
| parallelGesture | gesture | IgnoreInternal | 父组件 | 父组件 |
组合手势(GestureGroup)
手势组合是指多种手势组合为复合手势,通过GestureGroup属性,可以给同一个组件添加多个手势,支持连续识别、并行识别和互斥识别模式。开发者可以根据业务需求,选择合适的组合模式。
| 接口 | 可选模式 | 描述 | 注册事件 |
|---|---|---|---|
| GestureGroup | Sequence | 手势顺序队列,需要按预定的手势组顺序执行,有一个失败则全部失败 | onCancel |
| GestureGroup | Parallel | 手势组合,直到所有已识别的手势执行完 | 无 |
| GestureGroup | Exclusive | 互斥识别,成功完成一个手势,则完成手势任务 | 无 |
事件独占控制
通过monopolizeEvents属性设置组件是否独占事件,事件范围包括组件自带的事件和开发者自定义的点击、触摸、手势事件。先响应事件的控件作为第一响应者,在手指离开屏幕前其他组件不会响应任何事件。
在一个窗口内,设置了独占控制的组件上的事件如果首先响应,则本次交互只允许此组件上设置的事件响应,窗口内其他组件上的事件不会响应。
如果开发者通过parallelGesture绑定了与子组件同时触发的手势,如PanGesture,子组件设置了独占控制且首个响应事件,则父组件的手势不会响应。
自定义手势判定
为组件提供自定义手势判定能力。开发者可根据需要,在手势识别期间,根据自己的业务逻辑来决定是否响应手势。使用onGestureJudgeBegin方法对手势进行判定,开发者可以根据自身业务逻辑,选择是否响应自定义手势。
手势拦截增强
为组件提供手势拦截能力。开发者可根据需要,将系统内置手势和响应链上更高优先级的手势做并行化处理,并可以动态控制手势事件的触发。
responseRegion和hitTestBehavior
触摸测试同样也可能会影响到手势的响应流程。例如responseRegion属性和hitTestBehavior属性可以控制Touch事件的分发,从而可以影响到onTouch事件和手势的响应。而绑定手势方法属性可以控制手势的竞争从而影响手势的响应,但不会影响到onTouch事件。
ArkUI组件自身的属性控制手势响应
影响到手势的响应流程。例如responseRegion属性和hitTestBehavior属性可以控制Touch事件的分发,从而可以影响到onTouch事件和手势的响应。而绑定手势方法属性可以控制手势的竞争从而影响手势的响应,但不会影响到onTouch事件。
更多推荐



所有评论(0)