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![gesture_judge](figures/gesture_judge.png)
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![gesture_judge_image_response_region](figures/gesture_judge_image_response_region.png)
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![gesture_judge_controller](figures/gesture_judge_controller.png)
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