DEV Community

Studotie Handwer
Studotie Handwer

Posted on

Drag-and-Drop: A Signature Feature of HarmonyOS

Drag-and-drop is one of HarmonyOS’s standout features. Since HarmonyOS 4 introduced the Data Transfer Station and enabled drag by default on the Image component in NEXT, it’s clear that drag gestures are being heavily promoted. Therefore, learning how to configure drag-and-drop properly is essential!

HarmonyOS already provides a set of components with built-in drag-and-drop support, including Search, TextInput, TextArea, RichEditor, Text, Image, and Hyperlink. These components require checking whether the draggable attribute is set—if set to true, drag events can be triggered. A well-known example is the Bilibili feature where you can "drag out" characters 22 and 33—that works because their Image components have draggable set to true by default.


Drag-and-Drop for Custom Components

Enabling Drag Capability

To initiate drag behavior, the key is to implement the onDragStart callback:

.onDragStart((event) => {
    let data: unifiedDataChannel.Image = new unifiedDataChannel.Image();
    data.imageUri = 'common/pic/img.png';
    let unifiedData = new unifiedDataChannel.UnifiedData(data);
    event.setData(unifiedData);

    let dragItemInfo: DragItemInfo = {
        pixelMap: this.pixmap,
        extraInfo: "this is extraInfo",
    };
    // Return custom drag preview image in onDragStart callback
    return dragItemInfo;
})
Enter fullscreen mode Exit fullscreen mode

The unifiedDataChannel shown here refers to the Unified Data Management Framework (UDMF) provided by HarmonyOS. This framework offers standardized data channels for various multi-to-multi, cross-app data sharing scenarios. It allows for standardized data access and reading, making it quick and easy to support cross-app drag-and-drop for files.

In the example above:

  • A unifiedDataChannel.Image object is created to represent an image being dragged.
  • unifiedDataChannel offers multiple data types—refer to the UnifiedRecord documentation for details.
  • The object's information (such as uri) is set, then passed to the drag event using event.setData.

The next part introduces DragItemInfo, which allows customization of the drag preview image.


Custom Drag Preview

There are three key callbacks involved in customizing the drag preview:

  • onPreDrag: Triggered ~50ms after drag begins to prepare the preview.
  • onDragStart: Return value provides the preview.
  • dragPreview: Predefined preview, higher priority than onDragStart.

Both the return value of onDragStart and the value set by dragPreview must be of type DragItemInfo, which represents metadata about the drag item. You can use either pixelMap or CustomBuilder. Since CustomBuilder takes time to compute, for performance reasons it’s generally recommended to use pixelMap.

You can also use the DragPreviewOptions object to configure the drag mode, badges, and other visual elements during the drag operation.


Receiving Drag Events

To receive a drag, the target component must have allowDrop set. This accepts an array that defines the data types it can receive:

.allowDrop([
    uniformTypeDescriptor.UniformDataType.HYPERLINK,
    uniformTypeDescriptor.UniformDataType.PLAIN_TEXT
])
Enter fullscreen mode Exit fullscreen mode

The example above configures the component to accept both hyperlinks and plain text.

Next, implement the onDrop callback to handle the received data:

.onDrop((dragEvent?: DragEvent) => {
    // Retrieve drag data
    this.getDataFromUdmf((dragEvent as DragEvent), (event: DragEvent) => {
        let records: Array<unifiedDataChannel.UnifiedRecord> = event.getData().getRecords();
        this.targetImage = (records[0] as unifiedDataChannel.Image).imageUri; // Handle the dropped data

        // Explicitly set result as successful; this result is passed back to the drag source's onDragEnd
        event.setResult(DragResult.DRAG_SUCCESSFUL);
    })
})
Enter fullscreen mode Exit fullscreen mode

The records array contains all the drag data. From here, implement your own business logic to process the drop. Finally, explicitly set the event’s result—this will be sent to the source component via its onDragEnd callback, indicating whether the drag-and-drop operation succeeded or failed.

Example:

.onDragEnd((event) => {
    // The result value here is set by the receiver’s onDrop
    if (event.getResult() === DragResult.DRAG_SUCCESSFUL) {
        promptAction.showToast({ duration: 100, message: 'Drag Success' });
    } else if (event.getResult() === DragResult.DRAG_FAILED) {
        promptAction.showToast({ duration: 100, message: 'Drag failed' });
    }
})
Enter fullscreen mode Exit fullscreen mode

And with that, the full drag-and-drop flow is complete!

Top comments (0)