【高心星出品】

事件响应原理

概述

在复杂的应用界面中,多个组件嵌套时同时绑定手势事件,或者同一个组件同时绑定多个手势,都有可能导致手势事件产生冲突,达不到用户的预期效果。

本文从事件响应的机制入手,介绍手势触发的基本流程,以及如何响应手势事件,了解背后的执行原理,并用来解决冲突问题等。

事件响应链

在HarmonyOS开发中,触摸事件(onTouch事件)是用户与设备交互的基础,是所有手势事件组成的基础,有Down,Move,Up,Cancel四种触摸事件的类型。手势均由触摸事件组成,例如,点击为Down+Up,滑动为Down+一系列Move+Up。

触摸事件的分发由触摸测试(TouchTest)结果决定,其结果会直接决定哪些控件的事件加入事件响应链(事件响应成员组成的链表),并最终按照响应链顺序判定是否消费。因此了解触摸事件的响应链收集过程,有助于开发者处理手势事件冲突问题。

ArkUI事件响应链收集,根据右子树(按组件布局的先后层级)优先的后序遍历流程。下面通过一个示例来介绍响应链收集的流程,示例伪代码如下:

build() {
  StackA() {
    ComponentB() {
      ComponentC()
    }

    ComponentD() {
      ComponentE()
    }
  }
}

代码逻辑走读:

  1. 函数定义:代码以 build()函数开始,这是一个构建函数,通常用于定义用户界面的结构。
  2. 主容器StackA被定义为主容器,用于垂直堆叠子组件。
  3. 子组件 ComponentB:在 StackA内部,ComponentB被定义,并进一步包含 ComponentC。这表明 ComponentB是一个容器,用于组织子组件 ComponentC
  4. 子组件 ComponentD:同样在 StackA内部,ComponentD被定义,并包含 ComponentE。这表明 ComponentD也是一个容器,用于组织子组件 ComponentE
  5. 组件组织:通过 StackA的堆叠布局,ComponentBComponentD被组织在一起,形成最终的用户界面结构。

其中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事件)外的所有手势与事件,均是通过基础手势或者组合手势实现的。例如,拖拽事件是由长按手势和滑动手势组成的一个顺序手势。

在默认情况下,这些手势为非冒泡事件,当父组件和子组件绑定同类型的手势时,父子组件绑定的手势事件会发生竞争,子组件会优先识别绑定的手势。

因此,除非显式声明允许多个手势同时成功,否则同一时间只会有一个手势响应。

  1. 当父子组件均绑定同一类手势时,子组件优先于父组件触发。
  2. 当同一个组件同时绑定多个手势时,先达到手势触发条件的手势优先触发。
  3. 当同一个组件绑定相同事件类型的系统手势和自定义手势时,系统手势会优先响应。比如自定义手势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事件。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐