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 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 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 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 276