• 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## 点击事件(onClick)
10
11单击作为常用的手势,可以方便地使用[onClick](../reference/apis-arkui/arkui-ts/ts-universal-events-click.md#onclick)接口实现。尽管被称为事件,它实际上是基本手势类型,等同于将count配置为1的TapGesture,即单击手势。
12
13onClick与其他手势类型相同,也会参与命中测试、响应链收集等过程。可以使用[干预手势处理](./arkts-interaction-development-guide-support-gesture.md#干预手势处理)机制对onClick的响应进行动态决策。
14
15
16```typescript
17@Entry
18@ComponentV2
19struct Index {
20  private judgeCount: number = 0
21
22  increaseJudgeGuard(): void {
23    this.judgeCount++
24  }
25
26  build() {
27    Column() {
28      Column() {
29        Column()
30          .width('60%')
31          .height('50%')
32          .backgroundColor(Color.Grey)
33          .onClick(() => { // 1. 子组件上注册了点击事件,正常情况下点击在子组件上时,优先得到响应
34            console.info('Clicked on child')
35            this.increaseJudgeGuard()
36          })
37          .onGestureJudgeBegin((gestureInfo: GestureInfo, event: BaseGestureEvent) => {
38            // 3. 当数字增长为5的倍数时禁用子组件上的点击手势,这样父组件上的点击可以得到响应
39            if (this.judgeCount % 5 == 0 && gestureInfo.type == GestureControl.GestureType.CLICK) {
40              return GestureJudgeResult.REJECT
41            } else {
42              return GestureJudgeResult.CONTINUE
43            }
44          })
45      }
46      .width('80%')
47      .height('80%')
48      .justifyContent(FlexAlign.Center)
49      .backgroundColor(Color.Green)
50      .gesture(
51        TapGesture() // 2. 父组件上注册了点击手势,正常情况下点击在子组件区域时,父组件上的手势优先级低于子组件
52          .onAction(() => {
53            console.info('Clicked on parent')
54            this.increaseJudgeGuard()
55          }))
56    }
57    .height('100%')
58    .width('100%')
59    .justifyContent(FlexAlign.Center)
60  }
61}
62```
63
64示例中,每点击5次,子组件的点击事件将临时禁用1次,确保父组件点击优先响应。
65
66
67## 点击手势(TapGesture)
68
69
70```ts
71TapGesture(value?:{count?:number, fingers?:number})
72```
73
74
75点击手势支持单次点击和多次点击,拥有两个可选参数:
76
77
78- count:声明该点击手势识别的连续点击次数。默认值为1,非法值(即小于1的值)将被转化为默认值。当配置多次点击时,上一次抬起和下一次按下的超时时间为300毫秒。
79
80- fingers:用于声明触发点击的手指数量,最小值为1,最大值为10,默认值为1。当配置多指时,若第一根手指按下300毫秒内未有足够的手指数按下则手势识别失败。
81    以在Text组件上绑定双击手势(count值为2的点击手势)为例:
82
83  ```ts
84  // xxx.ets
85  @Entry
86  @Component
87  struct Index {
88    @State value: string = "";
89
90    build() {
91      Column() {
92        Text('Click twice').fontSize(28)
93          .gesture(
94            // 绑定count为2的TapGesture
95            TapGesture({ count: 2 })
96              .onAction((event: GestureEvent|undefined) => {
97              if(event){
98                this.value = JSON.stringify(event.fingerList[0]);
99              }
100              }))
101        Text(this.value)
102      }
103      .height(200)
104      .width(250)
105      .padding(20)
106      .border({ width: 3 })
107      .margin(30)
108    }
109  }
110  ```
111
112  ![tap](figures/tap.gif)
113
114
115## 长按手势(LongPressGesture)
116
117
118```ts
119LongPressGesture(value?:{fingers?:number, repeat?:boolean, duration?:number})
120```
121
122
123长按手势用于触发长按手势事件,拥有三个可选参数:
124
125
126- fingers:用于声明触发长按手势所需要的最少手指数量,最小值为1,最大值为10,默认值为1。
127
128- repeat:用于声明是否连续触发事件回调,默认值为false。
129
130- duration:用于声明触发长按所需的最短时间,单位为毫秒,默认值为500。
131
132
133以在Text组件上绑定可以重复触发的长按手势为例:
134
135
136
137```ts
138// xxx.ets
139@Entry
140@Component
141struct Index {
142  @State count: number = 0;
143
144  build() {
145    Column() {
146      Text('LongPress OnAction:' + this.count).fontSize(28)
147        .gesture(
148          // 绑定可以重复触发的LongPressGesture
149          LongPressGesture({ repeat: true })
150           .onAction((event: GestureEvent|undefined) => {
151              if(event){
152                if (event.repeat) {
153                  this.count++;
154                }
155              }
156            })
157            .onActionEnd(() => {
158              this.count = 0;
159            })
160        )
161    }
162    .height(200)
163    .width(250)
164    .padding(20)
165    .border({ width: 3 })
166    .margin(30)
167  }
168}
169```
170
171
172![longPress](figures/longPress.gif)
173
174
175## 拖动手势(PanGesture)
176
177
178```ts
179PanGesture(value?:{ fingers?:number, direction?:PanDirection, distance?:number})
180```
181
182
183拖动手势用于触发拖动手势事件,滑动达到最小滑动距离(默认值为5vp)时拖动手势识别成功,拥有三个可选参数:
184
185
186- fingers:用于声明触发拖动手势所需要的最少手指数量,最小值为1,最大值为10,默认值为1。
187
188- direction:用于声明触发拖动的手势方向,此枚举值支持逻辑与(&amp;)和逻辑或(|)运算。默认值为PanDirection.All189
190- distance:用于声明触发拖动的最小拖动识别距离,单位为vp,默认值为5。
191
192
193以下以实现一个简单的音量控制为例,可以通过拖动手势的回调函数处理多种不同的输入情况下的音量值增减的逻辑。
194支持以下五种操作方式:
1951、单指上下滑动;
1962、按住鼠标左键上下滑动;
1973、鼠标滚轮滚动;
1984、单指按住触控板上下滑动;
1995、使用触控板双指滑动。
200
201
202```ts
203// xxx.ets
204@Entry
205@Component
206struct VolumeControlDemo {
207  @State currentVolume: number = 50;
208  private readonly MAX_VOLUME: number = 100;
209  private readonly MIN_VOLUME: number = 0;
210
211  private handlePanUpdate(event: GestureEvent) {
212    const volumeChange = -event.offsetY * 0.1;
213    this.handleVolumeChange(volumeChange);
214  }
215
216  private handleWheelEvent(event: GestureEvent) {
217    const volumeChange = event.offsetY * 0.1;
218    this.handleVolumeChange(volumeChange);
219  }
220
221  private handleTouchPadScroll(event: GestureEvent) {
222    const volumeChange = -event.offsetY * 0.02;
223    this.handleVolumeChange(volumeChange);
224  }
225
226  private handleVolumeChange(delta: number) {
227    this.currentVolume = Math.min(
228      this.MAX_VOLUME,
229      Math.max(this.MIN_VOLUME, this.currentVolume + delta)
230    )
231
232  }
233
234  build() {
235    Column() {
236      // 状态显示
237      Row() {
238        Text(`音量: ${this.currentVolume}`).fontSize(20)
239      }.margin(10)
240
241      // 手势识别区域
242      Column()
243        .width('100%')
244        .height(250)
245        .backgroundColor('#F5F5F5')
246        .borderRadius(12)
247        .gesture(
248          PanGesture()
249            .onActionStart(() => {
250              console.info("Pan start");
251            })
252            .onActionUpdate((event: GestureEvent) => {
253              // 单指上下滑动
254              if (event.source === SourceType.TouchScreen) {
255                console.info("finger move triggered PanGesture");
256                this.handlePanUpdate(event);
257              }
258              if (event.source === SourceType.Mouse && event.sourceTool === SourceTool.MOUSE) {
259                // 鼠标左键按住上下滑动或者触控板单指按住上下滑动
260                if (event.axisHorizontal === 0 && event.axisVertical === 0) {
261                  console.info("mouse move with left button pressed triggered PanGesture");
262                  this.handlePanUpdate(event);
263                } else { // 鼠标滚轮滚动
264                  console.info("mouse wheel triggered PanGesture");
265                  this.handleWheelEvent(event);
266                }
267              }
268              if (event.sourceTool === SourceTool.TOUCHPAD && (event.axisHorizontal !== 0 || event.axisVertical !== 0)) {
269                console.info("touchpad double finger move triggered PanGesture");
270                this.handleTouchPadScroll(event);
271              }
272            })
273        )
274    }
275    .width('100%')
276    .height('100%')
277    .padding(20)
278  }
279}
280```
281
282
283![pan](figures/pan.gif)
284
285
286>**说明:**
287>
288>大部分可滑动组件,如List、Grid、Scroll、Tab等组件是通过PanGesture实现滑动,在组件内部的子组件绑定[拖动手势(PanGesture)](#拖动手势pangesture)或者[滑动手势(SwipeGesture)](#滑动手势swipegesture)会导致手势竞争。
289>
290>当在子组件绑定PanGesture时,在子组件区域进行滑动仅触发子组件的PanGesture。如果需要父组件响应,需要通过修改手势绑定方法或者子组件向父组件传递消息进行实现,或者通过修改父子组件的PanGesture参数distance使得拖动更灵敏。当子组件绑定SwipeGesture时,由于PanGesture和SwipeGesture触发条件不同,需要修改PanGesture和SwipeGesture的参数以达到所需效果。
291>
292>不合理的阈值设置会导致滑动不跟手(响应时延慢)的问题。
293
294
295## 捏合手势(PinchGesture)
296
297
298```ts
299PinchGesture(value?:{fingers?:number, distance?:number})
300```
301
302
303捏合手势用于触发捏合手势事件,拥有两个可选参数:
304
305
306- fingers:用于声明触发捏合手势所需要的最少手指数量,最小值为2,最大值为5,默认值为2。
307
308- distance:用于声明触发捏合手势的最小距离,单位为vp,默认值为5。
309
310
311以在Column组件上绑定三指捏合手势为例,可以通过在捏合手势的函数回调中获取缩放比例,实现对组件的缩小或放大:
312
313
314
315```ts
316// xxx.ets
317@Entry
318@Component
319struct Index {
320  @State scaleValue: number = 1;
321  @State pinchValue: number = 1;
322  @State pinchX: number = 0;
323  @State pinchY: number = 0;
324
325  build() {
326    Column() {
327      Column() {
328        Text('PinchGesture scale:\n' + this.scaleValue)
329        Text('PinchGesture center:\n(' + this.pinchX + ',' + this.pinchY + ')')
330      }
331      .height(200)
332      .width(300)
333      .border({ width: 3 })
334      .margin({ top: 100 })
335      // 在组件上绑定缩放比例,可以通过修改缩放比例来实现组件的缩小或者放大
336      .scale({ x: this.scaleValue, y: this.scaleValue, z: 1 })
337      .gesture(
338        // 在组件上绑定三指触发的捏合手势
339        PinchGesture({ fingers: 3 })
340          .onActionStart((event: GestureEvent|undefined) => {
341            console.info('Pinch start');
342          })
343            // 当捏合手势触发时,可以通过回调函数获取缩放比例,从而修改组件的缩放比例
344          .onActionUpdate((event: GestureEvent|undefined) => {
345            if(event){
346              this.scaleValue = this.pinchValue * event.scale;
347              this.pinchX = event.pinchCenterX;
348              this.pinchY = event.pinchCenterY;
349            }
350          })
351          .onActionEnd(() => {
352            this.pinchValue = this.scaleValue;
353            console.info('Pinch end');
354          })
355      )
356    }
357  }
358}
359```
360
361
362![pinch](figures/pinch.png)
363
364
365## 旋转手势(RotationGesture)
366
367
368```ts
369RotationGesture(value?:{fingers?:number, angle?:number})
370```
371
372
373旋转手势用于触发旋转手势事件,拥有两个可选参数:
374
375
376- fingers:用于声明触发旋转手势所需要的最少手指数量,最小值为2,最大值为5,默认值为2。
377
378- angle:用于声明触发旋转手势的最小改变度数,单位为deg,默认值为1。
379
380
381以在Text组件上绑定旋转手势实现组件的旋转为例,可以通过在旋转手势的回调函数中获取旋转角度,从而实现组件的旋转:
382
383
384
385```ts
386// xxx.ets
387@Entry
388@Component
389struct Index {
390  @State angle: number = 0;
391  @State rotateValue: number = 0;
392
393  build() {
394    Column() {
395      Text('RotationGesture angle:' + this.angle).fontSize(28)
396        // 在组件上绑定旋转布局,可以通过修改旋转角度来实现组件的旋转
397        .rotate({ angle: this.angle })
398        .gesture(
399          RotationGesture()
400           .onActionStart((event: GestureEvent|undefined) => {
401              console.info('RotationGesture is onActionStart');
402            })
403              // 当旋转手势生效时,通过旋转手势的回调函数获取旋转角度,从而修改组件的旋转角度
404            .onActionUpdate((event: GestureEvent|undefined) => {
405              if(event){
406                this.angle = this.rotateValue + event.angle;
407              }
408              console.info('RotationGesture is onActionUpdate');
409            })
410              // 当旋转结束抬手时,固定组件在旋转结束时的角度
411            .onActionEnd(() => {
412              this.rotateValue = this.angle;
413              console.info('RotationGesture is onActionEnd');
414            })
415            .onActionCancel(() => {
416              console.info('RotationGesture is onActionCancel');
417            })
418        )
419        .height(200)
420        .width(300)
421        .padding(20)
422        .border({ width: 3 })
423        .margin(100)
424    }
425  }
426}
427```
428
429
430![rotation](figures/rotation.png)
431
432
433## 滑动手势(SwipeGesture)
434
435
436```ts
437SwipeGesture(value?:{fingers?:number, direction?:SwipeDirection, speed?:number})
438```
439
440
441滑动手势用于触发滑动事件,当滑动速度大于100vp/s时可以识别成功,拥有三个可选参数:
442
443
444- fingers:用于声明触发滑动手势所需要的最少手指数量,最小值为1,最大值为10,默认值为1。
445
446- direction:用于声明触发滑动手势的方向,此枚举值支持逻辑与(&amp;)和逻辑或(|)运算。默认值为SwipeDirection.All447
448- speed:用于声明触发滑动的最小滑动识别速度,单位为vp/s,默认值为100。
449
450
451以在Column组件上绑定滑动手势实现组件的旋转为例:
452
453
454
455```ts
456// xxx.ets
457@Entry
458@Component
459struct Index {
460  @State rotateAngle: number = 0;
461  @State speed: number = 1;
462
463  build() {
464    Column() {
465      Column() {
466        Text("SwipeGesture speed\n" + this.speed)
467        Text("SwipeGesture angle\n" + this.rotateAngle)
468      }
469      .border({ width: 3 })
470      .width(300)
471      .height(200)
472      .margin(100)
473      // 在Column组件上绑定旋转,通过滑动手势的滑动速度和角度修改旋转的角度
474      .rotate({ angle: this.rotateAngle })
475      .gesture(
476        // 绑定滑动手势且限制仅在竖直方向滑动时触发
477        SwipeGesture({ direction: SwipeDirection.Vertical })
478          // 当滑动手势触发时,获取滑动的速度和角度,实现对组件的布局参数的修改
479          .onAction((event: GestureEvent|undefined) => {
480            if(event){
481              this.speed = event.speed;
482              this.rotateAngle = event.angle;
483            }
484          })
485      )
486    }
487  }
488}
489```
490
491
492![swipe](figures/swipe.gif)
493
494
495>**说明:**
496>
497>当SwipeGesture和PanGesture同时绑定时,若二者是以默认方式或者互斥方式进行绑定时,会发生竞争。SwipeGesture的触发条件为滑动速度达到100vp/s,PanGesture的触发条件为滑动距离达到5vp,先达到触发条件的手势触发。可以通过修改SwipeGesture和PanGesture的参数以达到不同的效果。
498