核心内容摘要
å¤�å�ˆå�‹äººæ‰�缺å�£è¶…100ä¸‡ï¼šåˆ¶é€ ä¼�业的“AI人æ‰�è�’â€�æ€�ä¹ˆç ´ï¼Ÿ
单一手势点击事件onClick单击作为常用的手势可以方便地使用onClick接口实现。
尽管被称为事件它实际上是基本手势类型等同于将count配置为1的TapGesture即单击手势。
onClick与其他手势类型相同也会参与命中测试、响应链收集等过程。
可以使用干预手势处理机制对onClick的响应进行动态决策。
点击手势TapGesture点击手势支持单次点击和多次点击参数定义参考TapGesture。
长按手势LongPressGesture长按手势用于触发长按手势事件参数定义参考LongPressGesture。
滑动手势PanGesture滑动手势用于触发滑动手势事件滑动达到最小滑动距离默认值为5vp时滑动手势识别成功参数定义参考PanGesture。
说明大部分可滑动组件如List、Grid、Scroll、Tab等组件是通过PanGesture实现滑动在组件内部的子组件绑定滑动手势PanGesture或者滑动手势SwipeGesture会导致手势竞争。
当在子组件绑定PanGesture时在子组件区域进行滑动仅触发子组件的PanGesture。
如果需要父组件响应需要通过修改手势绑定方法或者子组件向父组件传递消息进行实现或者通过修改父子组件的PanGesture参数distance使得滑动更灵敏。
当子组件绑定SwipeGesture时由于PanGesture和SwipeGesture触发条件不同需要修改PanGesture和SwipeGesture的参数以达到所需效果。
不合理的阈值设置会导致滑动不跟手响应时延慢的问题。
捏合手势PinchGesture捏合手势用于触发捏合手势事件参数定义参考PinchGesture。
旋转手势RotationGesture旋转手势用于触发旋转手势事件参数定义参考RotationGesture。
快滑手势SwipeGesture快滑手势用于触发快滑事件当滑动速度大于100vp/s时可以识别成功参数定义参考SwipeGesture。
说明当SwipeGesture和PanGesture同时绑定时若二者是以默认方式或者互斥方式进行绑定时会发生竞争。
SwipeGesture的触发条件为滑动速度达到100vp/sPanGesture的触发条件为滑动距离达到5vp先达到触发条件的手势触发。
可以通过修改SwipeGesture和PanGesture的参数以达到不同的效果。
组合手势组合手势由多种单一手势组合而成通过在GestureGroup中使用不同的GestureMode来声明该组合手势的类型支持顺序识别、并行识别和互斥识别三种类型。
GestureGroup(mode:GestureMode, gesture:GestureType[])mode为GestureMode枚举类。
用于声明该组合手势的类型。
gesture由多个手势组合而成的数组。
用于声明该组合手势的各个手势。
顺序识别顺序识别组合手势对应的GestureMode为Sequence。
顺序识别组合手势将按照手势的注册顺序识别手势直到所有的手势识别成功。
当顺序识别组合手势中有一个手势识别失败时后续手势识别均失败。
顺序识别手势中仅有最后一个手势可以响应onActionEnd。
以一个由长按手势和滑动手势组合而成的顺序识别手势为例在一个Column组件上绑定了translate属性通过修改该属性可以设置组件的位置移动。
然后在该组件上绑定LongPressGesture和PanGesture组合而成的Sequence组合手势。
当触发LongPressGesture时更新显示的数字。
当长按后进行拖动时根据滑动手势的回调函数实现组件的拖动。
代码参考组合手势示例说明拖拽事件是一种典型的顺序识别组合手势事件由长按手势事件和滑动手势事件组合而成。
只有先长按达到长按手势事件预设置的时间后进行滑动才会触发拖拽事件。
如果长按事件未达到或者长按后未进行滑动拖拽事件均识别失败。
并行识别并行识别组合手势对应的GestureMode为Parallel。
并行识别组合手势中注册的手势将同时进行识别直到所有手势识别结束。
并行识别手势组合中的手势进行识别时互不影响。
以在一个Column组件上绑定点击手势和双击手势组成的并行识别手势为例由于单击手势和双击手势是并行识别因此两个手势可以同时进行识别二者互不干涉。
代码参考组合手势示例说明当由单击手势和双击手势组成一个并行识别组合手势后在区域内进行点击时单击手势和双击手势将同时进行识别。
当只有单次点击时单击手势识别成功双击手势识别失败。
当有两次点击时若两次点击相距时间在规定时间内默认规定时间为300毫秒触发两次单击事件和一次双击事件。
当有两次点击时若两次点击相距时间超出规定时间触发两次单击事件不触发双击事件。
互斥识别互斥识别组合手势对应的GestureMode为Exclusive。
互斥识别组合手势中注册的手势将同时进行识别若有一个手势识别成功则结束手势识别其他所有手势识别失败。
以在一个Column组件上绑定单击手势和双击手势组合而成的互斥识别组合手势为例。
若先绑定单击手势后绑定双击手势由于单击手势只需要一次点击即可触发而双击手势需要两次每次的点击事件均被单击手势消费而不能积累成双击手势所以双击手势无法触发。
若先绑定双击手势后绑定单击手势则触发双击手势不触发单击手势。
代码参考组合手势示例说明当由单击手势和双击手势组成一个互斥识别组合手势后在区域内进行点击时单击手势和双击手势将同时进行识别。
当只有单次点击时单击手势识别成功双击手势识别失败。
当有两次点击时手势响应取决于绑定手势的顺序。
若先绑定单击手势后绑定双击手势单击手势在第一次点击时即宣告识别成功此时双击手势已经失败。
即使在规定时间内进行了第二次点击双击手势事件也不会进行响应此时会触发单击手势事件的第二次识别成功。
若先绑定双击手势后绑定单击手势则会响应双击手势而不响应单击手势。
多层级手势事件多层级手势事件指父子组件嵌套时父子组件均绑定了手势或事件。
在该场景下手势或者事件的响应受到多个因素的影响相互之间发生传递和竞争容易出现预期外的响应。
本章主要介绍了多层级手势事件的默认响应顺序以及如何通过设置相关属性影响多层级手势事件的响应顺序。
默认多层级手势事件触摸事件触摸事件onTouch事件是所有手势组成的基础有DownMoveUpCancel四种。
手势均由触摸事件组成例如点击为DownUp滑动为Down一系列MoveUp。
触摸事件具有最特殊性
监听了onTouch事件的组件。
在手指落下被触摸时均会收到onTouch事件的回调被触摸受到触摸热区和触摸控制影响。
onTouch事件的回调是闭环的。
若一个组件收到了手指Id为0的Down事件后续也会收到手指Id为0的Move事件和Up事件。
onTouch事件的回调是一致的。
若一个组件收到了手指Id为0的Down事件但未收到手指Id为1的Down事件则后续只会收到手指Id为0的touch事件不会收到手指Id为1的后续touch事件。
对于一般的容器组件例如Column父子组件之间onTouch事件能够同时触发兄弟组件之间onTouch事件根据布局进行触发。
Column() { Column().id(ComponentB).onTouch(() {}) Column().id(ComponentC).onTouch(() {}) }.id(ComponentA).onTouch(() {})组件B和组件C作为组件A的子组件当触摸到组件B或者组件C时组件A也会被触摸到。
onTouch事件允许多个组件同时触发因此当触摸组件B时会触发组件A和组件B的onTouch回调不会触发组件C的onTouch回调。
当触摸组件C时会触发组件A和组件C的onTouch回调不触发组件B的回调。
特殊的容器组件如Stack等组件由于子组件之间存在着堆叠关系子组件的布局也存在相互遮盖关系。
所以父子组件之间onTouch事件能够同时触发兄弟组件之间onTouch事件会存在遮盖关系。
Stack() { Column().id(ComponentB).onTouch(() {}) Column().id(ComponentC).onTouch(() {}) }.id(Stack A).onTouch(() {})组件B和组件C作为Stack A的子组件组件C覆盖在组件B上。
当触摸到组件B或者组件C时Stack A也会被触摸到。
onTouch事件允许多个组件同时触发因此当触摸组件B和组件C的重叠区域时会触发Stack A和组件C的onTouch回调不会触发组件B的onTouch回调组件B被组件C遮盖。
手势与事件除了触摸事件onTouch事件外的所有手势与事件均是通过基础手势或者组合手势实现的。
例如拖拽事件是由长按手势和滑动手势组成的一个顺序手势。
在未显式声明的情况下同一时间一根手指对应的手势组中只会有一个手势获得成功从而触发所设置的回调。
因此除非显式声明允许多个手势同时成功同一时间只会有一个手势响应。
响应优先级遵循以下条件
当父子组件均绑定同一类手势时子组件优先于父组件触发。
当一个组件绑定多个手势时先达到手势触发条件的手势优先触发。
自定义控制的多层级手势事件可以通过设置属性控制默认的多层级手势事件竞争流程更好地实现手势事件。
目前responseRegion属性和hitTestBehavior属性可以控制Touch事件的分发从而可以影响到onTouch事件和手势的响应。
而绑定手势方法属性可以控制手势的竞争从而影响手势的响应但不能影响到onTouch事件。
responseRegion对手势和事件的控制responseRegion属性可以实现组件的响应区域范围的变化。
响应区域范围可以超出或者小于组件的布局范围。
Column() { Column() .id(ComponentB) .onTouch(() {}) .gesture(TapGesture({count: 1})) .responseRegion([rect1, rect2, rect3]) } .id(ComponentA) .onTouch(() {}) .gesture(TapGesture({count: 1})) .responseRegion([rect4])当组件A绑定了.responseRegion({Rect4})的属性后所有落在Rect4区域范围的触摸事件和手势可被组件A对应的回调响应。
当组件B绑定了.responseRegion({Rect1, Rect2, Rect3})的属性后所有落在Rect1,Rect2和Rect3区域范围的触摸事件和手势可被组件B对应的回调响应。
当绑定了responseRegion后手势与事件的响应区域范围将以所绑定的区域范围为准而不是以布局区域为准可能出现布局相关区域不响应手势与事件的情况。
此外responseRegion属性支持由多个Rect组成的数组作为入参以支持更多开发需求。
hitTestBehavior对手势和事件的控制hitTestBehavior属性可以实现在复杂的多层级场景下一些组件能够响应手势和事件而一些组件不能响应手势和事件。
Column() { Column() .id(ComponentB) .onTouch(() {}) .gesture(TapGesture({count: 1})) Column() { Column() .id(ComponentD) .onTouch(() {}) .gesture(TapGesture({count: 1})) } .id(ComponentC) .onTouch(() {}) .gesture(TapGesture({count: 1})) .hitTestBehavior(HitTestMode.Block) } .id(ComponentA) .onTouch(() {}) .gesture(TapGesture({count: 1}))HitTestMode.Block自身会响应触摸测试阻塞子节点和兄弟节点的触摸测试从而导致子节点和兄弟节点的onTouch事件和手势均无法触发。
当组件C未设置hitTestBehavior时点击组件D区域组件A、组件C和组件D的onTouch事件会触发组件D的点击手势会触发。
当组件C设置了hitTestBehavior为HitTestMode.Block时点击组件D区域组件A和组件C的onTouch事件会触发组件D的onTouch事件未触发。
同时由于组件D的点击手势因为被阻塞而无法触发组件C的点击手势会触发。
Stack() { Column() .id(ComponentB) .onTouch(() {}) .gesture(TapGesture({count: 1})) Column() .id(ComponentC) .onTouch(() {}) .gesture(TapGesture({count: 1})) .hitTestBehavior(HitTestMode.Transparent) } .id(Stack A) .onTouch(() {}) .gesture(TapGesture({count: 1}))HitTestMode.Transparent自身响应触摸测试不会阻塞兄弟节点的触摸测试。
当组件C未设置hitTestBehavior时点击组件B和组件C的重叠区域时Stack A和组件C的onTouch事件会触发组件C的点击事件会触发组件B的onTouch事件和点击手势均不触发。
而当组件C设置hitTestBehavior为HitTestMode.Transparent时点击组件B和组件C的重叠区域组件A和组件C不受到影响与之前一致组件A和组件C的onTouch事件会触发组件C的点击手势会触发。
而组件B因为组件C设置了HitTestMode.Transparent组件B也收到了Touch事件从而组件B的onTouch事件触发。
Column() { Column() .id(ComponentB) .onTouch(() {}) .gesture(TapGesture({count: 1})) } .id(ComponentA) .onTouch(() {}) .gesture(TapGesture({count: 1})) .hitTestBehavior(HitTestMode.None)HitTestMode.None自身不响应触摸测试不会阻塞子节点和兄弟节点的触摸控制。
当组件A未设置hitTestBehavior时点击组件B区域时组件A和组件B的onTouch事件均会触发组件B的点击手势会触发。
当组件A设置hitTestBehavior为HitTestMode.None时点击组件B区域时组件B的onTouch事件触发而组件A的onTouch事件无法触发组件B的点击手势触发。
Stack() { Column() .id(ComponentB) .onTouch(() {}) .gesture(TapGesture({count: 1})) Column() { Column() .id(ComponentD) .onTouch(() {}) .gesture(TapGesture({count: 1})) } .id(ComponentC) .onTouch(() {}) .gesture(TapGesture({count: 1})) .hitTestBehavior(HitTestMode.BLOCK_HIERARCHY) } .id(Stack A) .onTouch(() {}) .gesture(TapGesture({count: 1}))从API version 20开始HitTestMode.BLOCK_HIERARCHY自身和子节点响应触摸测试阻止所有优先级较低的兄弟节点和父节点参与触摸测试。
当组件C未设置hitTestBehavior时点击组件B和组件D的重叠区域时组件A组件C和组件D的onTouch事件均会触发组件D的点击手势会触发。
当组件C设置hitTestBehavior为HitTestMode.BLOCK_HIERARCHY时点击组件B和组件D的重叠区域时组件C和组件D的onTouch事件触发组件A和组件B的onTouch事件无法触发组件D的点击手势会触发。
Stack() { Column() .id(ComponentB) .onTouch(() {}) .gesture(TapGesture({count: 1})) Column() { Column() .id(ComponentD) .onTouch(() {}) .gesture(TapGesture({count: 1})) } .id(ComponentC) .onTouch(() {}) .gesture(TapGesture({count: 1})) .hitTestBehavior(HitTestMode.BLOCK_DESCENDANTS) } .id(Stack A) .onTouch(() {}) .gesture(TapGesture({count: 1}))从API version 20开始HitTestMode.BLOCK_DESCENDANTS自身不响应触摸测试并且所有的后代孩子孙子等也不响应触摸测试不会影响祖先节点的触摸测试。
若组件C未设置hitTestBehavior点击组件B和组件D的重叠区域时组件A、组件C和组件D都会触发onTouch事件同时组件D的点击手势也会被触发。
当组件C设置hitTestBehavior为HitTestMode.BLOCK_DESCENDANTS时点击组件B和组件D的重叠区域时组件A和组件B的onTouch事件触发组件C和组件D的onTouch事件无法触发组件B的点击手势会触发。
针对简单的场景建议在单个组件上绑定hitTestBehavior。
针对复杂场景建议在多个组件上绑定不同的hitTestBehavior来控制Touch事件的分发。
绑定手势方法对手势的控制设置绑定手势的方法可以实现在多层级场景下当父组件与子组件绑定了相同的手势时设置不同的绑定手势方法有不同的响应优先级。
当父组件使用.gesture绑定手势父子组件所绑定手势类型相同时子组件优先于父组件响应。
Column() { Column() .id(ComponentB) .gesture(TapGesture({count: 1})) } .id(ComponentA) .gesture(TapGesture({count: 1}))当父子组件均正常绑定点击手势时子组件优先于父组件响应。
此时单击组件B区域范围组件B的点击手势会触发组件A的点击手势不会触发。
如果以带优先级的方式绑定手势则可使得父组件所绑定手势的响应优先级高于子组件。
Column() { Column() .id(ComponentB) .gesture(TapGesture({count: 1})) } .id(ComponentA) .priorityGesture(TapGesture({count: 1}))当父组件以.priorityGesture的形式绑定手势时父组件所绑定的手势优先级高于子组件。
此时单击组件B区域范围组件A的点击手势会触发组件B的点击手势不会触发。
如果需要父子组件所绑定的手势不发生冲突均可响应则可以使用并行的方式在父组件绑定手势。
Column() { Column() .id(ComponentB) .gesture(TapGesture({count: 1})) } .id(ComponentA) .parallelGesture(TapGesture({count: 1}))当父组件以.parallelGesture的形式绑定手势时父组件和子组件所绑定的手势均可触发。
此时单击组件B区域范围组件A和组件B的点击手势均会触发。
OverlayManager的事件透传OverlayManager事件机制默认优先被WrappedBuilder内组件先接收不会向下传递。
若希望OverlayManager下方的页面也能感应到事件可采用hitTestBehavior(HitTestMode.Transparent)来传递事件参考以下伪代码。
Builder function builderOverlay(params: Params) { Component1().hitTestBehavior(HitTestMode.Transparent) } // ··· aboutToAppear(): void { // ··· let componentContent new ComponentContent( this.context, wrapBuilder[Params](builderOverlay), new Params(uiContext, {x:0, y: 100}) ); this.overlayManager.addComponentContent(componentContent,