1# 拖拽事件 2 3## 概述 4 5拖拽框架提供了一种通过鼠标或手势触屏的方式传递数据,即从一个组件位置拖出数据,并拖入到另一个组件位置上进行响应,拖出一方提供数据,拖入一方接收和处理数据。该操作可以让用户方便地移动、复制或删除指定内容。 6 7* 拖拽操作:在某个能够响应拖出的组件上长按并滑动触发的拖拽行为,当用户释放时,拖拽操作结束; 8* 拖拽背景(背板):用户所拖动数据的形象化表示,开发者可通过[onDragStart](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragstart)的[CustomerBuilder](../reference/apis-arkui/arkui-ts/ts-types.md#custombuilder8)或[DragItemInfo](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragiteminfo说明)设置,也可以通过[dragPreview](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#dragpreview11)通用属性设置; 9* 拖拽内容:拖动的数据,使用UDMF统一API [UnifiedData](../reference/apis-arkdata/js-apis-data-unifiedDataChannel.md#unifieddata) 进行封装; 10* 拖出对象:触发拖拽操作并提供数据的组件; 11* 拖入目标:可接收并处理拖动数据的组件; 12* 拖拽点:鼠标或手指等与屏幕的接触位置,是否进入组件范围的判定是以接触点是否进入范围进行判断。 13 14## 拖拽流程 15 16### 手势拖拽 17 18对于手势长按触发拖拽的场景,发起拖拽前框架侧会对当前组件是否可拖拽进行校验,针对默认可拖出的组件([Search](../reference/apis-arkui/arkui-ts/ts-basic-components-search.md)、[TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md)、[TextArea](../reference/apis-arkui/arkui-ts/ts-basic-components-textarea.md)、[RichEditor](../reference/apis-arkui/arkui-ts/ts-basic-components-richeditor.md)、[Text](../reference/apis-arkui/arkui-ts/ts-basic-components-text.md)、[Image](../reference/apis-arkui/arkui-ts/ts-basic-components-image.md)、<!--Del-->[FormComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-formcomponent-sys.md)、<!--DelEnd-->[Hyperlink](../reference/apis-arkui/arkui-ts/ts-container-hyperlink.md))需要判断是否设置了[draggable](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#draggable)属性为true(若系统使能分层参数,则draggable默认为true),其他组件需要额外判断是否设置了onDragStart回调函数,在满足上述可拖拽条件下,长按大于等于500ms可触发拖拽,长按800ms开始做预览图的浮起动效。当与Menu功能一起使用,并通过isShow方式控制显隐时,不建议在用户操作800ms后再控制显示菜单,这可能会导致非预期的行为。 19 20手势拖拽(手指/手写笔)触发拖拽流程: 21 22 23 24### 鼠标拖拽 25 26鼠标拖拽属于即拖即走,只要鼠标左键在可拖拽的组件上按下并移动大于1vp就可触发拖拽。 27 28当前支持应用内和跨应用拖拽,提供了多个回调事件供开发者感知拖拽状态并干预系统默认拖拽行为,具体如下: 29 30| **回调事件** | **说明**| 31| ---------------- | ------------------------| 32| onDragStart | 支持拖出的组件产生拖出动作时触发。<br>该回调可以感知拖拽行为的发起,开发者可通过在 onDragStart 方法中设置拖拽所传递的数据以及自定义拖拽背板图。推荐开发者使用pixelmap的方式返回背板图,不推荐使用customBuilder的方式,会有额外的性能开销。| 33| onDragEnter | 当拖拽活动的拖拽点进入组件范围内时触发,只有该组件监听了[onDrop](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondrop)事件时,此回调才会被触发。| 34| onDragMove| 拖拽点在组件范围内移动时触发;只有该组件监听了onDrop事件时,此回调才会被触发。<br>在此过程中可通过[DragEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent)中的setResult方法影响系统部分场景下的外观<br>1. 设置DragResult.DROP\_ENABLED;<br>2. 设置DragResult.DROP\_DISABLED。| 35| onDragLeave | 拖拽点离开组件范围时触发;只有该组件监听了onDrop事件时,此回调才会被触发。<br>针对以下两种情况默认不会发送onDragLeave事件:<br>1. 父组件移动到子组件;<br>2. 目标组件与当前组件布局有重叠。<br>API version 12开始可通过[UIContext](../reference/apis-arkui/js-apis-arkui-UIContext.md)中的[setDragEventStrictReportingEnabled](../reference/apis-arkui/js-apis-arkui-UIContext.md#setdrageventstrictreportingenabled12)方法严格触发onDragLeave事件。| 36| onDrop | 当用户在组件范围内释放时触发,需在此回调中通过DragEvent中的setResult方法设置拖拽结果,否则在拖出方组件的onDragEnd方法中通过getResult方法只能拿到默认的处理结果DragResult.DRAG\_FAILED。<br>该回调也是开发者干预系统默认拖入处理行为的地方,系统会优先执行开发者的onDrop回调,通过在回调中执行setResult方法来告知系统该如何处理所拖拽的数据;<br>1. 设置 DragResult.DRAG\_SUCCESSFUL,数据完全由开发者自己处理,系统不进行处理;<br>2. 设置DragResult.DRAG\_FAILED,数据不再由系统继续处理;<br>3. 设置DragResult.DRAG\_CANCELED,系统也不需要进行数据处理;<br>4. 设置DragResult.DROP\_ENABLED或DragResult.DROP\_DISABLED会被忽略,同设置DragResult.DRAG\_FAILED。| 37| onDragEnd | 当用户释放拖拽时,拖拽活动结束,发起拖出动作的组件会触发该回调。| 38| onPreDrag | 绑定此事件的组件,当触发拖拽发起前的不同阶段时,触发回调。<br>开发者可以使用该方法监听[PreDragStatus](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#predragstatus12枚举说明)中的枚举在发起拖拽前的不同阶段准备数据。<br>1. ACTION\_DETECTING\_STATUS:拖拽手势启动阶段。(按下50ms时触发);<br>2. READY\_TO\_TRIGGER\_DRAG\_ACTION:拖拽准备完成,可发起拖拽阶段。(按下500ms时触发);<br>3. PREVIEW\_LIFT\_STARTED:拖拽浮起动效发起阶段。(按下800ms时触发);<br>4. PREVIEW\_LIFT\_FINISHED:拖拽浮起动效结束阶段。(浮起动效完全结束时触发);<br>5. PREVIEW\_LANDING\_STARTED:拖拽落回动效发起阶段。(落回动效发起时触发);<br>6. PREVIEW\_LANDING\_FINISHED:拖拽落回动效结束阶段。(落回动效结束时触发);<br>7. ACTION\_CANCELED\_BEFORE\_DRAG:拖拽浮起落位动效中断。(已满足READY\_TO\_TRIGGER\_DRAG\_ACTION状态后,未达到动效阶段,手指抬手时触发)。 | 39 40更多用法参考[拖拽事件](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md)。 41 42[DragEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent)支持相关get方法获取拖拽行为的相关信息,下表列出了get方法在对应拖拽回调中是否能返回有效数据。 43| 回调事件 | onDragStart | onDragEnter | onDragMove | onDragLeave | onDrop | onDragEnd | 44| - | - | - | - | - | - | - | 45| getData | | | | | 支持 | | 46| getSummary | | 支持 | 支持 | 支持 | 支持 | | 47| getResult | | | | | | 支持 | 48| getPreviewRect | | | | | 支持 | | 49| getVelocity/X/Y | | 支持 | 支持 | 支持 | 支持 | | 50| getWindowX/Y | 支持 | 支持 | 支持 | 支持 | 支持 | | 51| getDisplayX/Y | 支持 | 支持 | 支持 | 支持 | 支持 | | 52| getX/Y | 支持 | 支持 | 支持 | 支持 | 支持 | | 53| behavior | | | | | | 支持 | 54 55[DragEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent)支持相关set方法向系统传递信息,这些信息部分会影响系统对UI或数据的处理方式。下表列出了set方法应该在回调的哪个阶段执行才会被系统接受并处理。 56| 回调事件 | onDragStart | onDragEnter | onDragMove | onDragLeave | onDrop | 57| - | - | - | - | - | - | 58| useCustomDropAnimation | | | | | 支持 | 59| setData | 支持 | | | | | 60| setResult | 支持,可通过set failed或cancel来阻止拖拽发起 | 支持,不作为最终结果传递给onDragEnd | 支持,不作为最终结果传递给onDragEnd | 支持,不作为最终结果传递给onDragEnd | 支持,作为最终结果传递给onDragEnd | 61| behavior | | 支持 | 支持 | 支持 | 支持 | 62 63## 拖拽背板图 64 65拖拽移动过程中显示的拖拽背板图,并非是组件本身,其是用户拖动数据的表示,开发者可以将其设置为任意可显示的图像。其中onDragStart 回调返回的customBuilder或pixelmap可以设置拖拽移动过程中的背板图,浮起图默认使用组件本身的截图;dragpreview属性设置的customBuilder或pixelmap可以设置浮起和拖拽过程的背板图;如果开发者没有配置背板图,则系统会默认取组件本身的截图作为浮起及拖拽过程中的背板图。 66 67拖拽背板图当前支持设置透明度、圆角、阴影和模糊,具体用法见:[拖拽控制](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md)。 68 69**约束:** 70 71* 对于容器组件,如果内部内容通过position,offset等手段使得绘制区域超出了容器组件范围,则系统截图无法截取到范围之外的内容,此种情况下,如果一定要浮起及拖拽背板能够包含范围之外的内容,则可考虑通过扩大容器范围或自定义方式进行; 72* 不管是使用自定义builder或是系统默认截图方式,截图都暂时无法应用[scale](../reference/apis-arkui/arkui-ts/ts-universal-attributes-transformation.md#scale)、[rotate](../reference/apis-arkui/arkui-ts/ts-universal-attributes-transformation.md#rotate)等图形变换效果。 73 74## 开发步骤 75 76### 通用拖拽适配 77 78如下以[Image](../reference/apis-arkui/arkui-ts/ts-basic-components-image.md)组件为例,介绍组件拖拽开发的基本步骤,以及开发中需要注意的事项。 79 801. 组件使能拖拽。 81 82 设置draggable属性为true,并设置onDragStart回调,回调中可以通过UDMF设置拖拽的数据,并返回自定义拖拽背板图; 83 84 ```ts 85 import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData'; 86 87 Image($r('app.media.app_icon')) 88 .width(100) 89 .height(100) 90 .draggable(true) 91 .onDragStart((event) => { 92 let data: unifiedDataChannel.Image = new unifiedDataChannel.Image(); 93 data.imageUri = 'common/pic/img.png'; 94 let unifiedData = new unifiedDataChannel.UnifiedData(data); 95 event.setData(unifiedData); 96 97 let dragItemInfo: DragItemInfo = { 98 pixelMap: this.pixmap, 99 extraInfo: "this is extraInfo", 100 }; 101 // onDragStart回调函数中返回自定义拖拽背板图 102 return dragItemInfo; 103 }) 104 ``` 105 106 手势场景触发拖拽依赖底层绑定的长按手势,若开发者在被拖拽组件上也绑定长按手势,则会与底层的长按手势发生竞争,导致拖拽失败。可以用并行手势解决此类问题,如下: 107 108 ```ts 109 .parallelGesture(LongPressGesture().onAction(() => { 110 promptAction.showToast({ duration: 100, message: 'Long press gesture trigger' }); 111 })) 112 ``` 113 1142. 自定义拖拽背板图。 115 116 自定义拖拽背板图的pixmap可以通过设置[onPreDrag](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#onpredrag12)函数在长按50ms时触发的回调中前提准备; 117 118 ```ts 119 .onPreDrag((status: PreDragStatus) => { 120 if (preDragStatus == PreDragStatus.ACTION_DETECTING_STATUS) { 121 this.getComponentSnapshot(); 122 } 123 }) 124 ``` 125 126 具体pixmap的生成可以调用[componentSnapshot.createFromBuilder](../reference/apis-arkui/js-apis-arkui-componentSnapshot.md#componentsnapshotcreatefrombuilder)函数; 127 128 ```ts 129 @Builder 130 pixelMapBuilder() { 131 Column() { 132 Image($r('app.media.startIcon')) 133 .width(120) 134 .height(120) 135 .backgroundColor(Color.Yellow) 136 } 137 } 138 private getComponentSnapshot(): void { 139 this.getUIContext().getComponentSnapshot().createFromBuilder(()=>{this.pixelMapBuilder()}, 140 (error: Error, pixmap: image.PixelMap) => { 141 if(error){ 142 console.log("error: " + JSON.stringify(error)) 143 return; 144 } 145 this.pixmap = pixmap; 146 }) 147 } 148 ``` 149 1503. 如果开发者要严格触发[onDragLeave](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragleave)事件,可以通过[setDragEventStrictReportingEnabled](../reference/apis-arkui/js-apis-arkui-UIContext.md#setdrageventstrictreportingenabled12)方法设置。 151 152 ```ts 153 import { UIAbility } from '@kit.AbilityKit'; 154 import { window, UIContext } from '@kit.ArkUI'; 155 156 export default class EntryAbility extends UIAbility { 157 onWindowStageCreate(windowStage: window.WindowStage): void { 158 windowStage.loadContent('pages/Index', (err, data) => { 159 if (err.code) { 160 return; 161 } 162 windowStage.getMainWindow((err, data) => { 163 if (err.code) { 164 return; 165 } 166 let windowClass: window.Window = data; 167 let uiContext: UIContext = windowClass.getUIContext(); 168 uiContext.getDragController().setDragEventStrictReportingEnabled(true); 169 }); 170 }); 171 } 172 } 173 ``` 174 1754. 拖拽过程显示角标样式。 176 177 可以通过设置[allowDrop](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#allowdrop)定义接收的数据类型影响角标显示,当拖拽数据是定义允许落入的数据类型时,显示COPY角标;当拖拽数据不在定义允许落入的数据类型范围时,显示FORBIDDEN角标;未设置allowDrop时,显示MOVE角标。如下代码表示只接收UnifiedData中定义的HYPERLINK和PLAIN\_TEXT类型的数据,其他数据类型将禁止落入; 178 179 ```ts 180 .allowDrop([uniformTypeDescriptor.UniformDataType.HYPERLINK, uniformTypeDescriptor.UniformDataType.PLAIN_TEXT]) 181 ``` 182 183 此外在实现onDrop回调的情况下还可以通过在onDragMove中设置[DragResult](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragresult10枚举说明)为DROP\_ENABLED,并设置[DragBehavior](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragbehavior10)为COPY或MOVE控制角标显示。如下代码将移动时的角标强制设置为MOVE; 184 185 ```ts 186 .onDragMove((event) => { 187 event.setResult(DragResult.DROP_ENABLED); 188 event.dragBehavior = DragBehavior.MOVE; 189 }) 190 ``` 191 1925. 拖拽数据的接收。 193 194 需要设置onDrop回调,并在回调中处理拖拽数据,显示设置拖拽结果 195 196 ```ts 197 .onDrop((dragEvent?: DragEvent) => { 198 // 获取拖拽数据 199 this.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => { 200 let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords(); 201 let rect: Rectangle = event.getPreviewRect(); 202 this.imageWidth = Number(rect.width); 203 this.imageHeight = Number(rect.height); 204 this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri; 205 this.imgState = Visibility.None; 206 // 显式设置result为successful,则将该值传递给拖出方的onDragEnd 207 event.setResult(DragResult.DRAG_SUCCESSFUL); 208 }) 209 ``` 210 211 数据的传递是通过UDMF实现的,在数据较大时可能存在时延,因此在首次获取数据失败时建议加1500ms的延迟重试机制: 212 213 ```ts 214 getDataFromUdmfRetry(event: DragEvent, callback: (data: DragEvent) => void) { 215 try { 216 let data: UnifiedData = event.getData(); 217 if (!data) { 218 return false; 219 } 220 let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords(); 221 if (!records || records.length <= 0) { 222 return false; 223 } 224 callback(event); 225 return true; 226 } catch (e) { 227 console.log("getData failed, code: " + (e as BusinessError).code + ", message: " + (e as BusinessError).message); 228 return false; 229 } 230 } 231 232 getDataFromUdmf(event: DragEvent, callback: (data: DragEvent) => void) { 233 if (this.getDataFromUdmfRetry(event, callback)) { 234 return; 235 } 236 setTimeout(() => { 237 this.getDataFromUdmfRetry(event, callback); 238 }, 1500); 239 } 240 ``` 241 2426. 拖拽发起方可以通过设置onDragEnd回调感知拖拽结果。 243 244 ```ts 245 import { promptAction } from '@kit.ArkUI'; 246 247 .onDragEnd((event) => { 248 // onDragEnd里取到的result值在接收方onDrop设置 249 if (event.getResult() === DragResult.DRAG_SUCCESSFUL) { 250 promptAction.showToast({ duration: 100, message: 'Drag Success' }); 251 } else if (event.getResult() === DragResult.DRAG_FAILED) { 252 promptAction.showToast({ duration: 100, message: 'Drag failed' }); 253 } 254 }) 255 ``` 256 257### 多选拖拽适配 258 259API version 12开始[Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md)组件和[List](../reference/apis-arkui/arkui-ts/ts-container-list.md)组件中的GridItem和ListItem组件支持多选拖拽,当前只支持onDragStart的方式。如下以Grid为例,介绍多选拖拽的基本步骤,以及开发中的注意事项。 260 2611. 组件多选拖拽使能。 262 263 创建GridItem子组件并绑定onDragStart函数。同时设置GridItem组件的状态是可选中的。 264 265 ```ts 266 Grid() { 267 ForEach(this.numbers, (idx: number) => { 268 GridItem() { 269 Column() 270 .backgroundColor(this.colors[idx % 9]) 271 .width(50) 272 .height(50) 273 .opacity(1.0) 274 .id('grid'+idx) 275 } 276 .onDragStart(()=>{}) 277 .selectable(true) 278 }, (idx: string) => idx) 279 } 280 ``` 281 282 多选拖拽功能默认为关闭状态,使用多选拖拽需要在[dragPreviewOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#dragpreviewoptions11)接口中的DragInteractionOptions参数中设置isMultiSelectionEnabled为true,表示当前组件是否多选。DragInteractionOptions也有支持组件浮起前默认效果的参数defaultAnimationBeforeLifting,设置该参数为true后组件在浮起前会有一个默认缩小动效。 283 284 ```ts 285 .dragPreviewOptions({isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true}) 286 ``` 287 288 为了保证选中状态,需要设置GridItem子组件selected状态为true。例如,可以通过[onClick](../reference/apis-arkui/arkui-ts/ts-universal-events-click.md#onclick)的调用去设置特定的组件为选中的状态。 289 290 ```ts 291 .selected(this.isSelectedGrid[idx]) 292 .onClick(()=>{ 293 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx] 294 }) 295 ``` 296 2972. 多选拖拽性能优化。 298 299 多选拖拽情况下,多选会有聚拢的动画效果,聚拢效果触发时,会给当前屏幕内显示的选中组件截图,当选中组件过多,会导致很高的性能开销。多选拖拽支持从dragPreview中获取截图来作为聚拢动效的截图,以节省性能。 300 301 ```ts 302 .dragPreview({ 303 pixelMap:this.pixmap 304 }) 305 ``` 306 307 截图的获取可以在选中组件时通过调用componentSnapshot中的get方法获取。如下通过获取组件对应id的方法进行截图。 308 309 ```ts 310 @State previewData: DragItemInfo[] = [] 311 @State isSelectedGrid: boolean[] = [] 312 .onClick(()=>{ 313 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx] 314 if (this.isSelectedGrid[idx]) { 315 let gridItemName = 'grid' + idx 316 this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{ 317 this.pixmap = pixmap 318 this.previewData[idx] = { 319 pixelMap:this.pixmap 320 } 321 }) 322 } 323 }) 324 ``` 325 3263. 多选显示效果。 327 328 通过stateStyles可以设置选中态和非选中态的显示效果,方便区分。 329 330 ```ts 331 @Styles 332 normalStyles(): void{ 333 .opacity(1.0) 334 } 335 336 @Styles 337 selectStyles(): void{ 338 .opacity(0.4) 339 } 340 341 .stateStyles({ 342 normal : this.normalStyles, 343 selected: this.selectStyles 344 }) 345 ``` 346 3474. 数量角标适配。 348 349 多选拖拽的数量角标当前需要应用使用dragPreviewOptions中的numberBadge参数设置,开发者需要根据当前选中的节点数量来设置数量角标。 350 351 ```ts 352 @State numberBadge: number = 0; 353 354 .onClick(()=>{ 355 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx] 356 if (this.isSelectedGrid[idx]) { 357 this.numberBadge++; 358 } else { 359 this.numberBadge--; 360 } 361 }) 362 // 多选场景右上角数量角标需要应用设置numberBadge参数 363 .dragPreviewOptions({numberBadge: this.numberBadge}) 364 ``` 365 366## 完整示例 367 368### 通用拖拽适配 369 370```ts 371import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData'; 372import { promptAction } from '@kit.ArkUI'; 373import { BusinessError } from '@kit.BasicServicesKit'; 374import { image } from '@kit.ImageKit'; 375 376@Entry 377@Component 378struct Index { 379 @State targetImage: string = ''; 380 @State imageWidth: number = 100; 381 @State imageHeight: number = 100; 382 @State imgState: Visibility = Visibility.Visible; 383 @State pixmap: image.PixelMap|undefined = undefined 384 385 @Builder 386 pixelMapBuilder() { 387 Column() { 388 Image($r('app.media.startIcon')) 389 .width(120) 390 .height(120) 391 .backgroundColor(Color.Yellow) 392 } 393 } 394 395 getDataFromUdmfRetry(event: DragEvent, callback: (data: DragEvent) => void) { 396 try { 397 let data: UnifiedData = event.getData(); 398 if (!data) { 399 return false; 400 } 401 let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords(); 402 if (!records || records.length <= 0) { 403 return false; 404 } 405 callback(event); 406 return true; 407 } catch (e) { 408 console.log("getData failed, code: " + (e as BusinessError).code + ", message: " + (e as BusinessError).message); 409 return false; 410 } 411 } 412 // 获取UDMF数据,首次获取失败后添加1500ms延迟重试机制 413 getDataFromUdmf(event: DragEvent, callback: (data: DragEvent) => void) { 414 if (this.getDataFromUdmfRetry(event, callback)) { 415 return; 416 } 417 setTimeout(() => { 418 this.getDataFromUdmfRetry(event, callback); 419 }, 1500); 420 } 421 // 调用componentSnapshot中的createFromBuilder接口截取自定义builder的截图 422 private getComponentSnapshot(): void { 423 this.getUIContext().getComponentSnapshot().createFromBuilder(()=>{this.pixelMapBuilder()}, 424 (error: Error, pixmap: image.PixelMap) => { 425 if(error){ 426 console.log("error: " + JSON.stringify(error)) 427 return; 428 } 429 this.pixmap = pixmap; 430 }) 431 } 432 // 长按50ms时提前准备自定义截图的pixmap 433 private PreDragChange(preDragStatus: PreDragStatus): void { 434 if (preDragStatus == PreDragStatus.ACTION_DETECTING_STATUS) { 435 this.getComponentSnapshot(); 436 } 437 } 438 439 build() { 440 Row() { 441 Column() { 442 Text('start Drag') 443 .fontSize(18) 444 .width('100%') 445 .height(40) 446 .margin(10) 447 .backgroundColor('#008888') 448 Row() { 449 Image($r('app.media.app_icon')) 450 .width(100) 451 .height(100) 452 .draggable(true) 453 .margin({ left: 15 }) 454 .visibility(this.imgState) 455 // 绑定平行手势,可同时触发应用自定义长按手势 456 .parallelGesture(LongPressGesture().onAction(() => { 457 promptAction.showToast({ duration: 100, message: 'Long press gesture trigger' }); 458 })) 459 .onDragStart((event) => { 460 let data: unifiedDataChannel.Image = new unifiedDataChannel.Image(); 461 data.imageUri = 'common/pic/img.png'; 462 let unifiedData = new unifiedDataChannel.UnifiedData(data); 463 event.setData(unifiedData); 464 465 let dragItemInfo: DragItemInfo = { 466 pixelMap: this.pixmap, 467 extraInfo: "this is extraInfo", 468 }; 469 return dragItemInfo; 470 }) 471 // 提前准备拖拽自定义背板图 472 .onPreDrag((status: PreDragStatus) => { 473 this.PreDragChange(status); 474 }) 475 .onDragEnd((event) => { 476 // onDragEnd里取到的result值在接收方onDrop设置 477 if (event.getResult() === DragResult.DRAG_SUCCESSFUL) { 478 promptAction.showToast({ duration: 100, message: 'Drag Success' }); 479 } else if (event.getResult() === DragResult.DRAG_FAILED) { 480 promptAction.showToast({ duration: 100, message: 'Drag failed' }); 481 } 482 }) 483 } 484 485 Text('Drag Target Area') 486 .fontSize(20) 487 .width('100%') 488 .height(40) 489 .margin(10) 490 .backgroundColor('#008888') 491 Row() { 492 Image(this.targetImage) 493 .width(this.imageWidth) 494 .height(this.imageHeight) 495 .draggable(true) 496 .margin({ left: 15 }) 497 .border({ color: Color.Black, width: 1 }) 498 // 控制角标显示类型为MOVE,即不显示角标 499 .onDragMove((event) => { 500 event.setResult(DragResult.DROP_ENABLED) 501 event.dragBehavior = DragBehavior.MOVE 502 }) 503 .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE]) 504 .onDrop((dragEvent?: DragEvent) => { 505 // 获取拖拽数据 506 this.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => { 507 let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords(); 508 let rect: Rectangle = event.getPreviewRect(); 509 this.imageWidth = Number(rect.width); 510 this.imageHeight = Number(rect.height); 511 this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri; 512 this.imgState = Visibility.None; 513 // 显式设置result为successful,则将该值传递给拖出方的onDragEnd 514 event.setResult(DragResult.DRAG_SUCCESSFUL); 515 }) 516 }) 517 } 518 } 519 .width('100%') 520 .height('100%') 521 } 522 .height('100%') 523 } 524} 525 526``` 527 528### 多选拖拽适配 529 530```ts 531import { image } from '@kit.ImageKit'; 532 533@Entry 534@Component 535struct GridEts { 536 @State pixmap: image.PixelMap|undefined = undefined 537 @State numbers: number[] = [] 538 @State isSelectedGrid: boolean[] = [] 539 @State previewData: DragItemInfo[] = [] 540 @State colors: Color[] = [Color.Red, Color.Blue, Color.Brown, Color.Gray, Color.Green, Color.Grey, Color.Orange,Color.Pink ,Color.Yellow] 541 @State numberBadge: number = 0; 542 543 @Styles 544 normalStyles(): void{ 545 .opacity(1.0) 546 } 547 548 @Styles 549 selectStyles(): void{ 550 .opacity(0.4) 551 } 552 553 onPageShow(): void { 554 let i: number = 0 555 for(i=0;i<100;i++){ 556 this.numbers.push(i) 557 this.isSelectedGrid.push(false) 558 this.previewData.push({}) 559 } 560 } 561 562 @Builder 563 RandomBuilder(idx: number) { 564 Column() 565 .backgroundColor(this.colors[idx % 9]) 566 .width(50) 567 .height(50) 568 .opacity(1.0) 569 } 570 571 build() { 572 Column({ space: 5 }) { 573 Grid() { 574 ForEach(this.numbers, (idx: number) => { 575 GridItem() { 576 Column() 577 .backgroundColor(this.colors[idx % 9]) 578 .width(50) 579 .height(50) 580 .opacity(1.0) 581 .id('grid'+idx) 582 } 583 .dragPreview(this.previewData[idx]) 584 .selectable(true) 585 .selected(this.isSelectedGrid[idx]) 586 // 设置多选显示效果 587 .stateStyles({ 588 normal : this.normalStyles, 589 selected: this.selectStyles 590 }) 591 .onClick(()=>{ 592 this.isSelectedGrid[idx] = !this.isSelectedGrid[idx] 593 if (this.isSelectedGrid[idx]) { 594 this.numberBadge++; 595 let gridItemName = 'grid' + idx 596 // 选中状态下提前调用componentSnapshot中的get接口获取pixmap 597 this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{ 598 this.pixmap = pixmap 599 this.previewData[idx] = { 600 pixelMap:this.pixmap 601 } 602 }) 603 } else { 604 this.numberBadge--; 605 } 606 }) 607 // 使能多选拖拽,右上角数量角标需要应用设置numberBadge参数 608 .dragPreviewOptions({numberBadge: this.numberBadge},{isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true}) 609 .onDragStart(()=>{ 610 }) 611 }, (idx: string) => idx) 612 } 613 .columnsTemplate('1fr 1fr 1fr 1fr 1fr') 614 .columnsGap(5) 615 .rowsGap(10) 616 .backgroundColor(0xFAEEE0) 617 }.width('100%').margin({ top: 5 }) 618 } 619} 620``` 621<!--RP1--><!--RP1End-->