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 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  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  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 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 794