1# Gesture Conflict Handling 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @jiangtao92--> 5<!--Designer: @piggyguy--> 6<!--Tester: @songyanhong--> 7<!--Adviser: @HelloCrease--> 8 9Gesture conflicts occur when multiple gesture recognizers compete for recognition on the same component or overlapping areas, resulting in unexpected behavior. Common conflict scenarios include: 10- Multiple gestures on the same component (for example, both tap and long-press gestures on a button) 11- Gesture recognizers of the same type on parent and child components 12- Conflicts between system default gestures and custom gestures (for example, conflict between the scroll gesture and the click gesture of a child component) 13 14Effective conflict resolution involves gesture intervention. Beyond controlling component response regions and hit test modes, there are three primary approaches: [Custom Gesture Judgment](#custom-gesture-judgment), [Parallel Gesture Dynamic Control](#parallel-gesture-dynamic-control), and [Gesture Recognition Prevention](#gesture-recognition-prevention). 15 16## Custom Gesture Judgment 17 18Custom gesture judgment enables applications to override system recognition decisions. When system thresholds are met, the application can determine whether to intercept the gesture (causing its recognition to fail) and prioritize other gestures instead. 19 20**Figure 1** Custom gesture judgment process 21 22 23 24Custom gesture judgment involves the following APIs. 25 26| API| Description| 27| ------- | -------------- | 28|[onGestureJudgeBegin](../reference/apis-arkui/arkui-ts/ts-gesture-customize-judge.md#ongesturejudgebegin)|Used for gesture interception as a universal event. Called when the gesture meets system trigger thresholds, allowing the application to determine whether to intercept the gesture.| 29|[onGestureRecognizerJudgeBegin](../reference/apis-arkui/arkui-ts/ts-gesture-blocking-enhancement.md#ongesturerecognizerjudgebegin)|Called to implement gesture judgment, obtain gesture recognizers, and set their enabled state. It is an extension of **onGestureJudgeBegin** and can serve as its substitute.<br>When obtaining gesture recognizers, this API obtains all gesture recognizers in the response chain of the current interaction, as well as the recognizer about to be triggered successfully, allowing the enabled state of the gesture to be set.| 30 31In the following example, the **Image** and **Stack** components are located in the same area. Long-pressing the upper half of the **Stack** component triggers the long-press gesture bound to the **Stack** component, while long-pressing the lower half of the **Stack** component triggers the drag operation of the **Image** component. 32 33**Figure 2** Example 34 35 36 371. Configure drag settings for the **Image** component. 38 39 ```ts 40 Image($r('sys.media.ohos_app_icon')) 41 .draggable(true) 42 .onDragStart(()=>{ 43 promptAction.showToast({ message: "Drag the lower blue area. The Image component responds." }); 44 }) 45 .width('200vp').height('200vp') 46 ``` 47 482. Set up gestures for the **Stack** component. 49 50 ```ts 51 Stack() {} 52 .width('200vp') 53 .height('200vp') 54 .hitTestBehavior(HitTestMode.Transparent) 55 .gesture(GestureGroup(GestureMode.Parallel, 56 LongPressGesture() 57 .onAction((event: GestureEvent) => { 58 promptAction.showToast({ message: "Long-press the upper red area. The red area responds." }); 59 }) 60 .tag("longpress") 61 )) 62 ``` 63 643. Set up gesture judgment for the **Stack** component. 65 66 ```ts 67 .onGestureJudgeBegin((gestureInfo: GestureInfo, event: BaseGestureEvent) => { 68 // If it is a long press gesture, determine whether the touch position is in the upper half area. 69 if (gestureInfo.type == GestureControl.GestureType.LONG_PRESS_GESTURE) { 70 if (event.fingerList.length > 0 && event.fingerList[0].localY < 100) { 71 return GestureJudgeResult.CONTINUE; 72 } else { 73 return GestureJudgeResult.REJECT; 74 } 75 } 76 return GestureJudgeResult.CONTINUE; 77 }) 78 ``` 79 804. Below is the complete code example. 81 82 ```ts 83 import { PromptAction } from '@kit.ArkUI'; 84 85 @Entry 86 @Component 87 struct Index { 88 scroller: Scroller = new Scroller(); 89 promptAction: PromptAction = this.getUIContext().getPromptAction(); 90 91 build() { 92 Scroll(this.scroller) { 93 Column({ space: 8 }) { 94 Text("There are two layers of components, the upper layer component bound to a long press gesture, and the lower layer component bound to a drag. The lower half of the upper layer component is bound to gesture judgment, making this area respond to the drag gesture of the lower layer.").width('100%').fontSize(20).fontColor('0xffdd00') 95 Stack({ alignContent: Alignment.Center }) { 96 Column() { 97 // Simulate the upper and lower half areas. 98 Stack().width('200vp').height('100vp').backgroundColor(Color.Red) 99 Stack().width('200vp').height('100vp').backgroundColor(Color.Blue) 100 }.width('200vp').height('200vp') 101 // The lower half of the Stack component is the image area bound to the drag gesture. 102 Image($r('sys.media.ohos_app_icon')) 103 .draggable(true) 104 .onDragStart(()=>{ 105 this.promptAction.showToast({ message: "Drag the lower blue area. The Image component responds." }); 106 }) 107 .width('200vp').height('200vp') 108 // The upper half of the Stack component is the floating area bound to the long press gesture. 109 Stack() { 110 } 111 .width('200vp') 112 .height('200vp') 113 .hitTestBehavior(HitTestMode.Transparent) 114 .gesture(GestureGroup(GestureMode.Parallel, 115 LongPressGesture() 116 .onAction((event: GestureEvent) => { 117 this.promptAction.showToast({ message: "Long-press the upper red area. The red area responds." }); 118 }) 119 .tag("longpress") 120 )) 121 .onGestureJudgeBegin((gestureInfo: GestureInfo, event: BaseGestureEvent) => { 122 // If it is a long press gesture, determine whether the touch position is in the upper half area. 123 if (gestureInfo.type == GestureControl.GestureType.LONG_PRESS_GESTURE) { 124 if (event.fingerList.length > 0 && event.fingerList[0].localY < 100) { 125 return GestureJudgeResult.CONTINUE; 126 } else { 127 return GestureJudgeResult.REJECT; 128 } 129 } 130 return GestureJudgeResult.CONTINUE; 131 }) 132 }.width('100%') 133 }.width('100%') 134 } 135 } 136 } 137 ``` 138 139## Parallel Gesture Dynamic Control 140 141Parallel gesture dynamic control allows you to manage whether a gesture callback should be executed, even after the gesture has been successfully recognized. 142 143**Figure 3** Parallel gesture dynamic control process 144 145 146Parallel gesture dynamic control is based on the successful recognition of a gesture. If the gesture is not recognized, no callback response will be triggered. 147 1481. Service gesture workflow: This refers to gestures that directly cause changes in the UI, such as **PanGesture** for scrolling pages or **TapGesture** for clicks. 149 1502. Gesture listening workflow: This involves dynamically controlling the start and stop of gesture recognizers based on the current service state. For example, during nested scrolling, the listener can determine whether the component has reached the edge. This can be achieved using **PanGesture** with the [parallel gesture binding method](arkts-gesture-events-binding.md#parallelgesture-parallel-gesture-binding-method) or by using touch events. 151 1523. Parallel gesture configuration: This step is optional. A typical use case is to set the scroll gesture of an outer component to be parallel with the scroll gesture of an inner component during nested scrolling. 153 1544. Dynamic gesture control: This involves controlling whether gestures respond to user callbacks by using the **setEnable** API of gesture recognizers. 155 156Parallel gesture dynamic control involves the following APIs. 157 158| API| Description| 159| ------- | -------------- | 160|[shouldBuiltInRecognizerParallelWith](../reference/apis-arkui/arkui-ts/ts-gesture-blocking-enhancement.md#shouldbuiltinrecognizerparallelwith)|Used to set the built-in gestures to be parallel with other gestures.| 161|[onGestureRecognizerJudgeBegin](../reference/apis-arkui/arkui-ts/ts-gesture-blocking-enhancement.md#ongesturerecognizerjudgebegin)|Called to implement gesture judgment, obtain gesture recognizers, and initialize and their enabled state.| 162|[parallelGesture](arkts-gesture-events-binding.md#parallelgesture-parallel-gesture-binding-method)|Allows custom gestures to be parallel with gestures of higher priority.| 163 164The following example demonstrates a nested scrolling scenario with two **Scroll** components, using gesture control APIs to manage the nested scrolling linkage between the outer and inner components. 165 1661. Use the **shouldBuiltInRecognizerParallelWith** API to set the **PanGesture** of the outer **Scroll** component to be parallel with the **PanGesture** of the inner **Scroll** component. 167 168 ```ts 169 .shouldBuiltInRecognizerParallelWith((current: GestureRecognizer, others: Array<GestureRecognizer>) => { 170 for (let i = 0; i < others.length; i++) { 171 let target = others[i].getEventTargetInfo(); 172 if (target.getId() == "inner" && others[i].isBuiltIn() && others[i].getType() == GestureControl.GestureType.PAN_GESTURE) { // Identify the recognizer to work in parallel. 173 this.currentRecognizer = current; // Save the recognizer of the current component. 174 this.childRecognizer = others[i]; // Save the recognizer to work in parallel. 175 return others[i]; // Return the recognizer to work in parallel with the current one. 176 } 177 } 178 return undefined; 179 }) 180 ``` 181 1822. Use the **onGestureRecognizerJudgeBegin** API to obtain the pan gesture recognizer of the **Scroll** component and to set the enabled state of the inner and outer gesture recognizers based on the boundary conditions of the **Scroll** components. 183 184 ```ts 185 .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer, others: Array<GestureRecognizer>) => { // When gesture recognition is about to be successful, set the recognizer's enabled state based on the current component state. 186 let target = current.getEventTargetInfo(); 187 if (target && target.getId() == "outer" && current.isBuiltIn() && current.getType() == GestureControl.GestureType.PAN_GESTURE) { 188 for (let i = 0; i < others.length; i++) { 189 let target = others[i].getEventTargetInfo() as ScrollableTargetInfo; 190 if (target instanceof ScrollableTargetInfo && target.getId() == "inner") { // Identify the recognizer to work in parallel on the response chain. 191 let panEvent = event as PanGestureEvent; 192 this.childRecognizer.setEnabled(true); 193 this.currentRecognizer.setEnabled(false); 194 if (target.isEnd()) { // Dynamically control the recognizer's enabled state based on the current component state and direction of movement. 195 if (panEvent && panEvent.offsetY < 0) { 196 this.childRecognizer.setEnabled(false); 197 this.currentRecognizer.setEnabled(true); 198 } 199 } else if (target.isBegin()) { 200 if (panEvent.offsetY > 0) { 201 this.childRecognizer.setEnabled(false); 202 this.currentRecognizer.setEnabled(true); 203 } 204 } 205 } 206 } 207 } 208 return GestureJudgeResult.CONTINUE; 209 }) 210 ``` 211 2123. Set up a gesture listener to listen for the state changes of the **Scroll** component, dynamically adjust the enabled state of the gesture recognizers, and controls whether gesture callbacks are triggered, allowing you to manage scrolling. 213 214 ```ts 215 .parallelGesture ( // Bind a PanGesture as a dynamic controller. 216 PanGesture() 217 .onActionUpdate((event: GestureEvent)=>{ 218 if (this.childRecognizer.getState() != GestureRecognizerState.SUCCESSFUL || this.currentRecognizer.getState() != GestureRecognizerState.SUCCESSFUL) { // If neither recognizer is in the SUCCESSFUL state, no control is applied. 219 return; 220 } 221 let target = this.childRecognizer.getEventTargetInfo() as ScrollableTargetInfo; 222 let currentTarget = this.currentRecognizer.getEventTargetInfo() as ScrollableTargetInfo; 223 if (target instanceof ScrollableTargetInfo && currentTarget instanceof ScrollableTargetInfo) { 224 this.childRecognizer.setEnabled(true); 225 this.currentRecognizer.setEnabled(false); 226 if (target.isEnd()) { // Adjust the enabled state of the gesture recognizers based on the current component state during movement. 227 if ((event.offsetY - this.lastOffset) < 0) { 228 this.childRecognizer.setEnabled(false); 229 if (currentTarget.isEnd()) { 230 this.currentRecognizer.setEnabled(false); 231 } else { 232 this.currentRecognizer.setEnabled(true); 233 } 234 } 235 } else if (target.isBegin()) { 236 if ((event.offsetY - this.lastOffset) > 0) { 237 this.childRecognizer.setEnabled(false); 238 if (currentTarget.isBegin()) { 239 this.currentRecognizer.setEnabled(false); 240 } else { 241 this.currentRecognizer.setEnabled(true); 242 } 243 } 244 } 245 } 246 this.lastOffset = event.offsetY 247 }) 248 ) 249 ``` 250 2514. Below is the complete code example. 252 253 ```ts 254 // xxx.ets 255 @Entry 256 @Component 257 struct FatherControlChild { 258 scroller: Scroller = new Scroller(); 259 scroller2: Scroller = new Scroller(); 260 private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 261 private childRecognizer: GestureRecognizer = new GestureRecognizer(); 262 private currentRecognizer: GestureRecognizer = new GestureRecognizer(); 263 private lastOffset: number = 0; 264 265 build() { 266 Stack({ alignContent: Alignment.TopStart }) { 267 Scroll(this.scroller) { // Outer scroll container. 268 Column() { 269 Text("Scroll Area") 270 .width('90%') 271 .height(150) 272 .backgroundColor(0xFFFFFF) 273 .borderRadius(15) 274 .fontSize(16) 275 .textAlign(TextAlign.Center) 276 .margin({ top: 10 }) 277 Scroll(this.scroller2) { // Inner scroll container. 278 Column() { 279 Text("Scroll Area2") 280 .width('90%') 281 .height(150) 282 .backgroundColor(0xFFFFFF) 283 .borderRadius(15) 284 .fontSize(16) 285 .textAlign(TextAlign.Center) 286 .margin({ top: 10 }) 287 Column() { 288 ForEach(this.arr, (item: number) => { 289 Text(item.toString()) 290 .width('90%') 291 .height(150) 292 .backgroundColor(0xFFFFFF) 293 .borderRadius(15) 294 .fontSize(16) 295 .textAlign(TextAlign.Center) 296 .margin({ top: 10 }) 297 }, (item: string) => item) 298 }.width('100%') 299 } 300 } 301 .id("inner") 302 .width('100%') 303 .height(800) 304 }.width('100%') 305 } 306 .id("outer") 307 .height(600) 308 .scrollable(ScrollDirection.Vertical) // The scrollbar scrolls in the vertical direction. 309 .scrollBar(BarState.On) // The scrollbar is always displayed. 310 .scrollBarColor(Color.Gray) // The scrollbar color is gray. 311 .scrollBarWidth(10) // The scrollbar width is 10. 312 .edgeEffect(EdgeEffect.None) 313 .shouldBuiltInRecognizerParallelWith((current: GestureRecognizer, others: Array<GestureRecognizer>) => { 314 for (let i = 0; i < others.length; i++) { 315 let target = others[i].getEventTargetInfo(); 316 if (target.getId() == "inner" && others[i].isBuiltIn() && 317 others[i].getType() == GestureControl.GestureType.PAN_GESTURE) { // Identify the recognizer that to be bound to parallelGesture. 318 this.currentRecognizer = current; // Save the recognizer of the current component. 319 this.childRecognizer = others[i]; // Save the recognizer to work in parallel. 320 return others[i]; // Return the recognizer to work in parallel with the current one. 321 } 322 } 323 return undefined; 324 }) 325 .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer, 326 others: Array<GestureRecognizer>) => { // When the implementation is about to succeed, set the recognizer enabling state based on the current component state. 327 let target = current.getEventTargetInfo(); 328 if (target && target.getId() == "outer" && current.isBuiltIn() && 329 current.getType() == GestureControl.GestureType.PAN_GESTURE) { 330 for (let i = 0; i < others.length; i++) { 331 let target = others[i].getEventTargetInfo() as ScrollableTargetInfo; 332 if (target instanceof ScrollableTargetInfo && target.getId() == "inner") { // Identify the recognizer to work in parallel on the response chain. 333 let panEvent = event as PanGestureEvent; 334 this.childRecognizer.setEnabled(true); 335 this.currentRecognizer.setEnabled(false); 336 if (target.isEnd()) { // Dynamically control the recognizer's enabled state based on the current component state and direction of movement. 337 if (panEvent && panEvent.offsetY < 0) { 338 this.childRecognizer.setEnabled(false); 339 this.currentRecognizer.setEnabled(true); 340 } 341 } else if (target.isBegin()) { 342 if (panEvent.offsetY > 0) { 343 this.childRecognizer.setEnabled(false); 344 this.currentRecognizer.setEnabled(true); 345 } 346 } 347 } 348 } 349 } 350 return GestureJudgeResult.CONTINUE; 351 }) 352 .parallelGesture ( // Bind a PanGesture as a dynamic controller. 353 PanGesture() 354 .onActionUpdate((event: GestureEvent) => { 355 if (this.childRecognizer?.getState() != GestureRecognizerState.SUCCESSFUL || 356 this.currentRecognizer?.getState() != GestureRecognizerState.SUCCESSFUL) { // If the recognizer is not in the SUCCESSFUL state, no control is applied. 357 return; 358 } 359 let target = this.childRecognizer.getEventTargetInfo() as ScrollableTargetInfo; 360 let currentTarget = this.currentRecognizer.getEventTargetInfo() as ScrollableTargetInfo; 361 if (target instanceof ScrollableTargetInfo && currentTarget instanceof ScrollableTargetInfo) { 362 this.childRecognizer.setEnabled(true); 363 this.currentRecognizer.setEnabled(false); 364 if (target.isEnd()) { // Adjust the enabled state of the gesture recognizers based on the current component state during movement. 365 if ((event.offsetY - this.lastOffset) < 0) { 366 this.childRecognizer.setEnabled(false); 367 if (currentTarget.isEnd()) { 368 this.currentRecognizer.setEnabled(false); 369 } else { 370 this.currentRecognizer.setEnabled(true); 371 } 372 } 373 } else if (target.isBegin()) { 374 if ((event.offsetY - this.lastOffset) > 0) { 375 this.childRecognizer.setEnabled(false) 376 if (currentTarget.isBegin()) { 377 this.currentRecognizer.setEnabled(false); 378 } else { 379 this.currentRecognizer.setEnabled(true); 380 } 381 } 382 } 383 } 384 this.lastOffset = event.offsetY; 385 }) 386 ) 387 }.width('100%').height('100%').backgroundColor(0xDCDCDC) 388 } 389 } 390 ``` 391 392## Gesture Recognition Prevention 393 394Gesture recognition is based on the response chain results of [hit testing](./arkts-interaction-basic-principles.md#hit-testing). Therefore, it is efficient to dynamically intervene in gesture processing by controlling the participation status of gesture recognizers in the response chain when the user presses down. 395 396This is achieved using the [onTouchTestDone](../reference/apis-arkui/arkui-ts/ts-gesture-blocking-enhancement.md#ontouchtestdone20) API: 397 398After hit testing is completed, the system returns all gesture recognizer objects through this API. Applications can filter recognizers by type, component ID, or associated component, and proactively disable specific recognizers by calling the [preventBegin](../reference/apis-arkui/arkui-ts/ts-gesture-blocking-enhancement.md#preventbegin20) API. 399 400Disabling by gesture type: 401 402```typescript 403 .onTouchTestDone((event, recognizers) => { 404 for (let i = 0; i < recognizers.length; i++) { 405 let recognizer = recognizers[i]; 406 // Disable all pan gestures based on type. 407 if (recognizer.getType() == GestureControl.GestureType.PAN_GESTURE) { 408 recognizer.preventBegin(); 409 } 410 } 411 }) 412``` 413 414Disabling by associated component: 415 416Components must be pre-configured with a component ID through the universal attribute [id](../reference/apis-arkui/arkui-ts/ts-universal-attributes-component-id.md#id). 417 418```typescript 419 .onTouchTestDone((event, recognizers) => { 420 for (let i = 0; i < recognizers.length; i++) { 421 let recognizer = recognizers[i]; 422 // Disable all gestures on the component with ID "myID." 423 if (recognizer.getEventTargetInfo().getId() == "myID") { 424 recognizer.preventBegin(); 425 } 426 } 427 }) 428``` 429 430Disabling system built-in gestures: 431 432```typescript 433 .onTouchTestDone((event, recognizers) => { 434 for (let i = 0; i < recognizers.length; i++) { 435 let recognizer = recognizers[i]; 436 // Disable all system built-in gestures. 437 if (recognizer.isBuiltIn()) { 438 recognizer.preventBegin(); 439 } 440 } 441 }) 442``` 443 444Combine the preceding conditions based on specific scenarios. 445 446> **NOTE** 447> 448> The system executes **onTouchTestDone** callbacks on nodes from innermost to outermost. 449 450In the NDK, the corresponding APIs for **onTouchTestDone** and **preventBegin** are **OH_ArkUI_SetTouchTestDoneCallback** and **OH_ArkUI_PreventGestureRecognizerBegin**, respectively. Their usage and functionality are consistent with the ArkTS APIs. 451 452The following example illustrates a simplified video player UI interaction. 453 454The parent container (**video_layer**) has multiple bound gestures: 455- Tap: controls the playback (pause/play). 456- Double-tap: switches between full-screen and non-full-screen modes. 457- Long press: fast-forwards. 458- Vertical swipe: adjusts brightness. 459- Horizontal swipe: seeks playback. 460 461The inner **Slider** component (**progress_layer**) does not have a long-press gesture bound to it. This causes the parent container's fast-forward gesture to be triggered when the user long-presses the **Slider** component, which is unexpected behavior. 462 463Solution: Register an **onTouchTestDone** callback on the **Slider** component. Use this callback to disable gesture recognizers not belonging to the **Slider** component, thereby resolving the conflict. 464 465The following shows the complete sample code: 466 467```typescript 468@Entry 469@ComponentV2 470struct Index { 471 @Local progress: number = 496000 // Initial progress, in seconds. 472 @Local total: number = 27490000 // Total duration, in seconds. 473 @Local currentWidth: string = '100%' 474 @Local currentHeight: string = '100%' 475 private currentPosX: number = 0 476 private currentPosY: number = 0 477 private currentFullScreenState: boolean = true 478 private normalPlayTimer: number = -1; 479 private isPlaying: boolean = true; 480 private fastForwardTimer: number = -1; 481 482 aboutToAppear(): void { 483 // Start a periodic timer to refresh the progress every second. 484 this.startNormalPlayTimer() 485 } 486 487 startNormalPlayTimer(): void { 488 if (this.normalPlayTimer != -1) { 489 this.stopNormalPlayTimer() 490 } 491 this.normalPlayTimer = setInterval(() => { 492 this.progress = this.progress + 1000 493 }, 1000) 494 } 495 496 stopNormalPlayTimer(): void { 497 if (this.normalPlayTimer == -1) { 498 return 499 } 500 clearInterval(this.normalPlayTimer) 501 this.normalPlayTimer = -1 502 } 503 504 startFastForwardTimer(): void { 505 if (this.fastForwardTimer != -1) { 506 this.stopFastForwardTimer() 507 } 508 this.fastForwardTimer = setInterval(() => { 509 this.progress = this.progress + 100000 510 }, 100) 511 } 512 513 stopFastForwardTimer(): void { 514 if (this.fastForwardTimer == -1) { 515 return 516 } 517 clearInterval(this.fastForwardTimer) 518 this.fastForwardTimer = -1 519 } 520 521 showMessage(message: string): void { 522 this.getUIContext().getPromptAction().showToast({ message: message, alignment: Alignment.Center }) 523 } 524 525 resetPosInfo(): void { 526 this.currentPosX = 0 527 this.currentPosY = 0 528 } 529 530 toggleFullScreenState(): void { 531 this.currentFullScreenState = !this.currentFullScreenState 532 if (this.currentFullScreenState) { 533 this.currentWidth = '100%' 534 this.currentHeight = '100%' 535 } else { 536 this.currentWidth = '100%' 537 this.currentHeight = '50%' 538 } 539 this.showMessage(this.currentFullScreenState ? 'Full-screen playback' : 'Exit full-screen') 540 } 541 542 togglePlayAndPause(): void { 543 this.isPlaying = !this.isPlaying 544 if (!this.isPlaying) { 545 this.stopNormalPlayTimer() 546 } else { 547 // Restart the timer. 548 this.startNormalPlayTimer() 549 } 550 this.showMessage(this.isPlaying ? 'Pause playback' : 'Resume playback') 551 } 552 553 doFastForward(start: boolean): void { 554 if (!start) { // Stop fast-forwarding and resume normal playback. 555 this.stopFastForwardTimer() 556 this.startNormalPlayTimer() 557 this.showMessage('Cancel fast-forwarding') 558 return 559 } 560 561 this.stopNormalPlayTimer() 562 this.startFastForwardTimer() 563 this.showMessage('Start fast-forwarding') 564 } 565 566 updateBrightness(start: boolean, event: BaseGestureEvent): void { 567 let newY = event.fingerList[0].localY 568 if (start) { 569 this.currentPosY = newY 570 this.showMessage('Start adjusting brightness') 571 return 572 } 573 let offsetY = newY - this.currentPosY; 574 if (offsetY > 10) { 575 this.showMessage((offsetY > 0) ? 'Decrease brightness' : 'Increase brightness') 576 } 577 this.currentPosY = newY 578 } 579 580 updateProgress(start: boolean, event: BaseGestureEvent): void { 581 let newX = event.fingerList[0].localX 582 if (start) { 583 this.currentPosX = newX 584 this.showMessage('Start adjusting progress') 585 return 586 } 587 let offsetX = newX - this.currentPosX; 588 this.progress = Math.floor(this.progress + offsetX * 10000) 589 this.currentPosX = newX 590 } 591 592 build() { 593 Stack({ alignContent: Alignment.Center }) { 594 Column() { 595 Column() { 596 Text("Playback progress: " + this.progress) 597 } 598 .width("100%").height("90%") 599 Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 600 Slider({ 601 value: this.progress, 602 min: 0, 603 max: this.total, 604 style: SliderStyle.OutSet 605 }) 606 .onChange((value: number, mode: SliderChangeMode) => { 607 this.progress = value 608 }) 609 .id("progress_layer") 610 .onTouchTestDone((event, allRecognizers: Array<GestureRecognizer>) => { 611 for (let i = 0; i < allRecognizers.length; i++) { 612 let recognizer = allRecognizers[i]; 613 let inspectorInfo = recognizer.getEventTargetInfo().getId(); 614 if (inspectorInfo !== "progress_layer") { 615 // When the user interacts with the progress bar area, disable all gestures not belonging to progress_layer. 616 recognizer.preventBegin(); 617 } 618 } 619 }) 620 .margin({ left: 5 }) 621 .trackColor(Color.Red) 622 .blockColor(Color.Yellow) 623 .selectedColor(Color.Orange) 624 .trackThickness(2) 625 .flexShrink(1) 626 .flexGrow(1) 627 } 628 .flexGrow(1) 629 .flexShrink(1) 630 .id('id_progress_view') 631 } 632 } 633 .id('video_layer') 634 .backgroundColor('#E0E0E0') 635 .gesture( 636 GestureGroup(GestureMode.Exclusive, 637 PanGesture({ direction: PanDirection.Vertical, distance: 10 }) 638 .tag('pan_for_brightness_control') 639 .onActionStart((event) => { 640 this.updateBrightness(true, event) 641 }) 642 .onActionUpdate((event) => { 643 this.updateBrightness(false, event) 644 }), 645 PanGesture({ direction: PanDirection.Horizontal, distance: 10 }) 646 .tag('pan_for_play_progress_control') 647 .onActionStart((event) => { 648 this.updateProgress(true, event) 649 }) 650 .onActionUpdate((event) => { 651 this.updateProgress(false, event) 652 }), 653 654 LongPressGesture() 655 .tag('long_press_for_fast_forward_control') 656 .onAction(() => { 657 this.doFastForward(true) // Start fast-forwarding. 658 }) 659 .onActionEnd(() => { 660 this.doFastForward(false) // Stop fast-forwarding. 661 }) 662 .onActionCancel(() => { 663 this.doFastForward(false) 664 }), 665 666 TapGesture({ count: 2 }) 667 .tag('double_tap_on_video') 668 .onAction(() => { 669 this.toggleFullScreenState() 670 }), 671 672 TapGesture() 673 .tag('single_tap_on_video') 674 .onAction(() => { 675 this.togglePlayAndPause() 676 }) 677 ) 678 ) 679 .width(this.currentWidth) 680 .height(this.currentHeight) 681 } 682} 683``` 684<!--no_check-->