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;
})
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 theUnifiedRecord
documentation for details. - The object's information (such as
uri
) is set, then passed to the drag event usingevent.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 thanonDragStart
.
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
])
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);
})
})
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' });
}
})
And with that, the full drag-and-drop flow is complete!
Top comments (0)