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  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 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:用于声明触发拖动的手势方向,此枚举值支持逻辑与(&)和逻辑或(|)运算。默认值为PanDirection.All。 189 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 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 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 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:用于声明触发滑动手势的方向,此枚举值支持逻辑与(&)和逻辑或(|)运算。默认值为SwipeDirection.All。 447 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 493 494 495>**说明:** 496> 497>当SwipeGesture和PanGesture同时绑定时,若二者是以默认方式或者互斥方式进行绑定时,会发生竞争。SwipeGesture的触发条件为滑动速度达到100vp/s,PanGesture的触发条件为滑动距离达到5vp,先达到触发条件的手势触发。可以通过修改SwipeGesture和PanGesture的参数以达到不同的效果。 498