• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Implementing Unified Drag and Drop
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @jiangtao92-->
5<!--Designer: @piggyguy-->
6<!--Tester: @songyanhong-->
7<!--Adviser: @HelloCrease-->
8
9Unified drag and drop refers to a data transfer interaction triggered by a mouse device or gesture. Users can drag data from one component (the drag source) and drop it into another (the drop target) to initiate a response. In this interaction, the drag source provides the data, while the drop target receives and processes it, thereby enabling users to easily move, copy, or delete data.
10
11## Basic Concepts
12
13* 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.
14* 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).
15* 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.
16* Drag source: component that initiates the drag operation and provides data, typically with characteristics for responding to dragging.
17* Drop target: component that can receive and process drag data, and is able to perform corresponding actions based on the data being dropped.
18* 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.
19
20## Drag Process
21
22The drag process encompasses both gesture-based dragging and mouse-based dragging. This distinction helps clarify the timing of callback event triggers.
23
24### ​​Gesture-based Drag Process
25
26​If a drag operation is triggered by a long press gesture, ArkUI checks whether the current component is draggable before initiating the drag. For components that are draggable by default ([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), [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** (the system predefines the default value of this attribute for these components through [system resources](../quick-start/resource-categories-and-access.md#system-resources)); for other components, ArkUI also 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.
27
28Below you can see the drag process initiated by a gesture (finger or stylus).
29
30![en-us_image_0000001562820825](figures/en-us_image_0000001562820825.png)
31
32### ​Mouse-based Drag Process
33
34When 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.
35
36A 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.
37
38| Callback Event| Description|
39| ---------------- | ------------------------|
40| [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**.|
41| [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.|
42| [onDragMove](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragmove) | Triggered when the drag point moves within the bounds of the component, but only if 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#dragevent7) can be used to affect the system appearance in some scenarios.<br>1. Set **DragResult.DROP\_ENABLED**.<br>2. Set **DragResult.DROP\_DISABLED**.|
43| [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/arkts-apis-uicontext-dragcontroller.md#setdrageventstrictreportingenabled12) API in [UIContext](../reference/apis-arkui/arkts-apis-uicontext-uicontext.md) can be used to trigger the **onDragLeave** event in a strict fashion.|
44| [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\_SUCCESSFUL**.|
45| [onDragEnd](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#ondragend10) | Triggered when dragging of the component ends.|
46| [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.|
47
48[DragEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent7) 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.
49| Callback Event| onDragStart | onDragEnter | onDragMove | onDragLeave | onDrop | onDragEnd |
50| - | - | - | - | - | - | - |
51| getData         |—|—|—|—| Supported|—|
52| getSummary      |—| Supported| Supported| Supported| Supported|—|
53| getResult       |—|—|—|—|—| Supported|
54| getPreviewRect  |—|—|—|—| Supported|—|
55| getVelocity/X/Y |—| Supported| Supported| Supported| Supported|—|
56| getWindowX/Y    | Supported| Supported| Supported| Supported| Supported|—|
57| getDisplayX/Y   | Supported| Supported| Supported| Supported| Supported|—|
58| getX/Y          | Supported| Supported| Supported| Supported| Supported|—|
59| behavior        |—|—|—|—|—| Supported|
60
61[DragEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent7) 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.
62| Callback Event| onDragStart | onDragEnter | onDragMove | onDragLeave | onDrop |
63| - | - | - | - | - | - |
64| useCustomDropAnimation |—|—|—|—| Supported|
65| setData                | Supported|—|—|—|—|
66| 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**|
67| behavior               |—| Supported| Supported| Supported| Supported|
68
69## Drag Preview
70
71The 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.
72
73You 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).
74
75![pixelMap](figures/pixelMap.png)
76
77**Constraints**:
78
79* 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.
80* 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).
81
82## Drag and Drop Capability
83
84### General Drag and Drop Adaptation
85
86The 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.
87
881. Make the component draggable.
89
90   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.
91
92    ```ts
93    import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData';
94
95    Image($r('app.media.app_icon'))
96        .width(100)
97        .height(100)
98        .draggable(true)
99        .onDragStart((event) => {
100            let data: unifiedDataChannel.Image = new unifiedDataChannel.Image();
101            data.imageUri = 'common/pic/img.png';
102            let unifiedData = new unifiedDataChannel.UnifiedData(data);
103            event.setData(unifiedData);
104
105            let dragItemInfo: DragItemInfo = {
106            pixelMap: this.pixmap,
107            extraInfo: "this is extraInfo",
108            };
109            // The custom drag preview is returned in onDragStart.
110            return dragItemInfo;
111        })
112    ```
113
114   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.
115
116    ```ts
117    .parallelGesture(LongPressGesture().onAction(() => {
118       this.getUIContext().getPromptAction().showToast({ duration: 100, message: 'Long press gesture trigger' });
119    }))
120    ```
121
1222. Customize the drag preview.
123
124   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.
125
126    ```ts
127    .onPreDrag((status: PreDragStatus) => {
128        if (preDragStatus == PreDragStatus.ACTION_DETECTING_STATUS) {
129            this.getComponentSnapshot();
130        }
131    })
132    ```
133
134   To generate a pixel map, you can use [this.getUIContext().getComponentSnapshot().createFromBuilder()](../reference/apis-arkui/arkts-apis-uicontext-componentsnapshot.md#createfrombuilder12).
135
136      ```ts
137      @Builder
138      pixelMapBuilder() {
139          Column() {
140            Image($r('app.media.startIcon'))
141              .width(120)
142              .height(120)
143              .backgroundColor(Color.Yellow)
144          }
145        }
146        private getComponentSnapshot(): void {
147        this.getUIContext().getComponentSnapshot().createFromBuilder(()=>{this.pixelMapBuilder()},
148        (error: Error, pixmap: image.PixelMap) => {
149            if(error){
150              console.error("error: " + JSON.stringify(error))
151              return;
152            }
153            this.pixmap = pixmap;
154        })
155      }
156      ```
157
1583. 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/arkts-apis-uicontext-dragcontroller.md#setdrageventstrictreportingenabled12) API.
159
160    ```ts
161    import { UIAbility } from '@kit.AbilityKit';
162    import { window, UIContext } from '@kit.ArkUI';
163
164    export default class EntryAbility extends UIAbility {
165      onWindowStageCreate(windowStage: window.WindowStage): void {
166        windowStage.loadContent('pages/Index', (err, data) => {
167          if (err.code) {
168            return;
169          }
170          windowStage.getMainWindow((err, data) => {
171            if (err.code) {
172              return;
173            }
174            let windowClass: window.Window = data;
175            let uiContext: UIContext = windowClass.getUIContext();
176            uiContext.getDragController().setDragEventStrictReportingEnabled(true);
177          });
178        });
179      }
180    }
181    ```
182
1834. Set the badge displayed during dragging.
184
185   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.
186
187    ```ts
188    .allowDrop([uniformTypeDescriptor.UniformDataType.HYPERLINK, uniformTypeDescriptor.UniformDataType.PLAIN_TEXT])
189    ```
190
191   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:
192
193    ```ts
194    .onDragMove((event) => {
195        event.setResult(DragResult.DROP_ENABLED);
196        event.dragBehavior = DragBehavior.MOVE;
197    })
198    ```
199
2005. Receive drag data.
201
202   Set the **onDrop** callback to handle the drag data and determine the drag result.
203
204    ```ts
205    .onDrop((dragEvent?: DragEvent) => {
206        // Obtain the drag data.
207        this.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => {
208        let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords();
209        let rect: Rectangle = event.getPreviewRect();
210        this.imageWidth = Number(rect.width);
211        this.imageHeight = Number(rect.height);
212        this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri;
213        this.imgState = Visibility.None;
214        // Explicitly set the result to successful, and then pass this value to onDragEnd of the drag source.
215        event.setResult(DragResult.DRAG_SUCCESSFUL);
216    })
217    ```
218
219   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.
220
221    ```ts
222    getDataFromUdmfRetry(event: DragEvent, callback: (data: DragEvent) => void) {
223       try {
224         let data: UnifiedData = event.getData();
225         if (!data) {
226           return false;
227         }
228         let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords();
229         if (!records || records.length <= 0) {
230           return false;
231         }
232         callback(event);
233         return true;
234       } catch (e) {
235         console.error("getData failed, code: " + (e as BusinessError).code + ", message: " + (e as BusinessError).message);
236         return false;
237       }
238    }
239
240    getDataFromUdmf(event: DragEvent, callback: (data: DragEvent) => void) {
241      if (this.getDataFromUdmfRetry(event, callback)) {
242        return;
243      }
244      setTimeout(() => {
245        this.getDataFromUdmfRetry(event, callback);
246      }, 1500);
247    }
248    ```
249
2506. The drag initiator can detect the result of the drag operation by setting the **onDragEnd** callback.
251
252    ```ts
253    import { promptAction } from '@kit.ArkUI';
254
255    .onDragEnd((event) => {
256        // The result value obtained from onDragEnd is set in onDrop of the drop target.
257      if (event.getResult() === DragResult.DRAG_SUCCESSFUL) {
258        this.getUIContext().getPromptAction().showToast({ duration: 100, message: 'Drag Success' });
259      } else if (event.getResult() === DragResult.DRAG_FAILED) {
260        this.getUIContext().getPromptAction().showToast({ duration: 100, message: 'Drag failed' });
261      }
262    })
263    ```
264
265**Sample Code**
266
267```ts
268import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData';
269import { promptAction } from '@kit.ArkUI';
270import { BusinessError } from '@kit.BasicServicesKit';
271import { image } from '@kit.ImageKit';
272
273@Entry
274@Component
275struct Index {
276  @State targetImage: string = '';
277  @State imageWidth: number = 100;
278  @State imageHeight: number = 100;
279  @State imgState: Visibility = Visibility.Visible;
280  @State pixmap: image.PixelMap|undefined = undefined
281
282  @Builder
283  pixelMapBuilder() {
284    Column() {
285      Image($r('app.media.startIcon'))
286        .width(120)
287        .height(120)
288        .backgroundColor(Color.Yellow)
289    }
290  }
291
292  getDataFromUdmfRetry(event: DragEvent, callback: (data: DragEvent) => void) {
293    try {
294      let data: UnifiedData = event.getData();
295      if (!data) {
296        return false;
297      }
298      let records: Array<unifiedDataChannel.UnifiedRecord> = data.getRecords();
299      if (!records || records.length <= 0) {
300        return false;
301      }
302      callback(event);
303      return true;
304    } catch (e) {
305      console.error("getData failed, code: " + (e as BusinessError).code + ", message: " + (e as BusinessError).message);
306      return false;
307    }
308  }
309  // Obtain UDMF data with a retry mechanism of 1500 ms if the initial attempt fails.
310  getDataFromUdmf(event: DragEvent, callback: (data: DragEvent) => void) {
311    if (this.getDataFromUdmfRetry(event, callback)) {
312      return;
313    }
314    setTimeout(() => {
315      this.getDataFromUdmfRetry(event, callback);
316    }, 1500);
317  }
318  // Use the createFromBuilder API of componentSnapshot to capture a snapshot of a custom builder.
319  private getComponentSnapshot(): void {
320    this.getUIContext().getComponentSnapshot().createFromBuilder(()=>{this.pixelMapBuilder()},
321      (error: Error, pixmap: image.PixelMap) => {
322        if(error){
323          console.error("error: " + JSON.stringify(error))
324          return;
325        }
326        this.pixmap = pixmap;
327      })
328  }
329  // Prepare a custom screenshot pixel map after a 50 ms long press is detected.
330  private PreDragChange(preDragStatus: PreDragStatus): void {
331    if (preDragStatus == PreDragStatus.ACTION_DETECTING_STATUS) {
332      this.getComponentSnapshot();
333    }
334  }
335
336  build() {
337    Row() {
338      Column() {
339        Text('start Drag')
340          .fontSize(18)
341          .width('100%')
342          .height(40)
343          .margin(10)
344          .backgroundColor('#008888')
345        Row() {
346          Image($r('app.media.app_icon'))
347            .width(100)
348            .height(100)
349            .draggable(true)
350            .margin({ left: 15 })
351            .visibility(this.imgState)
352            // Bind a parallel gesture to trigger a custom long press gesture.
353            .parallelGesture(LongPressGesture().onAction(() => {
354              this.getUIContext().getPromptAction().showToast({ duration: 100, message: 'Long press gesture trigger' });
355            }))
356            .onDragStart((event) => {
357              let data: unifiedDataChannel.Image = new unifiedDataChannel.Image();
358              data.imageUri = 'common/pic/img.png';
359              let unifiedData = new unifiedDataChannel.UnifiedData(data);
360              event.setData(unifiedData);
361
362              let dragItemInfo: DragItemInfo = {
363                pixelMap: this.pixmap,
364                extraInfo: "this is extraInfo",
365              };
366              return dragItemInfo;
367            })
368              // Prepare a custom drag preview in advance.
369            .onPreDrag((status: PreDragStatus) => {
370              this.PreDragChange(status);
371            })
372            .onDragEnd((event) => {
373              // The result value obtained from onDragEnd is set in onDrop of the drop target.
374              if (event.getResult() === DragResult.DRAG_SUCCESSFUL) {
375                this.getUIContext().getPromptAction().showToast({ duration: 100, message: 'Drag Success' });
376              } else if (event.getResult() === DragResult.DRAG_FAILED) {
377                this.getUIContext().getPromptAction().showToast({ duration: 100, message: 'Drag failed' });
378              }
379            })
380        }
381
382        Text('Drag Target Area')
383          .fontSize(20)
384          .width('100%')
385          .height(40)
386          .margin(10)
387          .backgroundColor('#008888')
388        Row() {
389          Image(this.targetImage)
390            .width(this.imageWidth)
391            .height(this.imageHeight)
392            .draggable(true)
393            .margin({ left: 15 })
394            .border({ color: Color.Black, width: 1 })
395            // Set the drag behavior to MOVE, which means no badge is displayed.
396            .onDragMove((event) => {
397              event.setResult(DragResult.DROP_ENABLED)
398              event.dragBehavior = DragBehavior.MOVE
399            })
400            .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE])
401            .onDrop((dragEvent?: DragEvent) => {
402              // Obtain the drag data.
403              this.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => {
404                let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords();
405                let rect: Rectangle = event.getPreviewRect();
406                this.imageWidth = Number(rect.width);
407                this.imageHeight = Number(rect.height);
408                this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri;
409                this.imgState = Visibility.None;
410                // Explicitly set the result to successful, and then pass this value to onDragEnd of the drag source.
411                event.setResult(DragResult.DRAG_SUCCESSFUL);
412              })
413            })
414        }
415      }
416      .width('100%')
417      .height('100%')
418    }
419    .height('100%')
420  }
421}
422
423```
424![commonDrag](figures/commonDrag.gif)
425
426### Multi-Select Drag and Drop Adaptation
427
428Since 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.
429
430The following uses **Grid** as an example to describe the basic procedure for multi-select drag and drop development and key considerations during development.
431
4321. Enable multi-select drag and drop.
433
434   Create **GridItem** child components and bind the **onDragStart** callback to them. In addition, set the **GridItem** components to be selectable.
435
436    ```ts
437    Grid() {
438      ForEach(this.numbers, (idx: number) => {
439        GridItem() {
440          Column()
441            .backgroundColor(Color.Blue)
442            .width(50)
443            .height(50)
444            .opacity(1.0)
445            .id('grid'+idx)
446        }
447        .onDragStart(()=>{})
448        .selectable(true)
449      }, (idx: string) => idx)
450    }
451    ```
452
453   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.
454
455    ```ts
456    .dragPreviewOptions({isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true})
457    ```
458
459   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.
460
461    ```ts
462    .selected(this.isSelectedGrid[idx])
463    .onClick(()=>{
464        this.isSelectedGrid[idx] = !this.isSelectedGrid[idx]
465    })
466    ```
467
4682. Optimize the multi-select drag and drop performance.
469
470   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.
471
472    ```ts
473    .dragPreview({
474        pixelMap:this.pixmap
475    })
476    ```
477
478   To obtain a snapshot of a component, you can call the [this.getUIContext().getComponentSnapshot().get()](../reference/apis-arkui/arkts-apis-uicontext-componentsnapshot.md#get12) API when the component is selected. The following shows how to use the component ID to obtain the snapshot.
479
480    ```ts
481    @State previewData: DragItemInfo[] = []
482    @State isSelectedGrid: boolean[] = []
483    .onClick(()=>{
484        this.isSelectedGrid[idx] = !this.isSelectedGrid[idx]
485        if (this.isSelectedGrid[idx]) {
486            let gridItemName = 'grid' + idx
487            this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{
488                this.pixmap = pixmap
489                this.previewData[idx] = {
490                    pixelMap:this.pixmap
491                }
492            })
493        }
494    })
495    ```
496
4973. Set the multi-select display effects.
498
499    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.
500
501    ```ts
502    @Styles
503    normalStyles(): void{
504      .opacity(1.0)
505    }
506
507    @Styles
508    selectStyles(): void{
509      .opacity(0.4)
510    }
511
512    .stateStyles({
513      normal : this.normalStyles,
514      selected: this.selectStyles
515    })
516    ```
517
5184. Adapt the number badge.
519
520    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.
521
522    ```ts
523    @State numberBadge: number = 0;
524
525    .onClick(()=>{
526        this.isSelectedGrid[idx] = !this.isSelectedGrid[idx]
527        if (this.isSelectedGrid[idx]) {
528          this.numberBadge++;
529        } else {
530          this.numberBadge--;
531      }
532    })
533    // Set the numberBadge parameter in dragPreviewOptions for the number badge in multi-select scenarios.
534    .dragPreviewOptions({numberBadge: this.numberBadge})
535    ```
536
537**Sample Code**
538
539```ts
540import { image } from '@kit.ImageKit';
541
542@Entry
543@Component
544struct GridEts {
545  @State pixmap: image.PixelMap|undefined = undefined
546  @State numbers: number[] = []
547  @State isSelectedGrid: boolean[] = []
548  @State previewData: DragItemInfo[] = []
549  @State numberBadge: number = 0;
550
551  @Styles
552  normalStyles(): void{
553    .opacity(1.0)
554  }
555
556  @Styles
557  selectStyles(): void{
558    .opacity(0.4)
559  }
560
561  onPageShow(): void {
562    let i: number = 0
563    for(i=0;i<100;i++){
564      this.numbers.push(i)
565      this.isSelectedGrid.push(false)
566      this.previewData.push({})
567    }
568  }
569
570  @Builder
571  RandomBuilder(idx: number) {
572    Column()
573      .backgroundColor(Color.Blue)
574      .width(50)
575      .height(50)
576      .opacity(1.0)
577  }
578
579  build() {
580    Column({ space: 5 }) {
581      Grid() {
582        ForEach(this.numbers, (idx: number) => {
583          GridItem() {
584            Column()
585              .backgroundColor(Color.Blue)
586              .width(50)
587              .height(50)
588              .opacity(1.0)
589              .id('grid'+idx)
590          }
591          .dragPreview(this.previewData[idx])
592          .selectable(true)
593          .selected(this.isSelectedGrid[idx])
594          // Set the multi-select display effects.
595          .stateStyles({
596            normal : this.normalStyles,
597            selected: this.selectStyles
598          })
599          .onClick(()=>{
600            this.isSelectedGrid[idx] = !this.isSelectedGrid[idx]
601            if (this.isSelectedGrid[idx]) {
602              this.numberBadge++;
603              let gridItemName = 'grid' + idx
604              // Call the get API in componentSnapshot to obtain the component snapshot pixel map on selection.
605              this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{
606                this.pixmap = pixmap
607                this.previewData[idx] = {
608                  pixelMap:this.pixmap
609                }
610              })
611            } else {
612              this.numberBadge--;
613            }
614          })
615          // Enable multi-select and set the number badge.
616          .dragPreviewOptions({numberBadge: this.numberBadge},{isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true})
617          .onDragStart(()=>{
618          })
619        }, (idx: string) => idx)
620      }
621      .columnsTemplate('1fr 1fr 1fr 1fr 1fr')
622      .columnsGap(5)
623      .rowsGap(10)
624      .backgroundColor(0xFAEEE0)
625    }.width('100%').margin({ top: 5 })
626  }
627}
628```
629![multiDrag](figures/multiDrag.gif)
630
631### Custom Drop Animation Adaptation
632
633When you need to create custom drop animations, you can disable the default system animations. Since API version 18, ArkUI provides the [executeDropAnimation](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#executedropanimation18) API, which allows you to define your own drop animations. The following provides step-by-step instructions using the **Image** component as an example, along with key points to keep in mind during development.
634
6351. Configure drag and drop settings for the component.
636   Set **draggable** to **true** and configure callbacks such as **onDragStart** and **onDragEnd**.
637    ```ts
638    Image($r('app.media.app_icon'))
639      .width(100)
640      .height(100)
641      .draggable(true)
642      .margin({ left: 15 ,top: 40})
643      .visibility(this.imgState)
644      .onDragStart((event) => {})
645      .onDragEnd((event) => {})
646    ```
6472. Define your custom animation.
648
649   Use the [animateTo](../reference/apis-arkui/arkts-apis-uicontext-uicontext.md#animateto) API to create a custom animation. For example, you can change the size of the component.
650
651    ```ts
652      customDropAnimation = () => {
653        this.getUIContext().animateTo({ duration: 1000, curve: Curve.EaseOut, playMode: PlayMode.Normal }, () => {
654          this.imageWidth = 200;
655          this.imageHeight = 200;
656          this.imgState = Visibility.None;
657        })
658      }
659    ```
660
6613. Trigger the custom drop animation.
662
663   Configure the **onDrop** callback to receive the drag data. Execute your custom drop animation using the [executeDropAnimation](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#executedropanimation18) API. Set [useCustomDropAnimation](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent7) to **true** to disable the default system animation.
664
665    ```ts
666      Column() {
667        Image(this.targetImage)
668          .width(this.imageWidth)
669          .height(this.imageHeight)
670      }
671      .draggable(true)
672      .margin({ left: 15 })
673      .border({ color: Color.Black, width: 1 })
674      .allowDrop([udmfType.UniformDataType.IMAGE])
675      .onDrop((dragEvent: DragEvent) => {
676        let records: Array<unifiedDataChannel.UnifiedRecord> = dragEvent.getData().getRecords();
677        let rect: Rectangle = dragEvent.getPreviewRect();
678        this.imageWidth = Number(rect.width);
679        this.imageHeight = Number(rect.height);
680        this.targetImage = (records[0] as udmf.Image).imageUri;
681        dragEvent.useCustomDropAnimation = true;
682        dragEvent.executeDropAnimation(this.customDropAnimation)
683      })
684    ```
685
686**Sample Code**
687
688```ts
689import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData';
690import { promptAction } from '@kit.ArkUI';
691
692
693@Entry
694@Component
695struct DropAnimationExample {
696  @State targetImage: string = '';
697  @State imageWidth: number = 100;
698  @State imageHeight: number = 100;
699  @State imgState: Visibility = Visibility.Visible;
700
701  customDropAnimation =
702    () => {
703      this.getUIContext().animateTo({ duration: 1000, curve: Curve.EaseOut, playMode: PlayMode.Normal }, () => {
704        this.imageWidth = 200;
705        this.imageHeight = 200;
706        this.imgState = Visibility.None;
707      })
708    }
709
710  build() {
711    Row() {
712      Column() {
713        Image($r('app.media.app_icon'))
714          .width(100)
715          .height(100)
716          .draggable(true)
717          .margin({ left: 15 ,top: 40})
718          .visibility(this.imgState)
719          .onDragStart((event) => {
720          })
721          .onDragEnd((event) => {
722            if (event.getResult() === DragResult.DRAG_SUCCESSFUL) {
723              console.info('Drag Success');
724            } else if (event.getResult() === DragResult.DRAG_FAILED) {
725              console.info('Drag failed');
726            }
727          })
728      }.width('45%')
729      .height('100%')
730      Column() {
731        Text('Drag Target Area')
732          .fontSize(20)
733          .width(180)
734          .height(40)
735          .textAlign(TextAlign.Center)
736          .margin(10)
737          .backgroundColor('rgb(240,250,255)')
738        Column() {
739          Image(this.targetImage)
740            .width(this.imageWidth)
741            .height(this.imageHeight)
742        }
743        .draggable(true)
744        .margin({ left: 15 })
745        .border({ color: Color.Black, width: 1 })
746        .allowDrop([uniformTypeDescriptor.UniformDataType.IMAGE])
747        .onDrop((dragEvent: DragEvent) => {
748          let records: Array<unifiedDataChannel.UnifiedRecord> = dragEvent.getData().getRecords();
749          let rect: Rectangle = dragEvent.getPreviewRect();
750          this.imageWidth = Number(rect.width);
751          this.imageHeight = Number(rect.height);
752          this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri;
753          dragEvent.useCustomDropAnimation = true;
754          dragEvent.executeDropAnimation(this.customDropAnimation)
755        })
756        .width(this.imageWidth)
757        .height(this.imageHeight)
758      }.width('45%')
759      .height('100%')
760      .margin({ left: '5%' })
761    }
762    .height('100%')
763  }
764}
765```
766![executeDropAnimation](figures/executeDropAnimation.gif)
767
768### Handling Large Volumes of Data
769
770When dealing with a large number of items or large data volumes during drag and drop operations, processing all the data at once can negatively impact the user experience. The following uses the **Grid** component as an example to provide recommended practices for handling large data volumes during drag and drop operations, along with key points to keep in mind during development.
771
7721. Enable multi-select drag and drop.
773
774   Create **GridItem** child components and set their state to be selectable. Enable multi-select drag and drop by setting **isMultiSelectionEnabled** to **true**. Use the selected state to distinguish whether an item is selected.
775
776    ```ts
777    Grid() {
778      ForEach(this.numbers, (idx: number) => {
779        GridItem() {
780          Column()
781            .backgroundColor(Color.Blue)
782            .width(50)
783            .height(50)
784            .opacity(1.0)
785            .id('grid'+idx)
786        }
787        .dragPreview(this.previewData[idx])
788        .dragPreviewOptions({numberBadge: this.numberBadge},{isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true})
789        .selectable(true)
790        .selected(this.isSelectedGrid[idx])
791        .stateStyles({
792          normal : this.normalStyles,
793          selected: this.selectStyles
794        })
795        .onClick(() => {
796          this.isSelectedGrid[idx] = !this.isSelectedGrid[idx];
797        })
798      }, (idx: string) => idx)
799    }
800    ```
801
802   To maintain performance, limit the maximum number of items for multi-select drag and drop to 500.
803
804    ```ts
805    onPageShow(): void {
806      let i: number = 0
807      for(i=0;i<500;i++){
808        this.numbers.push(i)
809        this.isSelectedGrid.push(false)
810        this.previewData.push({})
811      }
812    }
813    ```
8142. Add data incrementally when items are selected.
815
816   When dealing with large data volumes, you are advised to add data records incrementally using [addRecord](../reference/apis-arkdata/js-apis-data-unifiedDataChannel.md#addrecord) as items are selected. This avoids significant performance overhead from processing all data at once during the drag operation.
817
818    ```ts
819    .onClick(()=>{
820      this.isSelectedGrid[idx] = !this.isSelectedGrid[idx];
821      if (this.isSelectedGrid[idx]) {
822        let data: UDC.Image = new UDC.Image();
823        data.uri = '/resource/image.jpeg';
824        if (!this.unifiedData) {
825          this.unifiedData = new UDC.UnifiedData(data);
826        }
827        this.unifiedData.addRecord(data);
828        this.numberBadge++;
829        let gridItemName = 'grid' + idx;
830        // Call the get API in componentSnapshot to obtain the component snapshot pixel map on selection.
831        this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{
832          this.pixmap = pixmap;
833          this.previewData[idx] = {
834            pixelMap:this.pixmap
835          }
836        })
837      } else {
838        this.numberBadge--;
839        for (let i=0; i<this.isSelectedGrid.length; i++) {
840          if (this.isSelectedGrid[i] === true) {
841            let data: UDC.Image = new UDC.Image();
842            data.uri = '/resource/image.jpeg';
843            if (!this.unifiedData) {
844              this.unifiedData = new UDC.UnifiedData(data);
845            }
846            this.unifiedData.addRecord(data);
847          }
848        }
849      }
850    })
851    ```
852
8533. Prepare data in advance.
854
855   Use the **onPreDrag** callback to receive a signal that a drag operation is about to start. If the data volume is large, prepare the data in advance.
856
857    ```ts
858    .onPreDrag((status: PreDragStatus) => {
859      if (status == PreDragStatus.PREPARING_FOR_DRAG_DETECTION) {
860        this.loadData()
861      }
862    })
863    ```
864
8654. Block the drag operation if data preparation is not complete.
866
867   When initiating a drag operation, check whether the data is ready. If the data is not yet ready, send a [WAITING](../reference/apis-arkui/js-apis-arkui-dragController.md#dragstartrequeststatus18) signal to the system to block the drag operation. In this case, if the user performs a drag gesture, the drag preview will remain stationary until the application sends a READY signal or the maximum blocking time limit (5 seconds) is exceeded. If the data is ready, you can directly set it to [dragEvent](../reference/apis-arkui/arkui-ts/ts-universal-events-drag-drop.md#dragevent7). Note that when using the blocking feature, you need to save the current **dragEvent** and set the data when preparation is complete. In non-blocking scenarios, saving the current **dragEvent** is not recommended.
868
869    ```ts
870    .onDragStart((event: DragEvent) => {
871      this.dragEvent = event;
872      if (this.finished == false) {
873        this.getUIContext().getDragController().notifyDragStartRequest(dragController.DragStartRequestStatus.WAITING);
874      } else {
875        event.setData(this.unifiedData);
876      }
877    })
878    ```
879
880**Sample Code**
881
882```ts
883import { image } from '@kit.ImageKit';
884import { unifiedDataChannel as UDC } from '@kit.ArkData';
885import { dragController } from '@kit.ArkUI';
886
887@Entry
888@Component
889struct GridEts {
890  @State pixmap: image.PixelMap|undefined = undefined
891  @State numbers: number[] = []
892  @State isSelectedGrid: boolean[] = []
893  @State previewData: DragItemInfo[] = []
894  @State numberBadge: number = 0;
895  unifiedData: UnifiedData|undefined = undefined;
896  timeout: number = 1
897  finished: boolean = false;
898  dragEvent: DragEvent|undefined;
899
900  @Styles
901  normalStyles(): void{
902    .opacity(1.0)
903  }
904
905  @Styles
906  selectStyles(): void{
907    .opacity(0.4)
908  }
909
910  onPageShow(): void {
911    let i: number = 0
912    for(i=0;i<500;i++){
913      this.numbers.push(i)
914      this.isSelectedGrid.push(false)
915      this.previewData.push({})
916    }
917  }
918
919  loadData() {
920    this.timeout = setTimeout(() => {
921      // State after data preparation is complete.
922      if (this.dragEvent) {
923        this.dragEvent.setData(this.unifiedData);
924      }
925      this.getUIContext().getDragController().notifyDragStartRequest(dragController.DragStartRequestStatus.READY);
926      this.finished = true;
927    }, 4000);
928  }
929
930  @Builder
931  RandomBuilder(idx: number) {
932    Column()
933      .backgroundColor(Color.Blue)
934      .width(50)
935      .height(50)
936      .opacity(1.0)
937  }
938
939  build() {
940    Column({ space: 5 }) {
941      Button('Select All')
942        .onClick(() => {
943          for (let i=0;i<this.isSelectedGrid.length;i++) {
944            if (this.isSelectedGrid[i] === false) {
945              this.numberBadge++;
946              this.isSelectedGrid[i] = true;
947              let data: UDC.Image = new UDC.Image();
948              data.uri = '/resource/image.jpeg';
949              if (!this.unifiedData) {
950                this.unifiedData = new UDC.UnifiedData(data);
951              }
952              this.unifiedData.addRecord(data);
953              let gridItemName = 'grid' + i;
954              // Call the get API in componentSnapshot to obtain the component snapshot pixel map on selection.
955              this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{
956                this.pixmap = pixmap
957                this.previewData[i] = {
958                  pixelMap:this.pixmap
959                }
960              })
961            }
962          }
963        })
964      Grid() {
965        ForEach(this.numbers, (idx: number) => {
966          GridItem() {
967            Column()
968              .backgroundColor(Color.Blue)
969              .width(50)
970              .height(50)
971              .opacity(1.0)
972              .id('grid'+idx)
973          }
974          .dragPreview(this.previewData[idx])
975          .selectable(true)
976          .selected(this.isSelectedGrid[idx])
977          // Set the multi-select display effects.
978          .stateStyles({
979            normal : this.normalStyles,
980            selected: this.selectStyles
981          })
982          .onClick(()=>{
983            this.isSelectedGrid[idx] = !this.isSelectedGrid[idx];
984            if (this.isSelectedGrid[idx]) {
985              let data: UDC.Image = new UDC.Image();
986              data.uri = '/resource/image.jpeg';
987              if (!this.unifiedData) {
988                this.unifiedData = new UDC.UnifiedData(data);
989              }
990              this.unifiedData.addRecord(data);
991              this.numberBadge++;
992              let gridItemName = 'grid' + idx;
993              // Call the get API in componentSnapshot to obtain the component snapshot pixel map on selection.
994              this.getUIContext().getComponentSnapshot().get(gridItemName, (error: Error, pixmap: image.PixelMap)=>{
995                this.pixmap = pixmap;
996                this.previewData[idx] = {
997                  pixelMap:this.pixmap
998                }
999              })
1000            } else {
1001              this.numberBadge--;
1002              for (let i=0; i<this.isSelectedGrid.length; i++) {
1003                if (this.isSelectedGrid[i] === true) {
1004                  let data: UDC.Image = new UDC.Image();
1005                  data.uri = '/resource/image.jpeg';
1006                  if (!this.unifiedData) {
1007                    this.unifiedData = new UDC.UnifiedData(data);
1008                  }
1009                  this.unifiedData.addRecord(data);
1010                }
1011              }
1012            }
1013          })
1014          .onPreDrag((status: PreDragStatus) => {
1015            // 1. Long press notification. Callback upon 350 ms.
1016            if (status == PreDragStatus.PREPARING_FOR_DRAG_DETECTION) {
1017              // 2. The user presses and holds for a period of time without releasing, which may lead to dragging: Prepare data at this time.
1018              this.loadData()
1019            } else if (status == PreDragStatus.ACTION_CANCELED_BEFORE_DRAG) {
1020              // 3. The user stops the drag operation: Cancel data preparation (simulation method: cancel the timer).
1021              clearTimeout(this.timeout);
1022            }
1023          })
1024          // Triggered when the component is pressed for 500 ms or longer and moved more than 10 vp.
1025          .onDragStart((event: DragEvent) => {
1026            this.dragEvent = event;
1027            if (this.finished == false) {
1028              this.getUIContext().getDragController().notifyDragStartRequest(dragController.DragStartRequestStatus.WAITING);
1029            } else {
1030              event.setData(this.unifiedData);
1031            }
1032          })
1033          .onDragEnd(() => {
1034            this.finished = false;
1035          })
1036          .dragPreviewOptions({numberBadge: this.numberBadge},{isMultiSelectionEnabled:true,defaultAnimationBeforeLifting:true})
1037        }, (idx: string) => idx)
1038      }
1039      .columnsTemplate('1fr 1fr 1fr 1fr 1fr')
1040      .columnsGap(5)
1041      .rowsGap(10)
1042      .backgroundColor(0xFAEEE0)
1043    }.width('100%').margin({ top: 5 })
1044  }
1045}
1046```
1047
1048
1049
1050## Spring Loading (Hover Detection) Support
1051Spring loading, also known as drag hover detection or spring-loaded navigation, is an enhanced drag and drop capability that allows users to automatically trigger view transitions by hovering over targets during drag operations. This feature significantly improves operational efficiency and is recommended for implementation in all page transition scenarios.
1052
1053> This feature is supported since API version 20.
1054
1055This feature is particularly useful in the following scenarios:
1056
1057- File management: Dragging a file over a folder automatically expands the folder.
1058- Home screen launcher: Hovering a file over an application icon automatically launches the application.
1059
1060Beyond view transitions, spring loading can also activate specific UI elements. For example, when a user drags text and hovers it over a button, a text boxcan be activated. The user can then move the dragged text into this text box and release it to display search results, enabling efficient one-handed operation.
1061
1062![drag spring loading example](figures/drag_springloading-01.png)
1063
1064### Implementation Principle
1065
1066To implement this feature, register the **onDragSpringLoading** API on a component and pass a callback to handle hover trigger notifications. Once registered, the component acts as a drop target (similar to components using the **onDrop** API) and follows the same hit detection rules: Only the topmost component under the hover position receives the drag event response.
1067
1068The spring loading process follows three distinct phases: hover detection -> callback notification -> completion. If the user continues dragging before completion, spring loading is automatically canceled, triggering a cancellation notification to the application. However, if dragging is resumed during the hover detection phase before the spring loading state is entered, no cancellation notification is sent.
1069
1070![drag spring loading pharse](figures/drag_springloading-02.png)
1071
1072Applications receive state updates through callbacks, enabling dynamic UI adjustments.
1073
1074| State  | Description                                                                                             | Recommended Action                                                                              |
1075| :----- | :------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------- |
1076| BEGIN  | The user has hovered over the component long enough to start entering the spring loading state.                         | Change the background color or adjust the component size to provide visual feedback.                                  |
1077| UPDATE | The user holds the pointer device stationary, and the system sends periodic refresh notifications (default: 3 times).                                                | Use the notification sequence parity to determine whether to reset UI display, creating pulsating visual effects.|
1078| END    | The user has hovered long enough to complete the entire spring loading process.                             | Execute page transitions or view changes.                                                                  |
1079| CANCEL | The user continues dragging or hover detection is interrupted in BEGIN state, preventing completion of hover detection.| Reset the UI state and cancel view transition-related states and logic.                                          |
1080
1081>**NOTE**
1082>
1083>1. Keeping the pointer device stationary within the same component triggers only one complete spring loading cycle. The cycle will not repeat until the pointer device leaves and re-enters the component.
1084>2. A component can support both spring loading and other drag events (for example, **onDrop** and **onDragEnter**).
1085
1086
1087### Triggering Customization
1088
1089You can customize spring loading detection parameters to dynamically determine whether to continue triggering.
1090
10911. Trigger parameter customization
1092
1093  The **onDragSpringLoading** API includes an optional **configuration** parameter, which allows you to customize settings such as detection duration, trigger intervals, and number of triggers. This enables personalized definition of spring loading trigger conditions. In most cases, however, the system's default configuration is sufficient, and no modifications are required.
1094
1095  The **configuration** parameter must be set before detection begins. Once the system initiates the spring loading detection process, it will no longer read updates from this parameter. Yet, you can still dynamically adjust configurations during detection using the **updateConfiguration** API in the **context** object provided using the callback. These dynamic updates apply only to the current trigger and do not affect the base configurations set through the **configuration** parameter.
1096
1097  It is recommended that you use either the default configuration or fixed parameters through the **configuration** parameter in **onDragSpringLoading**. In most cases, dynamically modifying detection parameters during spring loading is unnecessary. However, this functionality can be useful if you need to provide different user feedback based on the type of dragged data.
1098
1099  >**NOTE**
1100  >
1101  >Avoid setting excessively long time intervals or overly frequent trigger counts, as these typically fail to provide meaningful user feedback.
1102
11032. Dynamic termination
1104
1105  When the system detects sufficient hover duration and invokes the **onDragSpringLoading** callback, you can decide whether to allow the pending spring loading notification to proceed. This is particularly useful when you need to check the type of dragged data and align it with your service logic.
1106
1107  The following pseudocode demonstrates this dynamic termination functionality:
1108  ```typescript
1109    .onDragSpringLoading((context: DragSpringLoadingContext)=>{
1110      // Check the current state.
1111      if (context.state == DragSpringLoadingState.BEGIN) {
1112        // Verify whether the dragged data type can be processed.
1113        boolean isICanHandle = false;
1114        let dataSummary = context?.dragInfos?.dataSummary;
1115        if (dataSummary != undefined) {
1116          for (const [type, size] of dataSummary) {
1117            if (type === "general.plain-text") { // Only plain text can be processed.
1118              isICanHandle = true;
1119              break;
1120            }
1121          }
1122        }
1123        // Terminate spring loading if data cannot be processed.
1124        if (!isICanHandle) {
1125          context.abort();
1126          return;
1127        }
1128      }
1129    })
1130  ```
1131
11323. Disabling spring loading
1133
1134  If you no longer need a component to respond to spring loading, you can explicitly disable the feature by passing **null** to **onDragSpringLoading**:
1135
1136  ```typescript
1137    .onDragSpringLoading(null)
1138  ```
1139
1140### Example
1141
1142The following example demonstrates how to implement the device search functionality using **onDragSpringLoading**, including visual feedback and view switching triggered by drag hover interactions.
1143
11441. Prepare components.
1145
1146  To simplify the example, create two core components: a draggable text component and a button control. The button responds to spring loading to activate a view implemented using **bindSheet**, containing a text box for receiving dragged text and a text component for displaying search results.
1147
1148  ```typescript
1149    build() {
1150      Column() {
1151        Column() {
1152          Text('Double-click to select and drag text: \n     DeviceName')
1153            .fontSize(30)
1154            .copyOption(CopyOptions.InApp) // Enable text selection and dragging when copyOption is enabled.
1155        }.padding({bottom:30})
1156
1157        Button('Search Devices').width('80%').height('80vp').fontSize(30)
1158          .bindSheet($$this.isShowSheet, this.SheetBuilder(), {
1159            detents: [SheetSize.MEDIUM, SheetSize.LARGE, 600],
1160            preferType: SheetType.BOTTOM,
1161            title: { title: 'Search Devices' },
1162          })
1163      }.width('100%').height('100%')
1164      .justifyContent(FlexAlign.Center)
1165    }
1166  ```
11672. Implement **SheetBuilder**.
1168
1169  Implements the UI for the sheet.
1170
1171  ```typescript
1172    @Builder
1173    SheetBuilder() {
1174      Column() {
1175        // Text box
1176        TextInput({placeholder: 'Drag text here'})
1177          .width('80%').borderWidth(1).borderColor(Color.Black)
1178          .onChange((value: string)=>{
1179            if (value.length == 0) {
1180              this.isSearchDone = false;
1181              return;
1182            }
1183            // Simplified handling: display fixed search results.
1184            this.isSearchDone = true;
1185        })
1186        if (this.isSearchDone) {
1187          Text(this.searchResult).fontSize(30)
1188        }
1189      }.width('100%').height('100%')
1190    }
1191  ```
1192
11933. Add the enter and leave response to the button.
1194
1195  To provide visual feedback, add **onDragEnter** and **onDragLeave** handlers to the target component. When text is dragged over the component, the background color changes to prompt the user.
1196
1197  ```typescript
1198    .onDragEnter(()=>{
1199      // Change the button color when dragged text enters the area.
1200      this.buttonBackgroundColor = this.reminderColor
1201    })
1202    .onDragLeave(()=>{
1203      // Restore the original color when text leaves the area.
1204      this.buttonBackgroundColor = this.normalColor
1205    })
1206  ```
1207
12084. Implement spring loading response.
1209
1210  Implement a spring loading handler to process all states.
1211
1212  ```typescript
1213  handleSpringLoading(context: dragController.SpringLoadingContext) {
1214      // Check the drag data type during the BEGIN state.
1215      if (context.state == dragController.DragSpringLoadingState.BEGIN) {
1216        // Add necessary checks to decide whether to terminate the process.
1217        return;
1218      }
1219      if (context.state == dragController.DragSpringLoadingState.UPDATE) {
1220        // Provide periodic visual reminders during hovering.
1221        return;
1222      }
1223      // Handle completion: Trigger view transition.
1224      if (context.state == dragController.DragSpringLoadingState.END) {
1225        // Activate or switch views.
1226        return;
1227      }
1228      // Handle cancellation: Restore the UI.
1229      if (context.state == dragController.DragSpringLoadingState.CANCEL) {
1230        // Restore the state and UI.
1231        return;
1232      }
1233    }
1234  ```
1235
1236**Complete Code**
1237
1238  ```typescript
1239  import { dragController } from '@kit.ArkUI';
1240  import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData';
1241
1242  @Entry
1243  @ComponentV2
1244  struct Index {
1245    @Local isShowSheet: boolean = false;
1246    private searchResult: string = 'Search results:\n  Device 1\n  Device 2\n  Device 3\n  ... ...';
1247    @Local isSearchDone: boolean = false;
1248    private  reminderColor: Color = Color.Green;
1249    private normalColor: Color = Color.Blue;
1250    @Local buttonBackgroundColor: Color = this.normalColor;
1251
1252    @Builder
1253    SheetBuilder() {
1254      Column() {
1255        // Text box
1256        TextInput({placeholder: 'Drag text here'})
1257          .width('80%').borderWidth(1).borderColor(Color.Black).padding({bottom: 5})
1258          .onChange((value: string)=>{
1259            if (value.length == 0) {
1260              this.isSearchDone = false;
1261              return;
1262            }
1263            // Simplified handling: display fixed search results.
1264            this.isSearchDone = true;
1265          })
1266        if (this.isSearchDone) {
1267          Text(this.searchResult).fontSize(20).textAlign(TextAlign.Start).width('80%')
1268        }
1269      }.width('100%').height('100%')
1270    }
1271
1272    // Check whether the dragged data contains plain text.
1273    checkDataType(dataSummary: unifiedDataChannel.Summary | undefined): boolean {
1274      let summary = dataSummary?.summary;
1275      if (summary == undefined) {
1276        return false;
1277      }
1278
1279      let dataSummaryObjStr: string = JSON.stringify(summary);
1280      let dataSummaryArray: Array<Array<string>> = JSON.parse(dataSummaryObjStr);
1281      let isDataTypeMatched: boolean = false;
1282      dataSummaryArray.forEach((record: Array<string>) => {
1283        if (record[0] == 'general.plain-text') {
1284          isDataTypeMatched = true;
1285        }
1286      });
1287      return isDataTypeMatched;
1288    }
1289
1290    // Handle the BEGIN state.
1291    handleBeginState(context: SpringLoadingContext): boolean {
1292      // Verify whether the dragged data type can be processed.
1293      if (this.checkDataType(context?.dragInfos?.dataSummary)) {
1294        return true;
1295      }
1296      // Terminate spring loading if data cannot be processed.
1297      context.abort();
1298      return false;
1299    }
1300
1301    // Spring Loading handler.
1302    handleSpringLoading(context: SpringLoadingContext) {
1303      // Check the drag data type during the BEGIN state.
1304      if (context.state == dragController.DragSpringLoadingState.BEGIN) {
1305        if (this.handleBeginState(context)) {
1306          // The visual feedback is already provided in onDragEnter. When the spring loading state is entered, restore the UI to prompt the user to remain stationary.
1307          this.buttonBackgroundColor = this.normalColor;
1308        }
1309        return;
1310      }
1311      if (context.state == dragController.DragSpringLoadingState.UPDATE) {
1312        // Alternate the UI reminder color based on the notification sequence parity.
1313        if (context.currentNotifySequence % 2 != 0) {
1314          this.buttonBackgroundColor = this.reminderColor;
1315        } else {
1316          this.buttonBackgroundColor = this.normalColor;
1317        }
1318        return;
1319      }
1320      // Handle completion: Trigger view transition.
1321      if (context.state == dragController.DragSpringLoadingState.END) {
1322        this.isShowSheet = true;
1323        return;
1324      }
1325      // Handle cancellation: Restore the UI.
1326      if (context.state == dragController.DragSpringLoadingState.CANCEL) {
1327        this.buttonBackgroundColor = this.normalColor;
1328        return;
1329      }
1330    }
1331
1332    build() {
1333      Column() {
1334        Column() {
1335          Text('Double-click to select and drag text: \n     DeviceName')
1336            .fontSize(30)
1337            .copyOption(CopyOptions.InApp) // Enable text selection and dragging when copyOption is enabled.
1338        }.padding({bottom:30})
1339
1340        Button('Search Devices').width('80%').height('80vp').fontSize(30)
1341          .bindSheet($$this.isShowSheet, this.SheetBuilder(), {
1342            detents: [SheetSize.MEDIUM, SheetSize.LARGE, 600],
1343            preferType: SheetType.BOTTOM,
1344            title: { title: 'Search Devices' },
1345          })
1346          .allowDrop([uniformTypeDescriptor.UniformDataType.PLAIN_TEXT])
1347          .backgroundColor(this.buttonBackgroundColor)
1348          .onDragEnter(()=>{
1349            // Change the button color when dragged text enters the area.
1350            this.buttonBackgroundColor = this.reminderColor
1351          })
1352          .onDragLeave(()=>{
1353            // Restore the original color when text leaves the area.
1354            this.buttonBackgroundColor = this.normalColor
1355          })
1356          .onDragSpringLoading((context: SpringLoadingContext)=>{
1357            this.handleSpringLoading(context);
1358          })
1359      }.width('100%').height('100%')
1360      .justifyContent(FlexAlign.Center)
1361    }
1362  }
1363```
1364
1365
1366<!--RP1--><!--RP1End-->
1367<!--no_check-->