• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![zh-cn_image_0000001562820825](figures/zh-cn_image_0000001562820825.png)
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.None206        // 显式设置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-->