1# Gesture Judgment 2 3Gesture judgment is primarily used to ensure that gestures are executed as needed, effectively resolving gesture conflict issues. Typical use cases include nested scrolling and optimizing the interaction experience by filtering the range of components that respond to gestures. Gesture judgment is mainly implemented through two methods: [gesture triggering control](#gesture-triggering-control) and [gesture response control](#gesture-response-control). 4 5## Gesture Triggering Control 6 7Gesture triggering control refers to the process where, under conditions where the gesture recognition threshold is met, the application can independently determine whether to proceed with the gesture, causing the gesture operation to fail if necessary. 8 9**Figure 1** Flowchart of gesture triggering control 10 11 12 13The following APIs are involved in gesture triggering control. 14 15| API| Description| 16| ------- | -------------- | 17|[onGestureJudgeBegin](../reference/apis-arkui/arkui-ts/ts-gesture-customize-judge.md#ongesturejudgebegin)|Triggered when the gesture meets the recognition threshold to allow the application to decide whether to continue with the gesture. It is a universal event.| 18|[onGestureRecognizerJudgeBegin](../reference/apis-arkui/arkui-ts/ts-gesture-blocking-enhancement.md#ongesturerecognizerjudgebegin)|Triggered to implement gesture judgment, obtain gesture recognizers, and initialize and their enabled state. It is an extension of **onGestureJudgeBegin** and can serve as its substitute.<br>This API obtains all recognizers involved in the gesture response chain during a single interaction, including the current recognizer about to be triggered, and initializes the gesture's active state.| 19 20In 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. 21 22**Figure 2** Example 23 24 25 261. Configure drag settings for the **Image** component. 27 28 ```ts 29 Image($r('sys.media.ohos_app_icon')) 30 .draggable(true) 31 .onDragStart(()=>{ 32 promptAction.showToast({ message: ""Drag the lower blue area. The Image component responds." }); 33 }) 34 .width('200vp').height('200vp') 35 ``` 36 372. Set up gestures for the **Stack** component. 38 39 ```ts 40 Stack() {} 41 .width('200vp') 42 .height('200vp') 43 .hitTestBehavior(HitTestMode.Transparent) 44 .gesture(GestureGroup(GestureMode.Parallel, 45 LongPressGesture() 46 .onAction((event: GestureEvent) => { 47 promptAction.showToast({ message: "Long-press the upper red area. The red area responds." }); 48 }) 49 .tag("longpress") 50 )) 51 ``` 52 533. Set up gesture judgment for the **Stack** component. 54 55 ```ts 56 .onGestureJudgeBegin((gestureInfo: GestureInfo, event: BaseGestureEvent) => { 57 // If it is a long press gesture, determine whether the touch position is in the upper half area. 58 if (gestureInfo.type == GestureControl.GestureType.LONG_PRESS_GESTURE) { 59 if (event.fingerList.length > 0 && event.fingerList[0].localY < 100) { 60 return GestureJudgeResult.CONTINUE; 61 } else { 62 return GestureJudgeResult.REJECT; 63 } 64 } 65 return GestureJudgeResult.CONTINUE; 66 }) 67 ``` 68 694. Below is the complete code example. 70 71 ```ts 72 import { promptAction } from '@kit.ArkUI'; 73 74 @Entry 75 @Component 76 struct Index { 77 scroller: Scroller = new Scroller(); 78 79 build() { 80 Scroll(this.scroller) { 81 Column({ space: 8 }) { 82 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') 83 Stack({ alignContent: Alignment.Center }) { 84 Column() { 85 // Simulate the upper and lower half areas. 86 Stack().width('200vp').height('100vp').backgroundColor(Color.Red) 87 Stack().width('200vp').height('100vp').backgroundColor(Color.Blue) 88 }.width('200vp').height('200vp') 89 // The lower half of the Stack component is the image area bound to the drag gesture. 90 Image($r('sys.media.ohos_app_icon')) 91 .draggable(true) 92 .onDragStart(()=>{ 93 promptAction.showToast({ message: ""Drag the lower blue area. The Image component responds." }); 94 }) 95 .width('200vp').height('200vp') 96 // The upper half of the Stack component is the floating area bound to the long press gesture. 97 Stack() { 98 } 99 .width('200vp') 100 .height('200vp') 101 .hitTestBehavior(HitTestMode.Transparent) 102 .gesture(GestureGroup(GestureMode.Parallel, 103 LongPressGesture() 104 .onAction((event: GestureEvent) => { 105 promptAction.showToast({ message: "Long-press the upper red area. The red area responds." }); 106 }) 107 .tag("longpress") 108 )) 109 .onGestureJudgeBegin((gestureInfo: GestureInfo, event: BaseGestureEvent) => { 110 // If it is a long press gesture, determine whether the touch position is in the upper half area. 111 if (gestureInfo.type == GestureControl.GestureType.LONG_PRESS_GESTURE) { 112 if (event.fingerList.length > 0 && event.fingerList[0].localY < 100) { 113 return GestureJudgeResult.CONTINUE; 114 } else { 115 return GestureJudgeResult.REJECT; 116 } 117 } 118 return GestureJudgeResult.CONTINUE; 119 }) 120 }.width('100%') 121 }.width('100%') 122 } 123 } 124 } 125 ``` 126 127## Gesture Response Control 128 129Gesture response control allows you to manage whether a gesture callback should be executed, even after the gesture has been successfully recognized. 130 131**Figure 3** Flowchart of gesture response control 132 133 134Gesture response control is based on the successful recognition of a gesture. If the gesture is not recognized, it will not trigger a callback response. 135 1361. Service gesture workflow: This refers to gestures that directly cause changes in the UI, such as **PanGesture** for scrolling pages or **TapGesture** for clicks. 137 1382. 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. 139 1403. 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. 141 1424. Dynamic gesture control: This involves controlling whether gestures respond to user callbacks by using the **setEnable** API of gesture recognizers. 143 144The following APIs are involved in gesture response control. 145 146| API| Description| 147| ------- | -------------- | 148|[shouldBuiltInRecognizerParallelWith](../reference/apis-arkui/arkui-ts/ts-gesture-blocking-enhancement.md#shouldbuiltinrecognizerparallelwith)|Used to set the system's built-in gestures to be parallel with other gestures.| 149|[onGestureRecognizerJudgeBegin](../reference/apis-arkui/arkui-ts/ts-gesture-blocking-enhancement.md#ongesturerecognizerjudgebegin)|Triggered to implement gesture judgment, obtain gesture recognizers, and initialize and their enabled state.| 150|[parallelGesture](arkts-gesture-events-binding.md#parallelgesture-parallel-gesture-binding-method)|Allows custom gestures to be parallel with gestures of higher priority.| 151 152The 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. 153 1541. Use the **shouldBuiltInRecognizerParallelWith** API to set the **PanGesture** of the outer **Scroll** component to be parallel with the **PanGesture** of the inner **Scroll** component. 155 156 ```ts 157 .shouldBuiltInRecognizerParallelWith((current: GestureRecognizer, others: Array<GestureRecognizer>) => { 158 for (let i = 0; i < others.length; i++) { 159 let target = others[i].getEventTargetInfo(); 160 if (target.getId() == "inner" && others[i].isBuiltIn() && others[i].getType() == GestureControl.GestureType.PAN_GESTURE) { // Identify the recognizer to work in parallel. 161 this.currentRecognizer = current; // Save the recognizer of the current component. 162 this.childRecognizer = others[i]; // Save the recognizer to work in parallel. 163 return others[i]; // Return the recognizer to work in parallel with the current one. 164 } 165 } 166 return undefined; 167 }) 168 ``` 169 1702. Use the **onGestureRecognizerJudgeBegin** API to obtain the PanGesture recognizer of the **Scroll** component and to initialize the enabled state of the inner and outer gesture recognizers based on the boundary conditions of the **Scroll** components. 171 172 ```ts 173 .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. 174 let target = current.getEventTargetInfo(); 175 if (target.getId() == "outer" && current.isBuiltIn() && current.getType() == GestureControl.GestureType.PAN_GESTURE) { 176 for (let i = 0; i < others.length; i++) { 177 let target = others[i].getEventTargetInfo() as ScrollableTargetInfo; 178 if (target instanceof ScrollableTargetInfo && target.getId() == "inner") { // Identify the recognizer to work in parallel on the response chain. 179 let panEvent = event as PanGestureEvent; 180 this.childRecognizer.setEnabled(true); 181 this.currentRecognizer.setEnabled(false); 182 if (target.isEnd()) { // Dynamically control the recognizer's enabled state based on the current component state and direction of movement. 183 if (panEvent && panEvent.offsetY < 0) { 184 this.childRecognizer.setEnabled(false); 185 this.currentRecognizer.setEnabled(true); 186 } 187 } else if (target.isBegin()) { 188 if (panEvent.offsetY > 0) { 189 this.childRecognizer.setEnabled(false); 190 this.currentRecognizer.setEnabled(true); 191 } 192 } 193 } 194 } 195 } 196 return GestureJudgeResult.CONTINUE; 197 }) 198 ``` 199 2003. Set up a gesture listener to listen for the state changes of the **Scroll** component and dynamically adjust the enabled state of the gesture recognizers to ensure they respond appropriately. 201 202 ```ts 203 .parallelGesture ( // Bind a PanGesture as a dynamic controller. 204 PanGesture() 205 .onActionUpdate((event: GestureEvent)=>{ 206 if (this.childRecognizer.getState() != GestureRecognizerState.SUCCESSFUL || this.currentRecognizer.getState() != GestureRecognizerState.SUCCESSFUL) { // If neither recognizer is in the SUCCESSFUL state, no control is applied. 207 return; 208 } 209 let target = this.childRecognizer.getEventTargetInfo() as ScrollableTargetInfo; 210 let currentTarget = this.currentRecognizer.getEventTargetInfo() as ScrollableTargetInfo; 211 if (target instanceof ScrollableTargetInfo && currentTarget instanceof ScrollableTargetInfo) { 212 this.childRecognizer.setEnabled(true); 213 this.currentRecognizer.setEnabled(false); 214 if (target.isEnd()) { // Adjust the enabled state of the gesture recognizers based on the current component state during movement. 215 if ((event.offsetY - this.lastOffset) < 0) { 216 this.childRecognizer.setEnabled(false); 217 if (currentTarget.isEnd()) { 218 this.currentRecognizer.setEnabled(false); 219 } else { 220 this.currentRecognizer.setEnabled(true); 221 } 222 } 223 } else if (target.isBegin()) { 224 if ((event.offsetY - this.lastOffset) > 0) { 225 this.childRecognizer.setEnabled(false); 226 if (currentTarget.isBegin()) { 227 this.currentRecognizer.setEnabled(false); 228 } else { 229 this.currentRecognizer.setEnabled(true); 230 } 231 } 232 } 233 } 234 this.lastOffset = event.offsetY 235 }) 236 ) 237 ``` 238 2394. Below is the complete code example. 240 241 ```ts 242 // xxx.ets 243 @Entry 244 @Component 245 struct FatherControlChild { 246 scroller: Scroller = new Scroller(); 247 scroller2: Scroller = new Scroller(); 248 private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 249 private childRecognizer: GestureRecognizer = new GestureRecognizer(); 250 private currentRecognizer: GestureRecognizer = new GestureRecognizer(); 251 private lastOffset: number = 0; 252 253 build() { 254 Stack({ alignContent: Alignment.TopStart }) { 255 Scroll(this.scroller) { // Outer scroll container. 256 Column() { 257 Text("Scroll Area") 258 .width('90%') 259 .height(150) 260 .backgroundColor(0xFFFFFF) 261 .borderRadius(15) 262 .fontSize(16) 263 .textAlign(TextAlign.Center) 264 .margin({ top: 10 }) 265 Scroll(this.scroller2) { // Inner scroll container. 266 Column() { 267 Text("Scroll Area2") 268 .width('90%') 269 .height(150) 270 .backgroundColor(0xFFFFFF) 271 .borderRadius(15) 272 .fontSize(16) 273 .textAlign(TextAlign.Center) 274 .margin({ top: 10 }) 275 Column() { 276 ForEach(this.arr, (item: number) => { 277 Text(item.toString()) 278 .width('90%') 279 .height(150) 280 .backgroundColor(0xFFFFFF) 281 .borderRadius(15) 282 .fontSize(16) 283 .textAlign(TextAlign.Center) 284 .margin({ top: 10 }) 285 }, (item: string) => item) 286 }.width('100%') 287 } 288 } 289 .id("inner") 290 .width('100%') 291 .height(800) 292 }.width('100%') 293 } 294 .id("outer") 295 .height(600) 296 .scrollable(ScrollDirection.Vertical) // The scrollbar scrolls in the vertical direction. 297 .scrollBar(BarState.On) // The scrollbar is always displayed. 298 .scrollBarColor(Color.Gray) // The scrollbar color is gray. 299 .scrollBarWidth(10) // The scrollbar width is 10. 300 .edgeEffect(EdgeEffect.None) 301 .shouldBuiltInRecognizerParallelWith((current: GestureRecognizer, others: Array<GestureRecognizer>) => { 302 for (let i = 0; i < others.length; i++) { 303 let target = others[i].getEventTargetInfo(); 304 if (target.getId() == "inner" && others[i].isBuiltIn() && others[i].getType() == GestureControl.GestureType.PAN_GESTURE) { // Identify the recognizer to work in parallel. 305 this.currentRecognizer = current; // Save the recognizer of the current component. 306 this.childRecognizer = others[i]; // Save the recognizer to work in parallel. 307 return others[i]; // Return the recognizer to work in parallel with the current one. 308 } 309 } 310 return undefined; 311 }) 312 .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. 313 let target = current.getEventTargetInfo(); 314 if (target.getId() == "outer" && current.isBuiltIn() && current.getType() == GestureControl.GestureType.PAN_GESTURE) { 315 for (let i = 0; i < others.length; i++) { 316 let target = others[i].getEventTargetInfo() as ScrollableTargetInfo; 317 if (target instanceof ScrollableTargetInfo && target.getId() == "inner") { // Identify the recognizer to work in parallel on the response chain. 318 let panEvent = event as PanGestureEvent; 319 this.childRecognizer.setEnabled(true); 320 this.currentRecognizer.setEnabled(false); 321 if (target.isEnd()) { // Dynamically control the recognizer's enabled state based on the current component state and direction of movement. 322 if (panEvent && panEvent.offsetY < 0) { 323 this.childRecognizer.setEnabled(false); 324 this.currentRecognizer.setEnabled(true); 325 } 326 } else if (target.isBegin()) { 327 if (panEvent.offsetY > 0) { 328 this.childRecognizer.setEnabled(false); 329 this.currentRecognizer.setEnabled(true); 330 } 331 } 332 } 333 } 334 } 335 return GestureJudgeResult.CONTINUE; 336 }) 337 .parallelGesture ( // Bind a PanGesture as a dynamic controller. 338 PanGesture() 339 .onActionUpdate((event: GestureEvent)=>{ 340 if (this.childRecognizer.getState() != GestureRecognizerState.SUCCESSFUL || this.currentRecognizer.getState() != GestureRecognizerState.SUCCESSFUL) { // If neither recognizer is in the SUCCESSFUL state, no control is applied. 341 return; 342 } 343 let target = this.childRecognizer.getEventTargetInfo() as ScrollableTargetInfo; 344 let currentTarget = this.currentRecognizer.getEventTargetInfo() as ScrollableTargetInfo; 345 if (target instanceof ScrollableTargetInfo && currentTarget instanceof ScrollableTargetInfo) { 346 this.childRecognizer.setEnabled(true); 347 this.currentRecognizer.setEnabled(false); 348 if (target.isEnd()) { // Adjust the enabled state of the gesture recognizers based on the current component state during movement. 349 if ((event.offsetY - this.lastOffset) < 0) { 350 this.childRecognizer.setEnabled(false); 351 if (currentTarget.isEnd()) { 352 this.currentRecognizer.setEnabled(false); 353 } else { 354 this.currentRecognizer.setEnabled(true); 355 } 356 } 357 } else if (target.isBegin()) { 358 if ((event.offsetY - this.lastOffset) > 0) { 359 this.childRecognizer.setEnabled(false) 360 if (currentTarget.isBegin()) { 361 this.currentRecognizer.setEnabled(false); 362 } else { 363 this.currentRecognizer.setEnabled(true); 364 } 365 } 366 } 367 } 368 this.lastOffset = event.offsetY; 369 }) 370 ) 371 }.width('100%').height('100%').backgroundColor(0xDCDCDC) 372 } 373 } 374 ``` 375