• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 组合手势
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @jiangtao92-->
5<!--Designer: @piggyguy-->
6<!--Tester: @songyanhong-->
7<!--Adviser: @HelloCrease-->
8
9
10组合手势由多种单一手势组合而成,通过在GestureGroup中使用不同的[GestureMode](../reference/apis-arkui/arkui-ts/ts-combined-gestures.md#gesturemode枚举说明)来声明该组合手势的类型,支持[顺序识别](#顺序识别)、[并行识别](#并行识别)和[互斥识别](#互斥识别)三种类型。
11
12```ts
13GestureGroup(mode:GestureMode, gesture:GestureType[])
14```
15
16
17- mode:为GestureMode枚举类。用于声明该组合手势的类型。
18
19- gesture:由多个手势组合而成的数组。用于声明组合成该组合手势的各个手势。
20
21
22## 顺序识别
23
24顺序识别组合手势对应的GestureMode为Sequence。顺序识别组合手势将按照手势的注册顺序识别手势,直到所有的手势识别成功。当顺序识别组合手势中有一个手势识别失败时,后续手势识别均失败。顺序识别手势组仅有最后一个手势可以响应onActionEnd。
25
26以一个由长按手势和拖动手势组合而成的顺序识别手势为例:
27
28在一个Column组件上绑定了translate属性,通过修改该属性可以设置组件的位置移动。然后在该组件上绑定LongPressGesture和PanGesture组合而成的Sequence组合手势。当触发LongPressGesture时,更新显示的数字。当长按后进行拖动时,根据拖动手势的回调函数,实现组件的拖动。
29
30```ts
31// xxx.ets
32@Entry
33@Component
34struct Index {
35  @State offsetX: number = 0;
36  @State offsetY: number = 0;
37  @State count: number = 0;
38  @State positionX: number = 0;
39  @State positionY: number = 0;
40  @State borderStyles: BorderStyle = BorderStyle.Solid;
41
42  build() {
43    Column() {
44      Text('sequence gesture\n' + 'LongPress onAction:' + this.count + '\nPanGesture offset:\nX: ' + this.offsetX + '\n' + 'Y: ' + this.offsetY)
45        .fontSize(28)
46    }.margin(10)
47    .borderWidth(1)
48    // 绑定translate属性可以实现组件的位置移动
49    .translate({ x: this.offsetX, y: this.offsetY, z: 0 })
50    .height(250)
51    .width(300)
52    //以下组合手势为顺序识别,当长按手势事件未正常触发时不会触发拖动手势事件
53    .gesture(
54      // 声明该组合手势的类型为Sequence类型
55      GestureGroup(GestureMode.Sequence,
56        // 该组合手势第一个触发的手势为长按手势,且长按手势可多次响应
57        LongPressGesture({ repeat: true })
58          // 当长按手势识别成功,增加Text组件上显示的count次数
59          .onAction((event: GestureEvent|undefined) => {
60            if(event){
61              if (event.repeat) {
62                this.count++;
63              }
64            }
65            console.info('LongPress onAction');
66          })
67          .onActionEnd(() => {
68            console.info('LongPress end');
69          }),
70        // 当长按之后进行拖动,PanGesture手势被触发
71        PanGesture()
72          .onActionStart(() => {
73            this.borderStyles = BorderStyle.Dashed;
74            console.info('pan start');
75          })
76            // 当该手势被触发时,根据回调获得拖动的距离,修改该组件的位移距离从而实现组件的移动
77          .onActionUpdate((event: GestureEvent|undefined) => {
78            if(event){
79              this.offsetX = (this.positionX + event.offsetX);
80              this.offsetY = this.positionY + event.offsetY;
81            }
82            console.info('pan update');
83          })
84          .onActionEnd(() => {
85            this.positionX = this.offsetX;
86            this.positionY = this.offsetY;
87            this.borderStyles = BorderStyle.Solid;
88          })
89      )
90      .onCancel(() => {
91        console.log("sequence gesture canceled")
92      })
93    )
94  }
95}
96```
97
98
99![sequence](figures/sequence.gif)
100
101
102>**说明:**
103>
104>拖拽事件是一种典型的顺序识别组合手势事件,由长按手势事件和滑动手势事件组合而成。只有先长按达到长按手势事件预设置的时间后进行滑动才会触发拖拽事件。如果长按事件未达到或者长按后未进行滑动,拖拽事件均识别失败。
105
106
107## 并行识别
108
109并行识别组合手势对应的GestureMode为Parallel。并行识别组合手势中注册的手势将同时进行识别,直到所有手势识别结束。并行识别手势组合中的手势进行识别时互不影响。
110
111以在一个Column组件上绑定点击手势和双击手势组成的并行识别手势为例,由于单击手势和双击手势是并行识别,因此两个手势可以同时进行识别,二者互不干涉。
112
113```ts
114// xxx.ets
115@Entry
116@Component
117struct Index {
118  @State count1: number = 0;
119  @State count2: number = 0;
120
121  build() {
122    Column() {
123      Text('Parallel gesture\n' + 'tapGesture count is 1:' + this.count1 + '\ntapGesture count is 2:' + this.count2 + '\n')
124        .fontSize(28)
125    }
126    .height(200)
127    .width('100%')
128    // 以下组合手势为并行识别,单击手势识别成功后,若在规定时间内再次点击,双击手势也会识别成功
129    .gesture(
130      GestureGroup(GestureMode.Parallel,
131        TapGesture({ count: 1 })
132          .onAction(() => {
133            this.count1++;
134          }),
135        TapGesture({ count: 2 })
136          .onAction(() => {
137            this.count2++;
138          })
139      )
140    )
141  }
142}
143```
144
145
146![parallel](figures/parallel.gif)
147
148
149>**说明:**
150>
151>当由单击手势和双击手势组成一个并行识别组合手势后,在区域内进行点击时,单击手势和双击手势将同时进行识别。
152>
153>当只有单次点击时,单击手势识别成功,双击手势识别失败。
154>
155>当有两次点击时,若两次点击相距时间在规定时间内(默认规定时间为300毫秒),触发两次单击事件和一次双击事件。
156>
157>当有两次点击时,若两次点击相距时间超出规定时间,触发两次单击事件不触发双击事件。
158
159
160## 互斥识别
161
162互斥识别组合手势对应的GestureMode为Exclusive。互斥识别组合手势中注册的手势将同时进行识别,若有一个手势识别成功,则结束手势识别,其他所有手势识别失败。
163
164以在一个Column组件上绑定单击手势和双击手势组合而成的互斥识别组合手势为例。若先绑定单击手势后绑定双击手势,由于单击手势只需要一次点击即可触发而双击手势需要两次,每次的点击事件均被单击手势消费而不能积累成双击手势,所以双击手势无法触发。若先绑定双击手势后绑定单击手势,则触发双击手势不触发单击手势。
165
166```ts
167// xxx.ets
168@Entry
169@Component
170struct Index {
171  @State count1: number = 0;
172  @State count2: number = 0;
173
174  build() {
175    Column() {
176      Text('Exclusive gesture\n' + 'tapGesture count is 1:' + this.count1 + '\ntapGesture count is 2:' + this.count2 + '\n')
177        .fontSize(28)
178    }
179    .height(200)
180    .width('100%')
181    //以下组合手势为互斥识别,单击手势识别成功后,双击手势会识别失败
182    .gesture(
183      GestureGroup(GestureMode.Exclusive,
184        TapGesture({ count: 1 })
185          .onAction(() => {
186            this.count1++;
187          }),
188        TapGesture({ count: 2 })
189          .onAction(() => {
190            this.count2++;
191          })
192      )
193    )
194  }
195}
196```
197
198
199![exclusive](figures/exclusive.gif)
200
201
202>**说明:**
203>
204>当由单击手势和双击手势组成一个互斥识别组合手势后,在区域内进行点击时,单击手势和双击手势将同时进行识别。
205>
206>当只有单次点击时,单击手势识别成功,双击手势识别失败。
207>
208>当有两次点击时,手势响应取决于绑定手势的顺序。若先绑定单击手势后绑定双击手势,单击手势在第一次点击时即宣告识别成功,此时双击手势已经失败。即使在规定时间内进行了第二次点击,双击手势事件也不会进行响应,此时会触发单击手势事件的第二次识别成功。若先绑定双击手势后绑定单击手势,则会响应双击手势而不响应单击手势。
209
210## 场景示例
211
212以下示例实现了子组件绑定长按和拖动手势,长按手势和拖动手势需要可以同时触发,但是在长按手势未成功时,需要让父组件Swiper的内置拖动手势触发的功能。由于子组件的拖动手势和父组件的内置拖动手势是竞争关系,且子组件的拖动手势的优先级更高,因此需要通过动态控制子组件的拖动手势是否触发。
213
214```ts
215// xxx.ets
216import { PromptAction } from '@kit.ArkUI';
217
218@Entry
219@Component
220struct CombinedGestureDemo {
221  @State isLongPress: boolean = false;
222  promptAction: PromptAction = this.getUIContext().getPromptAction();
223
224  build() {
225    Swiper() {
226      // 页面1
227      Row()
228        .width('100%')
229        .height('100%')
230        .backgroundColor(Color.Grey)
231        .borderRadius(12)
232        // 通过自定义手势判定回调,判断在长按手势未成功时,拒绝子组件的拖动手势,从而让父组件Swiper的拖动手势成功
233        .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer, others: Array<GestureRecognizer>)=>{
234          if (current.getType() !== GestureControl.GestureType.PAN_GESTURE) {
235            return GestureJudgeResult.CONTINUE;
236          }
237          if (this.isLongPress) {
238            return GestureJudgeResult.CONTINUE;
239          }
240          return GestureJudgeResult.REJECT;
241        })
242        .gesture(
243          // 绑定并行手势组,实现长按手势和拖动手势可以同时触发
244          GestureGroup(GestureMode.Parallel,
245            LongPressGesture()
246              .onAction(() => {
247                this.isLongPress = true;
248                this.promptAction.showToast({ message: "LongPress trigger" })
249              })
250              .onActionEnd(() => {
251                this.isLongPress = false;
252              })
253            ,
254            PanGesture()
255              .onActionStart(() => {
256                this.promptAction.showToast({ message: "child pan start" })
257              })
258          )
259        )
260      // 页面2
261      Row()
262        .width('100%')
263        .height('100%')
264        .backgroundColor(Color.Pink)
265        .borderRadius(12)
266    }
267    .borderWidth(2)
268    .width('100%')
269    .height(300)
270    .padding(20)
271  }
272}
273```
274
275![combined-gesture](figures/combined-gesture.gif)
276