1# Drag Event
2
3The drag event represents a drag and drop interaction for transfer of data using a mouse device or gesture. Users can drag data from one and drop it into another to trigger a response. During this process, the drag source provides the data, while the drop target is responsible for receiving and handling the data. With drag and drop, users can easily move, copy, or delete data.
4
5## Basic Concepts
6
7* Drag operation: an operation that begins when a user selects a draggable component, continues when the user drags the component on the screen, and ends when the user releases the component on a droppable component.
8* Drag preview (background): a visual representation of the data being dragged. You can set it by using [CustomerBuilder](../reference/apis-arkui/arkui-ts/ts-types.md#custombuilder8) or [DragItemInfo](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragiteminfo) of [onDragStart](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragstart), or by using the universal attribute [dragPreview](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#dragpreview11).
9* Drag data: data being dragged, encapsulated using the UDMF API [UnifiedData](../reference/apis-arkdata/js-apis-data-unifiedDataChannel.md#unifieddata) to ensure data consistency and security.
10* Drag source: component that initiates the drag operation and provides data, typically with characteristics for responding to dragging.
11* Drop target: component that can receive and process drag data, and is able to perform corresponding actions based on the data being dropped.
12* Drag point: point of contact between the mouse device or finger and the screen. It is used to determine whether data enters a drop target. The determination is based on whether the contact point is within the bounds of the component.
13
14## Drag Process
15
16The drag process can be gesture-based or ​mouse-based. The following describes these two types work.
17
18### ​​Gesture-based Drag Process
19
20​If a drag operation is initiated by a gesture, ArkUI checks whether the current component is draggable. For draggable components ([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)), ArkUI checks whether their [draggable](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#draggable) attribute is set to **true** (this attribute is **true** by default if layered parameters are used); for other components, ArkUI checks whether the **onDragStart** callback is set. If the attribute or callback is set as required, ArkUI starts dragging once the user has long pressed the component for 500 ms or longer, and displays a drag preview once the user has long pressed the component for 800 ms. When the drag operation is used together with a menu controlled by the **isShow** attribute for visibility, avoid delaying the display of the menu by 800 ms after the user's action. Otherwise, unexpected behavior may occur.
21
22Below you can see the drag process initiated by a gesture (finger or stylus).
23
24![en-us_image_0000001562820825](figures/en-us_image_0000001562820825.png)
25
26### ​Mouse-based Drag Process
27
28When a mouse device is used as the pointer, ArkUI starts dragging once the draggable component has been moved with the left mouse button by more than 1 vp.
29
30A drag and drop can occur in a single application, or start in one application and end in another. The following callback events are provided for you to detect the dragging status and intervene in the default dragging behavior of the system.
31
32| Callback Event| Description|
33| ---------------- | ------------------------|
34| [onDragStart](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragstart) | Triggered when a draggable component is dragged.<br>You can use this callback to detect the initiation of dragging behavior. You can also set the drag data and drag preview in this callback. To avoid extra performance overhead, it is recommended that the drag preview be returned in the form of a pixel map, instead of using **customBuilder**.|
35| [onDragEnter](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragenter) | Triggered when the drag point enters the bounds of the component. This callback is called only when the component listens for the [onDrop](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondrop) event.|
36| [onDragMove](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragmove) | Triggered when the drag point moves in the bounds of the component. This callback is called only when the component listens for the **onDrop** event.<br>During the movement, the **setResult** API in [DragEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent) can be used to affect the system appearance in some scenarios.<br>1. Set **DragResult.DROP\_ENABLED**.<br>2. Set **DragResult.DROP\_DISABLED**.|
37| [onDragLeave](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragleave) | Triggered when the drag point leaves the bounds of the component. This callback is called only when the component listens for the **onDrop** event.<br>By default, the **onDragLeave** callback is not called in the following cases:<br>1. An item in a parent component is dragged to one of its child components.<br>2. The layout of the drop target component overlaps that of the drag source component.<br>Since API version 12, the [setDragEventStrictReportingEnabled](../reference/apis-arkui/js-apis-arkui-UIContext.md#setdrageventstrictreportingenabled12) API in [UIContext](../reference/apis-arkui/js-apis-arkui-UIContext.md) can be used to trigger the **onDragLeave** event in a strict fashion.|
38| [onDrop](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondrop) | Triggered when the dragged item is dropped on the component. The drag result must be set in this callback through the **setResult** API in **DragEvent**. Otherwise, the **getResult** API in the **onDragEnd** method of the drag source only returns the default result **DragResult.DRAG_FAILED**.<br>This callback is where you can intervene in the default drop processing behavior. The system preferentially executes the **onDrop** callback and processes the drag data based on the **setResult** API in the callback function.<br>1. If **DragResult.DRAG\_SUCCESSFUL** is set, you need to process the data on your own; the system does not process the data.<br>2. If **DragResult.DRAG\_FAILED** is set, the system does not process the data.<br>3. If **DragResult.DRAG\_CANCELED** is set, the system does not process the data.<br>4. Setting **DragResult.DROP\_ENABLED** or **DragResult.DROP\_DISABLED** will be ignored, producing the same effect as **DragResult.DRAG\_FAILED**.|
39| [onDragEnd](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragend) | Triggered when dragging of the component ends.|
40| [onPreDrag](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#onpredrag12) | Triggered when the component enters a state prior to a drop and drop operation.<br>You can use this callback to listen for the value of [PreDragStatus](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#predragstatus12) to prepare corresponding data.<br>1. **ACTION\_DETECTING\_STATUS**: A drag gesture is being detected. Triggered when the component is long pressed for 50 ms.<br>2. **READY\_TO\_TRIGGER\_DRAG\_ACTION**: The component is ready to be dragged. Triggered when the component is long pressed for 500 ms.<br>3. **PREVIEW\_LIFT\_STARTED**: A lift animation is started. Triggered when the component is long pressed for 800 ms.<br>4. **PREVIEW\_LIFT\_FINISHED**: A lift animation is finished. Triggered at the completion of the lift animation.<br>5. **PREVIEW\_LANDING\_STARTED**: A drop animation is started. Triggered when the drop animation starts.<br>6. **PREVIEW\_LANDING\_FINISHED**: A drop animation is finished. Triggered when the drop animation ends.<br>7. **ACTION\_CANCELED\_BEFORE\_DRAG**: A drop animation is terminated. Triggered when the finger is lifted off the screen after the component enters the **READY_TO_TRIGGER_DRAG_ACTION** state.|
41
42[DragEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent) provides getters to obtain information about the drag operation. The table below lists whether the getters can return valid data in the corresponding drag callbacks.
43| Callback Event| onDragStart | onDragEnter | onDragMove | onDragLeave | onDrop | onDragEnd |
44| - | - | - | - | - | - | - |
45| getData         |—|—|—|—| Supported|—|
46| getSummary      |—| Supported| Supported| Supported| Supported|—|
47| getResult       |—|—|—|—|—| Supported|
48| getPreviewRect  |—|—|—|—| Supported|—|
49| getVelocity/X/Y |—| Supported| Supported| Supported| Supported|—|
50| getWindowX/Y    | Supported| Supported| Supported| Supported| Supported|—|
51| getDisplayX/Y   | Supported| Supported| Supported| Supported| Supported|—|
52| getX/Y          | Supported| Supported| Supported| Supported| Supported|—|
53| behavior        |—|—|—|—|—| Supported|
54
55[DragEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent) also provides setters to transfer information to the system, which may affect how the system handles UI or data. The table below lists the stages in the callbacks where the setters should be executed for the information to be accepted and processed by the system.
56| Callback Event| onDragStart | onDragEnter | onDragMove | onDragLeave | onDrop |
57| - | - | - | - | - | - |
58| useCustomDropAnimation |—|—|—|—| Supported|
59| setData                | Supported|—|—|—|—|
60| setResult              | Supported; can be used to prevent dragging initiation by setting failed or cancel| Supported; not passed as the final result to **onDragEnd**| Supported; not passed as the final result to **onDragEnd**| Supported; not passed as the final result to **onDragEnd** | Supported; passed as the final result to **onDragEnd**|
61| behavior               |—| Supported| Supported| Supported| Supported|
62
63## Drag Preview
64
65The drag preview is an image displayed during the drag and drop operation. It is a visual representation of the drag data, not the component itself. You can set it to any supported image that you want to display to users. The **customBuilder** or **pixelMap** object returned by the **onDragStart** callback can be used to set the drag preview displayed during dragging and moving, with a snapshot of the component being used as the default drag preview during a lift animation. The **customBuilder** or **pixelMap** object set by the **dragPreview** attribute can be used to set the drag preview during a lift animation and dragging. If no custom drag preview is set, the system uses a snapshot of the component by default.
66
67You can set the opacity, rounded corners, shadow, and blur for the drag preview. For details, see [Drag and Drop Control](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md).
68
69![pixelMap](figures/pixelMap.png)
70
71**Constraints**:
72
73* For a container component, if the internal content exceeds the bounds of the component due to **position**, **offset**, or other settings, the component snapshot does not capture the excess content. To show the excess content, you can expand the container scope or customize the container.
74* Regardless of whether you use a custom builder or rely on the default snapshot mechanism, the snapshot process does not support transformation APIs, including [scale](../reference/apis-arkui/arkui-ts/ts-universal-attributes-transformation.md#scale) and [rotate](../reference/apis-arkui/arkui-ts/ts-universal-attributes-transformation.md#rotate).
75
76## General Drag and Drop Adaptation
77
78The following uses the [Image](../reference/apis-arkui/arkui-ts/ts-basic-components-image.md) component as an example to describe the basic procedure for drag and drop development and the precautions to be taken during development.
79
801. Make the component draggable.
81
82   Set the **draggable** attribute to **true** and set the **onDragStart** callback function. In the callback function, you can use UDMF to set the drag data and return the custom drag preview.
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            // The custom drag preview is returned in onDragStart.
102            return dragItemInfo;
103        })
104    ```
105
106   The gesture-based drag operation is initiated by a long press gesture bound at the underlying layer. If a long press gesture is also bound to the dragged component, gesture conflict will occur, resulting in dragging to fail. To address this issue, you can use parallel gestures.
107
108    ```ts
109    .parallelGesture(LongPressGesture().onAction(() => {
110       promptAction.showToast({ duration: 100, message: 'Long press gesture trigger' });
111    }))
112    ```
113
1142. Customize the drag preview.
115
116   Prepare a pixel map for the custom drag preview within the callback triggered by [onPreDrag](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#onpredrag12) after a long press of 50 ms.
117
118    ```ts
119    .onPreDrag((status: PreDragStatus) => {
120        if (preDragStatus == PreDragStatus.ACTION_DETECTING_STATUS) {
121            this.getComponentSnapshot();
122        }
123    })
124    ```
125
126   To generate a pixel map, you can use the [componentSnapshot.createFromBuilder](../reference/apis-arkui/js-apis-arkui-componentSnapshot.md#componentsnapshotcreatefrombuilder) API.
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. To make sure the [onDragLeave](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragleave) event is triggered as expected, use the [setDragEventStrictReportingEnabled](../reference/apis-arkui/js-apis-arkui-UIContext.md#setdrageventstrictreportingenabled12) API.
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. Set the badge displayed during dragging.
176
177   You can set [allowDrop](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#allowdrop) to define the allowed data types for dropping, which affects the badge display. The **COPY** badge is displayed when the drag data matches the allowed data types, the **FORBIDDEN** badge is displayed when it does not, and the **MOVE** badge is displayed if **allowDrop** is not set. The following example allows only data of HYPERLINK and PLAIN\_TEXT types defined in UnifiedData.
178
179    ```ts
180    .allowDrop([uniformTypeDescriptor.UniformDataType.HYPERLINK, uniformTypeDescriptor.UniformDataType.PLAIN_TEXT])
181    ```
182
183   If the **onDrop** callback is implemented, you can control the badge display by setting [DragResult](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragresult10) to **DROP_ENABLED** in **onDragMove** and setting [DragBehavior](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragbehavior10) to **COPY** or **MOVE**. The following code forces the badge to display **MOVE** during a drag operation:
184
185    ```ts
186    .onDragMove((event) => {
187        event.setResult(DragResult.DROP_ENABLED);
188        event.dragBehavior = DragBehavior.MOVE;
189    })
190    ```
191
1925. Receive drag data.
193
194   Set the **onDrop** callback to handle the drag data and determine the drag result.
195
196    ```ts
197    .onDrop((dragEvent?: DragEvent) => {
198        // Obtain the drag data.
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        // Explicitly set the result to successful, and then pass this value to onDragEnd of the drag source.
207        event.setResult(DragResult.DRAG_SUCCESSFUL);
208    })
209    ```
210
211   Data transfer is managed by UDMF, which may experience latency when dealing with large data volumes. Therefore, you are advised to implement a retry mechanism with a 1500 ms delay after the initial data acquisition fails.
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. The drag initiator can detect the result of the drag operation by setting the **onDragEnd** callback.
243
244    ```ts
245    import { promptAction } from '@kit.ArkUI';
246
247    .onDragEnd((event) => {
248        // The result value obtained from onDragEnd is set in onDrop of the drop target.
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**Sample Code**
258
259```ts
260import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData';
261import { promptAction } from '@kit.ArkUI';
262import { BusinessError } from '@kit.BasicServicesKit';
263import { image } from '@kit.ImageKit';
264
265@Entry
266@Component
267struct Index {
268  @State targetImage: string = '';
269  @State imageWidth: number = 100;
270  @State imageHeight: number = 100;
271  @State imgState: Visibility = Visibility.Visible;
272  @State pixmap: image.PixelMap|undefined = undefined
273
274  @Builder
275  pixelMapBuilder() {
276    Column() {
277      Image($r('app.media.startIcon'))
278        .width(120)
279        .height(120)
280        .backgroundColor(Color.Yellow)
281    }
282  }
283
284  getDataFromUdmfRetry(event: DragEvent, callback: (data: DragEvent) => void) {
285    try {
286      let data: UnifiedData = event.getData();
287      if (!data) {
288        return false;
289      }
290      let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords();
291      if (!records || records.length <= 0) {
292        return false;
293      }
294      callback(event);
295      return true;
296    } catch (e) {
297      console.log("getData failed, code: " + (e as BusinessError).code + ", message: " + (e as BusinessError).message);
298      return false;
299    }
300  }
301  // Obtain UDMF data with a retry mechanism of 1500 ms if the initial attempt fails.
302  getDataFromUdmf(event: DragEvent, callback: (data: DragEvent) => void) {
303    if (this.getDataFromUdmfRetry(event, callback)) {
304      return;
305    }
306    setTimeout(() => {
307      this.getDataFromUdmfRetry(event, callback);
308    }, 1500);
309  }
310  // Use the createFromBuilder API of componentSnapshot to capture a snapshot of a custom builder.
311  private getComponentSnapshot(): void {
312    this.getUIContext().getComponentSnapshot().createFromBuilder(()=>{this.pixelMapBuilder()},
313      (error: Error, pixmap: image.PixelMap) => {
314        if(error){
315          console.log("error: " + JSON.stringify(error))
316          return;
317        }
318        this.pixmap = pixmap;
319      })
320  }
321  // Prepare a custom screenshot pixel map after a 50 ms long press is detected.
322  private PreDragChange(preDragStatus: PreDragStatus): void {
323    if (preDragStatus == PreDragStatus.ACTION_DETECTING_STATUS) {
324      this.getComponentSnapshot();
325    }
326  }
327
328  build() {
329    Row() {
330      Column() {
331        Text('start Drag')
332          .fontSize(18)
333          .width('100%')
334          .height(40)
335          .margin(10)
336          .backgroundColor('#008888')
337        Row() {
338          Image($r('app.media.app_icon'))
339            .width(100)
340            .height(100)
341            .draggable(true)
342            .margin({ left: 15 })
343            .visibility(this.imgState)
344            // Bind a parallel gesture to trigger a custom long press gesture.
345            .parallelGesture(LongPressGesture().onAction(() => {
346              promptAction.showToast({ duration: 100, message: 'Long press gesture trigger' });
347            }))
348            .onDragStart((event) => {
349              let data: unifiedDataChannel.Image = new unifiedDataChannel.Image();
350              data.imageUri = 'common/pic/img.png';
351              let unifiedData = new unifiedDataChannel.UnifiedData(data);
352              event.setData(unifiedData);
353
354              let dragItemInfo: DragItemInfo = {
355                pixelMap: this.pixmap,
356                extraInfo: "this is extraInfo",
357              };
358              return dragItemInfo;
359            })
360              // Prepare a custom drag preview in advance.
361            .onPreDrag((status: PreDragStatus) => {
362              this.PreDragChange(status);
363            })
364            .onDragEnd((event) => {
365              // The result value obtained from onDragEnd is set in onDrop of the drop target.
366              if (event.getResult() === DragResult.DRAG_SUCCESSFUL) {
367                promptAction.showToast({ duration: 100, message: 'Drag Success' });
368              } else if (event.getResult() === DragResult.DRAG_FAILED) {
369                promptAction.showToast({ duration: 100, message: 'Drag failed' });
370              }
371            })
372        }
373
374        Text('Drag Target Area')
375          .fontSize(20)
376          .width('100%')
377          .height(40)
378          .margin(10)
379          .backgroundColor('#008888')
380        Row() {
381          Image(this.targetImage)
382            .width(this.imageWidth)
383            .height(this.imageHeight)
384            .draggable(true)
385            .margin({ left: 15 })
386            .border({ color: Color.Black, width: 1 })
387            // Set the drag behavior to MOVE, which means no badge is displayed.
388            .onDragMove((event) => {
389              event.setResult(DragResult.DROP_ENABLED)
390              event.dragBehavior = DragBehavior.MOVE
391            })
392            .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE])
393            .onDrop((dragEvent?: DragEvent) => {
394              // Obtain the drag data.
395              this.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => {
396                let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords();
397                let rect: Rectangle = event.getPreviewRect();
398                this.imageWidth = Number(rect.width);
399                this.imageHeight = Number(rect.height);
400                this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri;
401                this.imgState = Visibility.None;
402                // Explicitly set the result to successful, and then pass this value to onDragEnd of the drag source.
403                event.setResult(DragResult.DRAG_SUCCESSFUL);
404              })
405            })
406        }
407      }
408      .width('100%')
409      .height('100%')
410    }
411    .height('100%')
412  }
413}
414
415```
416
417## Multi-Select Drag and Drop Adaptation
418
419Since API version 12, the **GridItem** and **ListItem** components, which are child components of [Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md) and [List](../reference/apis-arkui/arkui-ts/ts-container-list.md), respectively, support multi-select drag and drop, which can be initiated through the **onDragStart** API.
420
421The following uses **Grid** as an example to describe the basic procedure for multi-select drag and drop development and key considerations during development.
422
4231. Enable multi-select drag and drop.
424
425   Create **GridItem** child components and bind the **onDragStart** callback to them. In addition, set the **GridItem** components to be selectable.
426
427    ```ts
428    Grid() {
429      ForEach(this.numbers, (idx: number) => {
430        GridItem() {
431          Column()
432            .backgroundColor(this.colors[idx % 9])
433            .width(50)
434            .height(50)
435            .opacity(1.0)
436            .id('grid'+idx)
437        }
438        .onDragStart(()=>{})
439        .selectable(true)
440      }, (idx: string) => idx)
441    }
442    ```
443
444   Multi-select drag and drop is disabled by default. To enable it, set **isMultiSelectionEnabled** to **true** in the **DragInteractionOptions** parameter of the [dragPreviewOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-drop.md#dragpreviewoptions11) API. **DragInteractionOptions** also has the **defaultAnimationBeforeLifting** parameter, which, when set to **true**, applies a default scaling down animation as the lift animation for the component.
445
446    ```ts
447    .dragPreviewOptions({isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true})
448    ```
449
450   To maintain the selected state, set the **selected** attribute of the **GridItem** components to **true**. For example, you can use [onClick](../reference/apis-arkui/arkui-ts/ts-universal-events-click.md#onclick) to set a specific component to the selected state.
451
452    ```ts
453    .selected(this.isSelectedGrid[idx])
454    .onClick(()=>{
455        this.isSelectedGrid[idx] = !this.isSelectedGrid[idx]
456    })
457    ```
458
4592. Optimize the multi-select drag and drop performance.
460
461   In multi-select drag and drop scenarios, there is a clustering animation effect when multiple items are selected. This effect captures a snapshot of the selected components currently displayed on the screen, which can incur high performance costs if there are too many selected components. To save on performance, multi-select drag and drop allows for the use of a snapshot from **dragPreview** as the basis for the clustering animation.
462
463    ```ts
464    .dragPreview({
465        pixelMap:this.pixmap
466    })
467    ```
468
469   To obtain a snapshot of a component, you can call the [get](../reference/apis-arkui/js-apis-arkui-componentSnapshot.md#componentsnapshotget-1) API of **componentSnapshot** when the component is selected. The following shows how to use the component ID to obtain the snapshot.
470
471    ```ts
472    @State previewData: DragItemInfo[] = []
473    @State isSelectedGrid: boolean[] = []
474    .onClick(()=>{
475        this.isSelectedGrid[idx] = !this.isSelectedGrid[idx]
476        if (this.isSelectedGrid[idx]) {
477            let gridItemName = 'grid' + idx
478            this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{
479                this.pixmap = pixmap
480                this.previewData[idx] = {
481                    pixelMap:this.pixmap
482                }
483            })
484        }
485    })
486    ```
487
4883. Set the multi-select display effects.
489
490    Use [stateStyles](../reference/apis-arkui/arkui-ts/ts-universal-attributes-polymorphic-style.md#statestyles) to set display effects for selected and unselected states for easy distinction.
491
492    ```ts
493    @Styles
494    normalStyles(): void{
495      .opacity(1.0)
496    }
497
498    @Styles
499    selectStyles(): void{
500      .opacity(0.4)
501    }
502
503    .stateStyles({
504      normal : this.normalStyles,
505      selected: this.selectStyles
506    })
507    ```
508
5094. Adapt the number badge.
510
511    Configure the number badge for multi-select drag and drop using the **numberBadge** parameter in **dragPreviewOptions**, adjusting it based on the number of selected items.
512
513    ```ts
514    @State numberBadge: number = 0;
515
516    .onClick(()=>{
517        this.isSelectedGrid[idx] = !this.isSelectedGrid[idx]
518        if (this.isSelectedGrid[idx]) {
519          this.numberBadge++;
520        } else {
521          this.numberBadge--;
522      }
523    })
524    // Set the numberBadge parameter in dragPreviewOptions for the number badge in multi-select scenarios.
525    .dragPreviewOptions({numberBadge: this.numberBadge})
526    ```
527
528**Sample Code**
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          // Set the multi-select display effects.
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              // Call the get API in componentSnapshot to obtain the component snapshot pixel map on selection.
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          // Enable multiselect and set the number badge.
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-->
622