• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 手势拦截增强
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @jiangtao92-->
5<!--Designer: @piggyguy-->
6<!--Tester: @songyanhong-->
7<!--Adviser: @HelloCrease-->
8
9为组件提供手势拦截能力。开发者可根据需要,将系统内置手势和比其优先级高的手势做并行化处理,并可以动态控制手势事件的触发。
10
11>  **说明:**
12>
13>  从API Version 12开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。
14
15## shouldBuiltInRecognizerParallelWith
16
17shouldBuiltInRecognizerParallelWith(callback: ShouldBuiltInRecognizerParallelWithCallback): T
18
19提供系统内置手势与响应链上其他组件的手势设置并行关系的回调事件。此接口对应的C API接口为[setInnerGestureParallelTo](../capi-arkui-nativemodule-arkui-nativegestureapi-1.md#setinnergestureparallelto)。
20
21**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
22
23**系统能力:** SystemCapability.ArkUI.ArkUI.Full
24
25**参数:**
26| 参数名        | 类型                    | 必填  | 说明                          |
27| ---------- | -------------------------- | ------- | ----------------------------- |
28| callback      | [ShouldBuiltInRecognizerParallelWithCallback](#shouldbuiltinrecognizerparallelwithcallback) | 是   |  系统内置手势与响应链上其他组件的手势设置并行关系的回调事件,当该组件进行触摸碰撞测试时,会触发用户定义的回调来形成手势并行关系。 |
29
30**返回值:**
31
32| 类型 | 说明 |
33| -------- | -------- |
34| T | 返回当前组件。 |
35
36## ShouldBuiltInRecognizerParallelWithCallback
37
38type ShouldBuiltInRecognizerParallelWithCallback = (current: GestureRecognizer, others: Array\<GestureRecognizer\>) => GestureRecognizer
39
40系统内置手势与响应链上其他组件的手势设置并行关系的回调事件类型。
41
42**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
43
44**系统能力:** SystemCapability.ArkUI.ArkUI.Full
45
46**参数:**
47
48| 参数名   | 类型                      | 必填 | 说明                                                         |
49| -------- | ------------------------- | ---- | ------------------------------------------------------------ |
50| current | [GestureRecognizer](ts-gesture-common.md#gesturerecognizer12) | 是   | 当前组件的系统内置手势识别器,当前版本只提供内置的[GestureType](./ts-gesture-common.md#gesturetype11).PAN_GESTURE类型的手势识别器。 |
51| others | Array\<[GestureRecognizer](ts-gesture-common.md#gesturerecognizer12)\> | 是   | 响应链上更高优先级的其他组件相同类别的手势识别器。 |
52
53**返回值:**
54
55| 类型     | 说明        |
56| ------ | --------- |
57| [GestureRecognizer](ts-gesture-common.md#gesturerecognizer12) | 与current识别器绑定并行关系的某个手势识别器。 |
58
59## onGestureRecognizerJudgeBegin<sup>13+</sup>
60
61onGestureRecognizerJudgeBegin(callback: GestureRecognizerJudgeBeginCallback, exposeInnerGesture: boolean): T
62
63给组件绑定自定义手势识别器判定回调。
64
65新增exposeInnerGesture参数作为是否将ArkUI系统组合组件的内置组件的手势暴露给开发者的标识。当该标识置为true时,将ArkUI系统组合组件的内置组件的手势暴露给开发者。<br>
66对于不需要将ArkUI系统组合组件的内置组件的手势暴露给开发者的场景,建议采用原有[onGestureRecognizerJudgeBegin](#ongesturerecognizerjudgebegin)接口。若要求将ArkUI系统组合组件的内置组件的手势暴露给开发者,建议使用该接口并将exposeInnerGesture设置为true。
67
68**原子化服务API:** 从API version 13开始,该接口支持在原子化服务中使用。
69
70**系统能力:** SystemCapability.ArkUI.ArkUI.Full
71
72**参数:**
73| 参数名        | 类型                    | 必填  | 说明                          |
74| ---------- | -------------------------- | ------- | ----------------------------- |
75| callback      | [GestureRecognizerJudgeBeginCallback](#gesturerecognizerjudgebegincallback) | 是     |  给组件绑定自定义手势识别器判定回调,当绑定到该组件的手势即将成功时,会触发用户定义的回调来获取结果。 |
76| exposeInnerGesture   | boolean         | 是    | 暴露内部手势标识。<br/>默认值:false<br/>**说明:**<br/>如果是组合组件,此参数设置true,回调中的current参数则会包含组合组件内部的手势识别器。<br>当前仅支持[Tabs](ts-container-tabs.md),其他组件请不要设置此参数。<br/>设置为false时,功能与原接口[onGestureRecognizerJudgeBegin](#ongesturerecognizerjudgebegin)相同。 |
77
78**返回值:**
79
80| 类型 | 说明 |
81| -------- | -------- |
82| T | 返回当前组件。 |
83
84## onGestureRecognizerJudgeBegin
85
86onGestureRecognizerJudgeBegin(callback: GestureRecognizerJudgeBeginCallback): T
87
88给组件绑定自定义手势识别器判定回调。
89
90**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。
91
92**系统能力:** SystemCapability.ArkUI.ArkUI.Full
93
94**参数:**
95| 参数名        | 类型                    | 必填  | 说明                          |
96| ---------- | -------------------------- | ------- | ----------------------------- |
97| callback      | [GestureRecognizerJudgeBeginCallback](#gesturerecognizerjudgebegincallback) | 是     |  自定义手势识别器判定回调。当绑定到该组件的手势即将成功时,会触发用户定义的回调来获取结果。 |
98
99**返回值:**
100
101| 类型 | 说明 |
102| -------- | -------- |
103| T | 返回当前组件。 |
104
105## GestureRecognizerJudgeBeginCallback
106
107type GestureRecognizerJudgeBeginCallback = (event: BaseGestureEvent, current: GestureRecognizer, recognizers: Array\<GestureRecognizer\>, touchRecognizers?: Array\<TouchRecognizer\>) => GestureJudgeResult
108
109自定义手势识别器判定回调类型。
110
111**系统能力:** SystemCapability.ArkUI.ArkUI.Full
112
113**参数:**
114
115| 参数名   | 类型                      | 必填 | 说明                                                         |
116| -------- | ------------------------- | ---- | ------------------------------------------------------------ |
117| event | [BaseGestureEvent](./ts-gesture-common.md#basegestureevent11对象说明) | 是   | 当前基础手势事件信息。<br/>**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 |
118| current | [GestureRecognizer](ts-gesture-common.md#gesturerecognizer12) | 是   | 当前即将要响应的识别器对象。<br/>**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 |
119| recognizers | Array\<[GestureRecognizer](ts-gesture-common.md#gesturerecognizer12)\> | 是   | 响应链上的其他手势识别器对象。<br/>**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 |
120| touchRecognizers<sup>20+</sup> | Array\<[TouchRecognizer](ts-gesture-common.md#touchrecognizer20)\> | 否   | 响应链上的Touch识别器对象。 默认值为null,表示在当前手势绑定组件及其子孙组件没有可响应的Touch识别器。<br/>**原子化服务API:** 从API version 20开始,该接口支持在原子化服务中使用。|
121**返回值:**
122
123| 类型     | 说明        |
124| ------ | --------- |
125| [GestureJudgeResult](./ts-gesture-common.md#gesturejudgeresult12) | 手势是否裁决成功的判定结果。 |
126
127## onTouchTestDone<sup>20+</sup>
128
129onTouchTestDone(callback: TouchTestDoneCallback): T
130
131提供在[触摸测试](../../../ui/arkts-interaction-basic-principles.md#触摸测试)结束后,指定手势识别器是否参与后续处理的能力。
132
133**原子化服务API:** 从API version 20开始,该接口支持在原子化服务中使用。
134
135**系统能力:** SystemCapability.ArkUI.ArkUI.Full
136
137**参数:**
138
139| 参数名        | 类型                    | 必填  | 说明                          |
140| ---------- | -------------------------- | ------- | ----------------------------- |
141| callback      | [TouchTestDoneCallback](#touchtestdonecallback20) | 是   |  回调函数,用于指定手势识别器是否参与后续处理。在[触摸测试](../../../ui/arkts-interaction-basic-principles.md#触摸测试)结束后,开始识别用户手势之前,会触发该回调来动态指定手势识别器是否参与后续处理。 |
142
143**返回值:**
144
145| 类型 | 说明 |
146| -------- | -------- |
147| T | 返回当前组件。 |
148
149## TouchTestDoneCallback<sup>20+</sup>
150
151type TouchTestDoneCallback = (event: BaseGestureEvent, recognizers: Array\<GestureRecognizer\>) => void
152
153动态指定手势识别器是否参与手势处理的回调事件类型。
154
155**原子化服务API:** 从API version 20开始,该接口支持在原子化服务中使用。
156
157**系统能力:** SystemCapability.ArkUI.ArkUI.Full
158
159**参数:**
160
161| 参数名   | 类型                      | 必填 | 说明                                                         |
162| -------- | ------------------------- | ---- | ------------------------------------------------------------ |
163| event | [BaseGestureEvent](./ts-gesture-common.md#basegestureevent11对象说明) | 是   | [触摸测试](../../../ui/arkts-interaction-basic-principles.md#触摸测试)结束后的基础手势事件的信息。 <br/>**说明:** <br/>仅包含BaseGestureEvent的信息,不包含其子类拓展信息。<br/>axisHorizontal和axisVertical的值为0。 |
164| recognizers | Array\<[GestureRecognizer](ts-gesture-common.md#gesturerecognizer12)\> | 是   | [触摸测试](../../../ui/arkts-interaction-basic-principles.md#触摸测试)结束后,所有手势识别器对象。 |
165
166## 示例
167
168### 示例1(嵌套滚动)
169
170该示例通过shouldBuiltInRecognizerParallelWith和onGestureRecognizerJudgeBegin实现了嵌套滚动的功能。内部组件优先响应滑动手势,当内部组件滑动至顶部或底部时,外部组件能够接替滑动。
171
172```ts
173// xxx.ets
174@Entry
175@Component
176struct FatherControlChild {
177  scroller: Scroller = new Scroller();
178  scroller2: Scroller = new Scroller();
179  private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
180  private childRecognizer: GestureRecognizer = new GestureRecognizer();
181  private currentRecognizer: GestureRecognizer = new GestureRecognizer();
182  private lastOffset: number = 0;
183
184  build() {
185    Stack({ alignContent: Alignment.TopStart }) {
186      Scroll(this.scroller) { // 外部滚动容器
187        Column() {
188          Text("Scroll Area")
189            .width('90%')
190            .height(150)
191            .backgroundColor(0xFFFFFF)
192            .borderRadius(15)
193            .fontSize(16)
194            .textAlign(TextAlign.Center)
195            .margin({ top: 10 })
196          Scroll(this.scroller2) { // 内部滚动容器
197            Column() {
198              Text("Scroll Area2")
199                .width('90%')
200                .height(150)
201                .backgroundColor(0xFFFFFF)
202                .borderRadius(15)
203                .fontSize(16)
204                .textAlign(TextAlign.Center)
205                .margin({ top: 10 })
206              Column() {
207                ForEach(this.arr, (item: number) => {
208                  Text(item.toString())
209                    .width('90%')
210                    .height(150)
211                    .backgroundColor(0xFFFFFF)
212                    .borderRadius(15)
213                    .fontSize(16)
214                    .textAlign(TextAlign.Center)
215                    .margin({ top: 10 })
216                }, (item: string) => item)
217              }.width('100%')
218            }
219          }
220          .id("inner")
221          .width('100%')
222          .height(800)
223        }.width('100%')
224      }
225      .id("outer")
226      .height(600)
227      .scrollable(ScrollDirection.Vertical) // 滚动方向纵向
228      .scrollBar(BarState.On) // 滚动条常驻显示
229      .scrollBarColor(Color.Gray) // 滚动条颜色
230      .scrollBarWidth(10) // 滚动条宽度
231      .edgeEffect(EdgeEffect.None)
232      .shouldBuiltInRecognizerParallelWith((current: GestureRecognizer, others: Array<GestureRecognizer>) => {
233        for (let i = 0; i < others.length; i++) {
234          let target = others[i].getEventTargetInfo();
235          if (target) {
236            if (target.getId() == "inner" && others[i].isBuiltIn() &&
237              others[i].getType() == GestureControl.GestureType.PAN_GESTURE) { // 找到将要组成并行手势的识别器
238              this.currentRecognizer = current; // 保存当前组件的识别器
239              this.childRecognizer = others[i]; // 保存将要组成并行手势的识别器
240              return others[i]; // 返回将要组成并行手势的识别器
241            }
242          }
243        }
244        return undefined;
245      })
246      .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer,
247        others: Array<GestureRecognizer>) => { // 在识别器即将要成功时,根据当前组件状态,设置识别器使能状态
248        if (current) {
249          let target = current.getEventTargetInfo();
250          if (target) {
251            if (target.getId() == "outer" && current.isBuiltIn() &&
252              current.getType() == GestureControl.GestureType.PAN_GESTURE) {
253              if (others) {
254                for (let i = 0; i < others.length; i++) {
255                  let target = others[i].getEventTargetInfo() as ScrollableTargetInfo;
256                  if (target instanceof ScrollableTargetInfo && target.getId() == "inner") { // 找到响应链上对应并行的识别器
257                    let panEvent = event as PanGestureEvent;
258                    if (target.isEnd()) { // 根据当前组件状态以及移动方向动态控制识别器使能状态
259                      if (panEvent && panEvent.offsetY < 0) {
260                        this.childRecognizer.setEnabled(false);
261                        this.currentRecognizer.setEnabled(true);
262                      } else {
263                        this.childRecognizer.setEnabled(true);
264                        this.currentRecognizer.setEnabled(false);
265                      }
266                    } else if (target.isBegin()) {
267                      if (panEvent.offsetY > 0) {
268                        this.childRecognizer.setEnabled(false);
269                        this.currentRecognizer.setEnabled(true);
270                      } else {
271                        this.childRecognizer.setEnabled(true);
272                        this.currentRecognizer.setEnabled(false);
273                      }
274                    } else {
275                      this.childRecognizer.setEnabled(true);
276                      this.currentRecognizer.setEnabled(false);
277                    }
278                  }
279                }
280              }
281            }
282          }
283        }
284        return GestureJudgeResult.CONTINUE;
285      })
286      .parallelGesture( // 绑定一个Pan手势作为动态控制器
287        PanGesture()
288          .onActionUpdate((event: GestureEvent) => {
289            if (this.childRecognizer.getState() != GestureRecognizerState.SUCCESSFUL ||
290              this.currentRecognizer.getState() != GestureRecognizerState.SUCCESSFUL) { // 如果识别器状态不是SUCCESSFUL,则不做控制
291              return;
292            }
293            let target = this.childRecognizer.getEventTargetInfo() as ScrollableTargetInfo;
294            let currentTarget = this.currentRecognizer.getEventTargetInfo() as ScrollableTargetInfo;
295            if (target instanceof ScrollableTargetInfo && currentTarget instanceof ScrollableTargetInfo) {
296              if (target.isEnd()) { // 在移动过程中实时根据当前组件状态,控制识别器的开闭状态
297                if ((event.offsetY - this.lastOffset) < 0) {
298                  this.childRecognizer.setEnabled(false);
299                  if (currentTarget.isEnd()) {
300                    this.currentRecognizer.setEnabled(false);
301                  } else {
302                    this.currentRecognizer.setEnabled(true);
303                  }
304                } else {
305                  this.childRecognizer.setEnabled(true);
306                  this.currentRecognizer.setEnabled(false);
307                }
308              } else if (target.isBegin()) {
309                if ((event.offsetY - this.lastOffset) > 0) {
310                  this.childRecognizer.setEnabled(false);
311                  if (currentTarget.isBegin()) {
312                    this.currentRecognizer.setEnabled(false);
313                  } else {
314                    this.currentRecognizer.setEnabled(true);
315                  }
316                } else {
317                  this.childRecognizer.setEnabled(true);
318                  this.currentRecognizer.setEnabled(false);
319                }
320              } else {
321                this.childRecognizer.setEnabled(true);
322                this.currentRecognizer.setEnabled(false);
323              }
324            }
325            this.lastOffset = event.offsetY;
326          })
327      )
328    }.width('100%').height('100%').backgroundColor(0xDCDCDC)
329  }
330}
331```
332![fatherControlChild](figures/fatherControlChild.gif)
333
334### 示例2(嵌套场景下拦截内部容器手势)
335
336本示例通过将参数exposeInnerGesture设置为true,实现了一级Tabs容器在嵌套二级Tabs的场景下,能够屏蔽二级Tabs内置Swiper的滑动手势,从而触发一级Tabs内置Swiper滑动手势的功能。
337开发者自行定义变量来记录内层Tabs的索引值,通过该索引值判断当滑动达到内层Tabs的边界处时,触发回调返回屏蔽使外层Tabs产生滑动手势。
338
339```ts
340// xxx.ets
341@Entry
342@Component
343struct Index {
344  @State currentIndex: number = 0;
345  @State selectedIndex: number = 0;
346  @State fontColor: string = '#182431';
347  @State selectedFontColor: string = '#007DFF';
348  innerSelectedIndex: number = 0; // 记录内层Tabs的索引
349  controller?: TabsController = new TabsController();
350
351  @Builder
352  tabBuilder(index: number, name: string) {
353    Column() {
354      Text(name)
355        .fontColor(this.selectedIndex === index ? this.selectedFontColor : this.fontColor)
356        .fontSize(16)
357        .fontWeight(this.selectedIndex === index ? 500 : 400)
358        .lineHeight(22)
359        .margin({ top: 17, bottom: 7 })
360      Divider()
361        .strokeWidth(2)
362        .color('#007DFF')
363        .opacity(this.selectedIndex === index ? 1 : 0)
364    }.width('100%')
365  }
366
367  build() {
368    Column() {
369      Tabs({ barPosition: BarPosition.Start, index: this.currentIndex, controller: this.controller }) {
370        TabContent() {
371          Column().width('100%').height('100%').backgroundColor(Color.Green)
372        }.tabBar(this.tabBuilder(0, 'green'))
373
374        TabContent() {
375          Tabs() {
376            TabContent() {
377              Column().width('100%').height('100%').backgroundColor(Color.Blue)
378            }.tabBar(new SubTabBarStyle('blue'))
379
380            TabContent() {
381              Column().width('100%').height('100%').backgroundColor(Color.Pink)
382            }.tabBar(new SubTabBarStyle('pink'))
383          }
384          .onAnimationStart((index: number, targetIndex: number) => {
385            console.info('ets onGestureRecognizerJudgeBegin child:' + targetIndex)
386            this.innerSelectedIndex = targetIndex
387          })
388          .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer,
389            others: Array<GestureRecognizer>): GestureJudgeResult => { // 在识别器即将要成功时,根据当前组件状态,设置识别器使能状态
390            console.info('ets onGestureRecognizerJudgeBegin child')
391            if (current) {
392              let target = current.getEventTargetInfo();
393              if (target && current.isBuiltIn() && current.getType() == GestureControl.GestureType.PAN_GESTURE) {
394                console.info('ets onGestureRecognizerJudgeBegin child PAN_GESTURE')
395                let panEvent = event as PanGestureEvent;
396                if (panEvent && panEvent.velocityX < 0 && this.innerSelectedIndex === 1) { // 内层Tabs滑动到尽头
397                  console.info('ets onGestureRecognizerJudgeBegin child reject end')
398                  return GestureJudgeResult.REJECT;
399                }
400                if (panEvent && panEvent.velocityX > 0 && this.innerSelectedIndex === 0) { // 内层Tabs滑动到开头
401                  console.info('ets onGestureRecognizerJudgeBegin child reject begin')
402                  return GestureJudgeResult.REJECT;
403                }
404              }
405            }
406            return GestureJudgeResult.CONTINUE;
407          }, true)
408        }.tabBar(this.tabBuilder(1, 'blue and pink'))
409
410        TabContent() {
411          Column().width('100%').height('100%').backgroundColor(Color.Brown)
412        }.tabBar(this.tabBuilder(2, 'brown'))
413      }
414      .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => {
415        // 切换动画开始时触发该回调。目标页签显示下划线。
416        this.selectedIndex = targetIndex
417      })
418    }
419  }
420}
421```
422
423 ![example](figures/gesture_recognizer.gif)
424
425
426### 示例3(拦截手势获取属性)
427
428该示例通过配置onGestureRecognizerJudgeBegin判定手势,获取相应属性参数。
429
430```ts
431// xxx.ets
432@Entry
433@Component
434struct Index {
435  @State message: string = 'Gesture';
436
437  build() {
438    Column() {
439      Row({ space: 20 }) {
440        Text(this.message)
441          .width(400)
442          .height(80)
443          .fontSize(23)
444      }.margin(25)
445    }
446    .margin(50)
447    .width(400)
448    .height(200)
449    .borderWidth(2)
450    .gesture(TapGesture())
451    .gesture(LongPressGesture())
452    .gesture(PanGesture({ direction: PanDirection.Vertical }))
453    .gesture(PinchGesture())
454    .gesture(RotationGesture())
455    .gesture(SwipeGesture({ direction: SwipeDirection.Horizontal }))
456    // 给组件绑定自定义手势识别器判定回调
457    .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer,
458      others: Array<GestureRecognizer>) => {
459      if (current) {
460        // 判断是否为拖动手势
461        if (current.getType() == GestureControl.GestureType.PAN_GESTURE) {
462          let target = current as PanRecognizer;
463          this.message = 'PanGesture\ndistance:' + target.getPanGestureOptions().getDistance() + '\nfingers:' +
464          target.getFingerCount() + '\nisFingerCountLimited:' + target.isFingerCountLimit();
465        }
466        // 判断是否为长按手势
467        if (current.getType() == GestureControl.GestureType.LONG_PRESS_GESTURE) {
468          let target = current as LongPressRecognizer;
469          this.message = 'LongPressGesture\nfingers:' + target.getFingerCount() + '\nisFingerCountLimited:' +
470          target.isFingerCountLimit() + '\nrepeat:' + target.isRepeat() + '\nduration:' + target.getDuration();
471        }
472        // 判断是否为捏合手势
473        if (current.getType() == GestureControl.GestureType.PINCH_GESTURE) {
474          let target = current as PinchRecognizer;
475          this.message = 'PinchGesture\ndistance:' + target.getDistance() + '\nfingers:' +
476          target.getFingerCount() + '\nisFingerCountLimited:' + target.isFingerCountLimit();
477        }
478        // 判断是否为点击手势
479        if (current.getType() == GestureControl.GestureType.TAP_GESTURE) {
480          let target = current as TapRecognizer;
481          this.message = 'TapGesture\ncount:' + target.getTapCount() + '\nfingers:' +
482          target.getFingerCount() + '\nisFingerCountLimited:' + target.isFingerCountLimit();
483        }
484        // 判断是否为旋转手势
485        if (current.getType() == GestureControl.GestureType.ROTATION_GESTURE) {
486          let target = current as RotationRecognizer;
487          this.message = 'RotationGesture\nangle:' + target.getAngle() + '\nfingers:' +
488          target.getFingerCount() + '\nisFingerCountLimited:' + target.isFingerCountLimit();
489        }
490        // 判断是否为滑动手势
491        if (current.getType() == GestureControl.GestureType.SWIPE_GESTURE) {
492          let target = current as SwipeRecognizer;
493          this.message = 'SwipeGesture\ndirection:' + target.getDirection() + '\nfingers:' +
494          target.getFingerCount() + '\nisFingerCountLimited:' + target.isFingerCountLimit() + '\nspeed:' +
495          target.getVelocityThreshold();
496        }
497      }
498      return GestureJudgeResult.CONTINUE;
499    })
500  }
501}
502```
503
504 ![example](figures/gesture_recognizer_obtain_attributes.gif)
505
506 ### 示例4(手势触发成功时取消子组件上的Touch事件)
507
508该示例通过配置onGestureRecognizerJudgeBegin判定手势,在父容器手势触发成功时,调用cancelTouch()强制取消子组件上的Touch事件,实现父子组件手势控制的精准切换。
509
510 ```ts
511 // xxx.ets
512@Entry
513@Component
514struct FatherControlChild {
515  scroller: Scroller = new Scroller();
516  scroller2: Scroller = new Scroller()
517  private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
518  private childRecognizer: GestureRecognizer = new GestureRecognizer();
519  private currentRecognizer: GestureRecognizer = new GestureRecognizer();
520  private lastOffset: number = 0;
521
522  @State outerState: string = "IDLE";
523  @State innerState: string = "IDLE";
524  @State willCancel: boolean = false;
525
526  build() {
527    Stack({ alignContent: Alignment.TopStart }) {
528      Scroll(this.scroller) { // 外部滚动容器
529        Column() {
530          Text("Scroll Area")
531            .width('90%')
532            .height(150)
533            .backgroundColor(0xFFFFFF)
534            .borderRadius(15)
535            .fontSize(16)
536            .textAlign(TextAlign.Center)
537            .margin({ top: 10 })
538
539          Scroll(this.scroller2) { // 内部滚动容器
540            Column() {
541              Text("Scroll Area2")
542                .width('90%')
543                .height(150)
544                .backgroundColor(0xFFFFFF)
545                .borderRadius(15)
546                .fontSize(16)
547                .textAlign(TextAlign.Center)
548                .margin({ top: 10 })
549
550              Column() {
551                ForEach(this.arr, (item: number) => {
552                  Text(item.toString())
553                    .width('90%')
554                    .height(150)
555                    .backgroundColor(0xFFFFFF)
556                    .borderRadius(15)
557                    .fontSize(16)
558                    .textAlign(TextAlign.Center)
559                    .margin({ top: 10 })
560                }, (item: string) => item)
561              }.width('100%')
562            }
563          }
564          .id("inner")
565          .width('100%')
566          .height(800)
567          .onTouch((event) => {
568            if (event.type === TouchType.Down) {
569              this.innerState = "TOUCHING";
570              this.willCancel = false;
571            } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
572              if (this.willCancel) {
573                this.innerState = "CANCELLED";
574                setTimeout(() => {
575                  this.innerState = "IDLE";
576                  this.willCancel = false;
577                }, 1000);
578              } else {
579                this.innerState = "IDLE";
580              }
581            }
582          })
583        }.width('100%')
584      }
585      .id("outer")
586      .height('100%')
587      .scrollable(ScrollDirection.Vertical)
588      .scrollBar(BarState.On)
589      .scrollBarColor(Color.Gray)
590      .scrollBarWidth(10)
591      .edgeEffect(EdgeEffect.None)
592      .shouldBuiltInRecognizerParallelWith((current: GestureRecognizer, others: Array<GestureRecognizer>) => {
593        for (let i = 0; i < others.length; i++) {
594          let target = others[i].getEventTargetInfo();
595          if (target) {
596            if (target.getId() == "inner" && others[i].isBuiltIn() &&
597              others[i].getType() == GestureControl.GestureType.PAN_GESTURE) { // 找到将要组成并行手势的识别器
598              this.currentRecognizer = current; // 保存当前组件的识别器
599              this.childRecognizer = others[i]; // 保存将要组成并行手势的识别器
600              return others[i]; // 返回将要组成并行手势的识别器
601            }
602          }
603        }
604        return undefined;
605      })
606      .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer,
607        others: Array<GestureRecognizer>,
608        touchRecognizers?: Array<TouchRecognizer>) => { // 在识别器即将要成功时,根据当前组件状态,设置识别器使能状态
609        if (current && touchRecognizers) {
610          let target = current.getEventTargetInfo();
611          if (target) {
612            if (target.getId() == "outer" && current.isBuiltIn() &&
613              current.getType() == GestureControl.GestureType.PAN_GESTURE) {
614              return GestureJudgeResult.CONTINUE
615            }
616            for (let index = 0; index < touchRecognizers.length; index++) {
617              const element = touchRecognizers![index];
618              let touchTarget = element.getEventTargetInfo()
619              if (touchTarget && touchTarget.getId() == "inner") {
620                this.willCancel = true;
621                element.cancelTouch();
622              }
623            }
624          }
625        }
626        return GestureJudgeResult.CONTINUE;
627      })
628      .onTouch((event) => {
629        if (event.type === TouchType.Down) {
630          this.outerState = "TOUCHING";
631        } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
632          this.outerState = "IDLE";
633        }
634      })
635      .parallelGesture( // 绑定一个Pan手势作为动态控制器
636        PanGesture()
637          .onActionUpdate((event: GestureEvent) => {
638            if (this.childRecognizer.getState() != GestureRecognizerState.SUCCESSFUL ||
639              this.currentRecognizer.getState() != GestureRecognizerState.SUCCESSFUL) { // 如果识别器状态不是SUCCESSFUL,则不做控制
640              return;
641            }
642            let target = this.childRecognizer.getEventTargetInfo() as ScrollableTargetInfo;
643            let currentTarget = this.currentRecognizer.getEventTargetInfo() as ScrollableTargetInfo;
644            if (target instanceof ScrollableTargetInfo && currentTarget instanceof ScrollableTargetInfo) {
645              if (target.isEnd()) { // 在移动过程中实时根据当前组件状态,控制识别器的开闭状态
646                if ((event.offsetY - this.lastOffset) < 0) {
647                  this.childRecognizer.setEnabled(false)
648                  if (currentTarget.isEnd()) {
649                    this.currentRecognizer.setEnabled(false)
650                  } else {
651                    this.currentRecognizer.setEnabled(true)
652                  }
653                } else {
654                  this.childRecognizer.setEnabled(true)
655                  this.currentRecognizer.setEnabled(false)
656                }
657              } else if (target.isBegin()) {
658                if ((event.offsetY - this.lastOffset) > 0) {
659                  this.childRecognizer.setEnabled(false)
660                  if (currentTarget.isBegin()) {
661                    this.currentRecognizer.setEnabled(false)
662                  } else {
663                    this.currentRecognizer.setEnabled(true)
664                  }
665                } else {
666                  this.childRecognizer.setEnabled(true)
667                  this.currentRecognizer.setEnabled(false)
668                }
669              } else {
670                this.childRecognizer.setEnabled(true)
671                this.currentRecognizer.setEnabled(false)
672              }
673            }
674            this.lastOffset = event.offsetY
675          })
676      )
677      Column() { // 外层状态显示
678        Text(`outer: ${this.outerState}`)
679          .fontSize(24)
680          .fontColor(this.outerState === "TOUCHING" ? Color.Green : Color.Gray)
681          .margin({ bottom: 10 })
682        // 内层状态显示
683        Text(`inner: ${this.innerState === "TOUCHING" ? "TOUCHING" : this.innerState}`)
684          .fontSize(24)
685          .fontColor(
686            this.innerState === "TOUCHING" ? Color.Blue :
687              this.innerState === "CANCELLED" ? Color.Red : Color.Gray
688          )
689      }
690      .width('90%')
691      .backgroundColor(Color.White)
692      .border({ width: 1, color: Color.Gray })
693      .position({ x: '5%', y: '80%'})
694      .padding(20)
695    }
696    .width('100%')
697    .height('100%')
698    .backgroundColor(0xDCDCDC)
699  }
700}
701```
702![example](figures/canceltouch.gif)
703
704 ### 示例5(自定义手势识别器是否参与手势处理)
705
706该示例通过配置onTouchTestDone指定手势识别器不参与后续手势处理,触发回调时,调用preventBegin()阻止手势识别器参与后续处理。
707
708```ts
709// xxx.ets
710@Entry
711@Component
712struct TouchTestDoneExample {
713  @State tagList: string[] = ['Null', 'Tap1', 'Tap2', 'Tap3', 'Tap4'];
714  @State tagId: number = 0;
715  @State textValue: string = '';
716
717  // 多层嵌套场景,为每一层的组件绑定一个Tap手势
718  build() {
719    Column() {
720      Column() {
721        Text('Tap1')
722          .margin(20)
723        Column() {
724          Text('Tap2')
725            .margin(20)
726          Column() {
727            Text('Tap3')
728              .margin(20)
729            Column() {
730              Text('Tap4')
731                .margin(20)
732            }
733            .backgroundColor('#D5D5D5')
734            .width('80%')
735            .height('80%')
736            .gesture(TapGesture().tag('Tap4').onAction(() => {
737              this.textValue = 'Tap4';
738            }))
739          }
740          .backgroundColor('#F7F7F7')
741          .width('80%')
742          .height('80%')
743          .gesture(TapGesture().tag('Tap3').onAction(() => {
744            this.textValue = 'Tap3';
745          }))
746        }
747        .backgroundColor('#707070')
748        .width('80%')
749        .height('80%')
750        .gesture(TapGesture().tag('Tap2').onAction(() => {
751          this.textValue = 'Tap2';
752        }))
753      }
754      .backgroundColor('#D5D5D5')
755      .width('80%')
756      .height('80%')
757      .gesture(TapGesture().tag('Tap1').onAction(() => {
758        this.textValue = 'Tap1';
759      }))
760      // 绑定onTouchTestDone,通过调用手势识别器的preventBegin()方法来自定义手势识别器是否参与后续手势处理
761      .onTouchTestDone((event, recognizers) => {
762        console.info('event is ' + JSON.stringify(event));
763        for (let i = 0; i < recognizers.length; i++) {
764          let recognizer = recognizers[i];
765          console.info('type is ' + JSON.stringify(recognizer.getType()))
766          // 根据tag的值屏蔽不同的手势识别器
767          if (recognizer.getTag() == this.tagList[this.tagId]) {
768            recognizer.preventBegin();
769          }
770        }
771      })
772
773      Text('Current Gesture: ' + this.textValue)
774        .margin(5)
775
776      Button('Click to change preventGesture')
777        .margin(5)
778        .onClick(() => {
779          this.tagId++;
780          this.tagId %= 5;
781        })
782      Text('Current prevent gesture tag: ' + this.tagList[this.tagId])
783        .margin(5)
784
785    }
786    .width('100%')
787    .height('100%')
788
789    // 示例gif中,点击Tap2和Tap1的重合区域,不调用preventBegin时,触发的为Tap2手势;调用preventBegin阻止Tap2时,触发的为Tap1手势
790  }
791}
792```
793![example](figures/touchTestDone.gif)
794