Drag and drop

With the Android drag and drop framework, you can enable users to move data by means of interactive drag and drop gestures. Users can drag text, images, objects—any content that can be represented by a URI—from one View to another within an app or, in multi-window mode, between apps.

Text string and image being dragged and dropped within an app. Text string and image being dragged and dropped between apps in split-screen mode.
Figure 1. Drag and drop within an app.
Figure 2. Drag and drop between apps.

The framework includes a drag event class, drag listeners, and helper classes and methods. Although primarily designed to enable the transfer of data, the framework can be used for other UI actions. For example, you could create an app that mixes colors when the user drags a color icon over another icon. The rest of this guide, however, describes the drag and drop framework in the context of data transfer.

Overview

A drag and drop operation starts when the user makes a UI gesture that your app recognizes as a signal to start dragging data. In response, the app notifies the system that a drag and drop operation is starting. The system calls back to your app to get a representation of the data being dragged (a drag shadow). As the user moves the drag shadow over the app's layout, the system sends drag events to the drag event listeners and callback methods associated with the View objects in the layout. If the user releases the drag shadow over a view that can accept the data (a drop target), the system sends the data to the target. The drag and drop operation ends when the user releases the drag shadow whether or not the drag shadow is over a drop target.

You create a drag event listener by implementing View.OnDragListener. You set the listener for a drop target with the View object's setOnDragListener() method. Each view in the layout also has an onDragEvent() callback method.

Your application notifies the system to start a drag and drop operation by calling the startDragAndDrop() method, which tells the system to start sending drag events. The method also provides the system with the data the user is dragging and metadata describing the data. You can call startDragAndDrop() on any View in the current layout. The system uses the View object only to get access to global settings in the layout.

During the drag and drop operation, the system sends drag events to the drag event listeners or callback methods of the View objects in the layout. The listeners or callback methods use the metadata to decide whether they want to accept the data when it is dropped. If the user drops the data on a drop target (a View that will accept the data), the system sends a drag event object containing the data to the drop target's drag event listener or callback method.

Drag event listeners and callback methods

A View receives drag events with either a drag event listener that implements View.OnDragListener or with the view's onDragEvent() callback method. When the system calls the method or listener, it provides a DragEvent argument.

In most cases, using a listener is preferable to using the callback method. When you design UIs, you usually don't subclass View classes, but using the callback method forces you to create subclasses to override the method. In comparison, you can implement one listener class and then use it with multiple different View objects. You can also implement it as an anonymous inline class or lambda expression. To set the listener for a View object, call setOnDragListener().

As an alternative, the default implementation of onDragEvent() can be altered without overriding the method. If you set an OnReceiveContentListener on a view (see setOnReceiveContentListener()), the onDragEvent() method by default does the following:

  • Returns true in response to the call to startDragAndDrop()
  • Calls performReceiveContent() if the drag and drop data is dropped on the view

    The data is passed to the method as a ContentInfo object. The method invokes the OnReceiveContentListener.

  • Returns true if the drag and drop data is dropped on the view and the OnReceiveContentListener consumes any of the content

You define the OnReceiveContentListener to handle the data specifically for your app. For backward compatibility down to API level 24, use the Jetpack version of OnReceiveContentListener.

You can have both a drag event listener and a callback method for a View object, in which case the system first calls the listener. The system doesn't call the callback method unless the listener returns false.

The combination of the onDragEvent() method and View.OnDragListener is analogous to the combination of the onTouchEvent() and View.OnTouchListener used with touch events.

Drag and drop process

There are basically four steps or states in the drag and drop process: Started, Continuing, Dropped, and Ended.

Started

In response to a user's drag gesture, your application calls startDragAndDrop() to tell the system to start a drag and drop operation. The method's arguments provide the following:

  • The data to be dragged
  • A callback for drawing the drag shadow
  • Metadata that describes the dragged data

The system responds by first calling back to your application to get a drag shadow. The system then displays the drag shadow on the device.

Next, the system sends a drag event with action type ACTION_DRAG_STARTED to the drag event listener of all Views in the current layout. To continue to receive drag events—including a possible drop event—the drag event listener must return true. This registers the listener with the system. Only registered listeners continue to receive drag events. At this point, listeners can also change the appearance of their drop target View object to show that the view can accept a drop event.

If the drag event listener returns false, it will not receive drag events for the current operation until the system sends a drag event with action type ACTION_DRAG_ENDED. By returning false, the listener tells the system that it is not interested in the drag and drop operation and does not want to accept the dragged data.

Continuing

The user continues the drag. As the drag shadow intersects the bounding box of a drop target, the system sends one or more drag events to the target's drag event listener. The listener may choose to alter the appearance of the drop target View in response to the event. For example, if the event indicates that the drag shadow has entered the bounding box of the drop target (action type ACTION_DRAG_ENTERED), the listener can react by highlighting the View.

Dropped

The user releases the drag shadow within the bounding box of a drop target. The system sends the drop target's listener a drag event with action type ACTION_DROP. The drag event object contains the data that was passed to the system in the call to startDragAndDrop() that started the operation. The listener is expected to return boolean true to the system if the listener successfully processes the dropped data.

Note that this step only occurs if the user drops the drag shadow within the bounding box of a View whose listener is registered to receive drag events (a drop target). If the user releases the drag shadow in any other situation, no ACTION_DROP drag event is sent.

Ended

After the user releases the drag shadow, and after the system sends out (if necessary) a drag event with action type ACTION_DROP, the system sends a drag event with action type ACTION_DRAG_ENDED to indicate that the drag and drop operation is over. This is done regardless of where the user released the drag shadow. The event is sent to every listener that is registered to receive drag events, even if the listener also received the ACTION_DROP event.

Each of these four steps is described in more detail in A drag and drop operation.

Drag events

The system sends out a drag event in the form of a DragEvent object, which contains an action type that describes what is happening in the drag and drop process. Depending on the action type, the object can also contain other data.

Drag event listeners receive the DragEvent object. To get the action type, listeners call DragEvent#getAction(). There are six possible values defined by constants in the DragEvent class.

Table 1. DragEvent action types

Action type Meaning
ACTION_DRAG_STARTED The application has called startDragAndDrop() and has obtained a drag shadow. If the listener wants to continue receiving drag events for this operation, it must return boolean true to the system.
ACTION_DRAG_ENTERED The drag shadow has just entered the bounding box of the drag event listener's View. This is the first event action type the listener receives when the drag shadow enters the bounding box.
ACTION_DRAG_LOCATION Subsequent to an ACTION_DRAG_ENTERED event, the drag shadow is still within the bounding box of the drag event listener's View.
ACTION_DRAG_EXITED Following an ACTION_DRAG_ENTERED and at least one ACTION_DRAG_LOCATION event, the drag shadow has moved outside the bounding box of the drag event listener's View.
ACTION_DROP The drag shadow has been released over the drag event listener's View. This action type is sent to a View object's listener only if the listener returned boolean true in response to the ACTION_DRAG_STARTED drag event. This action type is not sent if the user releases the drag shadow over a View whose listener is not registered or if the user releases the drag shadow over anything that is not part of the current layout.

The listener is expected to return boolean true if it successfully processes the drop. Otherwise, it should return false.

ACTION_DRAG_ENDED The system is ending the drag and drop operation. This action type is not necessarily preceded by an ACTION_DROP event. If the system sent an ACTION_DROP, receiving the ACTION_DRAG_ENDED action type does not imply that the drop succeeded. The listener must call getResult() (see table 2) to get the value that was returned in response to ACTION_DROP. If an ACTION_DROP event was not sent, then getResult() returns false.

The DragEvent object also contains the data and metadata that your application provided to the system in the call to startDragAndDrop(). Some of the data is valid only for certain action types as summarized in table 2. For more information about events and their associated data, see A drag and drop operation.

Table 2. Valid DragEvent data by action type

getAction()
value
getClipDescription()
value
getLocalState()
value
getX()
value
getY()
value
getClipData()
value
getResult()
value
ACTION_DRAG_STARTED    
ACTION_DRAG_ENTERED        
ACTION_DRAG_LOCATION    
ACTION_DRAG_EXITED        
ACTION_DROP  
ACTION_DRAG_ENDED        

The DragEvent getAction(), describeContents(), writeToParcel(), and toString() methods always return valid data.

If a method does not contain valid data for a particular action type, it returns either null or 0, depending on its result type.

Drag shadow

During a drag and drop operation, the system displays an image that the user drags. For data movement, this image represents the data being dragged. For other operations, the image represents some aspect of the drag operation.

The image is called a drag shadow. You create it with methods you declare for a View.DragShadowBuilder object. You pass the builder to the system when you start a drag and drop operation using startDragAndDrop(). As part of its response to startDragAndDrop(), the system invokes the callback methods you've defined in View.DragShadowBuilder to obtain a drag shadow.

The View.DragShadowBuilder class has two constructors:

View.DragShadowBuilder(View)

This constructor accepts any of your application's View objects. The constructor stores the View object in the View.DragShadowBuilder object, so the callbacks can access it to construct the drag shadow. The view doesn't have to be the View (if any) that the user selected to start the drag operation.

If you use this constructor, you don't have to extend View.DragShadowBuilder or override its methods. By default, you will get a drag shadow that has the same appearance as the View you pass as an argument, centered under the location where the user is touching the screen.

View.DragShadowBuilder()

If you use this constructor, no View object is available in the View.DragShadowBuilder object (the field is set to null). You must extend View.DragShadowBuilder and override its methods, or you will get an invisible drag shadow. The system does not throw an error.

The View.DragShadowBuilder class has two methods that together create the drag shadow:

onProvideShadowMetrics()

The system calls this method immediately after you call startDragAndDrop(). Use the method to send the dimensions and touch point of the drag shadow to the system. The method has two parameters:

outShadowSize
A Point object. The drag shadow width goes in x, and its height goes in y.
outShadowTouchPoint
A Point object. The touch point is the location within the drag shadow that should be under the user's finger during the drag. Its X position goes in x and its Y position goes in y.
onDrawShadow()

Immediately after the call to onProvideShadowMetrics() the system calls onDrawShadow() to create the drag shadow. The method has a single argument, a Canvas object that the system constructs from the parameters you provide in onProvideShadowMetrics(). The method draws the drag shadow on the provided Canvas.

To improve performance, you should keep the size of the drag shadow small. For a single item, you may want to use an icon. For a multiple-item selection, you may want to use icons in a stack rather than full images spread out over the screen.

A drag and drop operation

This section shows step by step how to start a drag, how to respond to events during the drag, how respond to a drop event, and how to end the drag and drop operation.

Start a drag

The user starts a drag with a drag gesture, usually a touch & hold, on a View object. In response, your app should do the following:

  1. Create a ClipData object and ClipData.Item object for the data being moved. As part of the ClipData, supply metadata that is stored in a ClipDescription object within the ClipData. For a drag and drop operation that does not represent data movement, you may want to use null instead of an actual object.

    For example, this code snippet shows how to respond to a touch & hold gesture on an ImageView by creating a ClipData object that contains the tag (or label) of an ImageView:

    Kotlin

    // Create a string for the ImageView label.
    val IMAGEVIEW_TAG = "icon bitmap"
    
    ...
    
    val imageView = ImageView(this).apply {
        // Sets the bitmap for the ImageView from an icon bit map (defined elsewhere).
        setImageBitmap(iconBitmap)
        tag = IMAGEVIEW_TAG
        setOnLongClickListener { v ->
            // Create a new ClipData.
            // This is done in two steps to provide clarity. The convenience method
            // ClipData.newPlainText() can create a plain text ClipData in one step.
    
            // Create a new ClipData.Item from the ImageView object's tag.
            val item = ClipData.Item(v.tag as? CharSequence)
    
            // Create a new ClipData using the tag as a label, the plain text MIME type, and
            // the already-created item. This creates a new ClipDescription object within the
            // ClipData and sets its MIME type to "text/plain".
            val dragData = ClipData(
                v.tag as? CharSequence,
                arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
                item)
    
            // Instantiate the drag shadow builder.
            val myShadow = MyDragShadowBuilder(this)
    
            // Start the drag.
            v.startDragAndDrop(dragData,  // The data to be dragged
                               myShadow,  // The drag shadow builder
                               null,      // No need to use local data
                               0          // Flags (not currently used, set to 0)
            )
    
           // Indicate that the long-click was handled.
           true
        }
    }
    

    Java

    // Create a string for the ImageView label.
    private static final String IMAGEVIEW_TAG = "icon bitmap";
    
    ...
    
    // Create a new ImageView.
    ImageView imageView = new ImageView(this);
    
    // Set the bitmap for the ImageView from an icon bit map (defined elsewhere).
    imageView.setImageBitmap(iconBitmap);
    
    // Set the tag.
    imageView.setTag(IMAGEVIEW_TAG);
    
    // Sets a long click listener for the ImageView using an anonymous listener object that
    // implements the OnLongClickListener interface.
    imageView.setOnLongClickListener( v -> {
    
        // Create a new ClipData.
        // This is done in two steps to provide clarity. The convenience method
        // ClipData.newPlainText() can create a plain text ClipData in one step.
    
        // Create a new ClipData.Item from the ImageView object's tag.
        ClipData.Item item = new ClipData.Item((CharSequence) v.getTag());
    
        // Create a new ClipData using the tag as a label, the plain text MIME type, and
        // the already-created item. This creates a new ClipDescription object within the
        // ClipData and sets its MIME type to "text/plain".
        ClipData dragData = new ClipData(
            (CharSequence) v.getTag(),
            new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN },
            item);
    
        // Instantiate the drag shadow builder.
        View.DragShadowBuilder myShadow = new MyDragShadowBuilder(imageView);
    
        // Start the drag.
        v.startDragAndDrop(dragData,  // The data to be dragged
                           myShadow,  // The drag shadow builder
                           null,      // No need to use local data
                           0          // Flags (not currently used, set to 0)
        );
    
        // Indicate that the long-click was handled.
        return true;
    });
    
  2. The following code snippet defines myDragShadowBuilder by overriding the methods in View.DragShadowBuilder. The code creates a small, gray, rectangular drag shadow for a TextView:

    Kotlin

    private class MyDragShadowBuilder(v: View) : View.DragShadowBuilder(v) {
    
        private val shadow = ColorDrawable(Color.LTGRAY)
    
        // Defines a callback that sends the drag shadow dimensions and touch point
        // back to the system.
        override fun onProvideShadowMetrics(size: Point, touch: Point) {
    
            // Set the width of the shadow to half the width of the original View.
            val width: Int = view.width / 2
    
            // Set the height of the shadow to half the height of the original View.
            val height: Int = view.height / 2
    
            // The drag shadow is a ColorDrawable. This sets its dimensions to be the
            // same as the Canvas that the system provides. As a result, the drag shadow
            // fills the Canvas.
            shadow.setBounds(0, 0, width, height)
    
            // Set the size parameter's width and height values. These get back to
            // the system through the size parameter.
            size.set(width, height)
    
            // Set the touch point's position to be in the middle of the drag shadow.
            touch.set(width / 2, height / 2)
        }
    
        // Defines a callback that draws the drag shadow in a Canvas that the system
        // constructs from the dimensions passed to onProvideShadowMetrics().
        override fun onDrawShadow(canvas: Canvas) {
    
            // Draw the ColorDrawable on the Canvas passed in from the system.
            shadow.draw(canvas)
        }
    }
    

    Java

    private static class MyDragShadowBuilder extends View.DragShadowBuilder {
    
        // The drag shadow image, defined as a drawable object.
        private static Drawable shadow;
    
        // Constructor
        public MyDragShadowBuilder(View v) {
    
            // Stores the View parameter.
            super(v);
    
            // Creates a draggable image that fills the Canvas provided by the system.
            shadow = new ColorDrawable(Color.LTGRAY);
        }
    
        // Defines a callback that sends the drag shadow dimensions and touch point
        // back to the system.
        @Override
        public void onProvideShadowMetrics (Point size, Point touch) {
    
            // Defines local variables
            int width, height;
    
            // Set the width of the shadow to half the width of the original View.
            width = getView().getWidth() / 2;
    
            // Set the height of the shadow to half the height of the original View.
            height = getView().getHeight() / 2;
    
            // The drag shadow is a ColorDrawable. This sets its dimensions to be the
            // same as the Canvas that the system provides. As a result, the drag shadow
            // fills the Canvas.
            shadow.setBounds(0, 0, width, height);
    
            // Set the size parameter's width and height values. These get back to the
            // system through the size parameter.
            size.set(width, height);
    
            // Set the touch point's position to be in the middle of the drag shadow.
            touch.set(width / 2, height / 2);
        }
    
        // Defines a callback that draws the drag shadow in a Canvas that the system
        // constructs from the dimensions passed to onProvideShadowMetrics().
        @Override
        public void onDrawShadow(Canvas canvas) {
    
            // Draw the ColorDrawable on the Canvas passed in from the system.
            shadow.draw(canvas);
        }
    }
    

Respond to a drag start

During the drag operation, the system dispatches drag events to the drag event listeners of the View objects in the current layout. The listeners should react by calling DragEvent#getAction() to get the action type. At the start of a drag, this method returns ACTION_DRAG_STARTED.

In response to an event with the action type ACTION_DRAG_STARTED, a drag event listener should do the following:

  1. Call DragEvent#getClipDescription() and use the MIME type methods in the returned ClipDescription to see if the listener can accept the data being dragged.

    If the drag and drop operation does not represent data movement, this might not be necessary.

  2. If the drag event listener can accept a drop, it should return true to tell the system to continue to send drag events to the listener. If the listener can't accept a drop, the listener should return false, and the system will stop sending drag events to the listener until the system sends ACTION_DRAG_ENDED to conclude the drag and drop operation.

Note that for an ACTION_DRAG_STARTED event, the following DragEvent methods are not valid: getClipData(), getX(), getY(), and getResult().

Handle events during the drag

During the drag action, drag event listeners that returned true in response to the ACTION_DRAG_STARTED drag event continue to receive drag events. The types of drag events a listener receives during the drag depend on the location of the drag shadow and the visibility of the listener's View. Listeners use the drag events primarily to decide if they should change the appearance of their View.

During the drag action, DragEvent#getAction() returns one of three values:

  • ACTION_DRAG_ENTERED: The listener receives this event action type when the touch point (the point on the screen underneath the user's finger or mouse) has entered the bounding box of the listener's View.
  • ACTION_DRAG_LOCATION: Once the listener receives an ACTION_DRAG_ENTERED event, and before it receives an ACTION_DRAG_EXITED event, it receives a new ACTION_DRAG_LOCATION event every time the touch point moves. The getX() and getY() methods return the X and Y coordinates of the touch point.
  • ACTION_DRAG_EXITED: This event action type is sent to a listener that previously received ACTION_DRAG_ENTERED. The event is sent when the drag shadow touch point moves from within the bounding box of the listener's View to outside the bounding box.

The drag event listener does not need to react to any of these action types. If the listener returns a value to the system, it is ignored.

Here are some guidelines for responding to each of these action types:

  • In response to ACTION_DRAG_ENTERED or ACTION_DRAG_LOCATION, the listener can change the appearance of the View to indicate that the view is a potential drop target.
  • An event with the action type ACTION_DRAG_LOCATION contains valid data for getX() and getY(), corresponding to the location of the touch point. The listener can use this information to alter the View's appearance at the touch point or to determine the exact position where the user could release the drag shadow (that is, drop the data).
  • In response to ACTION_DRAG_EXITED, the listener should reset any appearance changes it applied in response to ACTION_DRAG_ENTERED or ACTION_DRAG_LOCATION. This indicates to the user that the View is no longer an imminent drop target.

Respond to a drop

When the user releases the drag shadow over a View, and the View previously reported that it could accept the content being dragged, the system dispatches a drag event to the View with the action type ACTION_DROP.

The drag event listener should do the following:

  1. Call getClipData() to get the ClipData object that was originally supplied in the call to startDragAndDrop() and process the data.

    If the drag and drop operation does not represent data movement, this is not necessary.

  2. Return boolean true to indicate that the drop was processed successfully, or false if it was not. The returned value becomes the value returned by getResult() for the eventual ACTION_DRAG_ENDED event.

    Note that if the system does not send out an ACTION_DROP event, the value returned by getResult() for an ACTION_DRAG_ENDED event is false.

For an ACTION_DROP event, getX() and getY() use the coordinate system of the View that received the drop to return the X and Y position of the touch point at the moment of the drop.

The system allows the user to release the drag shadow over a View whose drag event listener is not receiving drag events. It also allows the user to release the drag shadow over empty regions of the application's UI or over areas outside of your application. In all of these cases, the system does not send an event with action type ACTION_DROP, although the system does send an ACTION_DRAG_ENDED event.

Respond to a drag end

Immediately after the user releases the drag shadow, the system sends a drag event with an action type of ACTION_DRAG_ENDED to all of the drag event listeners in your application. This indicates that the drag and drop operation is over.

Each drag event listener should do the following:

  1. If the listener changed its View object's appearance during the operation, the listener should reset the View to its default appearance. This is a visual indication to the user that the operation is over.
  2. The listener can optionally call getResult() to find out more about the operation. If a listener returned true in response to an event of action type ACTION_DROP, then getResult() returns boolean true. In all other cases, getResult() returns boolean false, including the case in which the system did not send an ACTION_DROP event.
  3. To indicate successful completion of the drag and drop operation, the listener should return boolean true to the system.

Respond to drag events: An example

All drag events are received by your drag event method or listener. The following code snippet is a simple example of responding to drag events:

Kotlin

val imageView = ImageView(this)

// Set the drag event listener for the View.
imageView.setOnDragListener { v, e ->

    // Handles each of the expected events.
    when (e.action) {
        DragEvent.ACTION_DRAG_STARTED -> {
            // Determines if this View can accept the dragged data.
            if (e.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
                // As an example of what your application might do, applies a blue color tint
                // to the View to indicate that it can accept data.
                (v as? ImageView)?.setColorFilter(Color.BLUE)

                // Invalidate the view to force a redraw in the new tint.
                v.invalidate()

                // Returns true to indicate that the View can accept the dragged data.
                true
            } else {
                // Returns false to indicate that, during the current drag and drop operation,
                // this View will not receive events again until ACTION_DRAG_ENDED is sent.
                false
            }
        }
        DragEvent.ACTION_DRAG_ENTERED -> {
            // Applies a green tint to the View.
            (v as? ImageView)?.setColorFilter(Color.GREEN)

            // Invalidates the view to force a redraw in the new tint.
            v.invalidate()

            // Returns true; the value is ignored.
            true
        }

        DragEvent.ACTION_DRAG_LOCATION ->
            // Ignore the event.
            true
        DragEvent.ACTION_DRAG_EXITED -> {
            // Resets the color tint to blue.
            (v as? ImageView)?.setColorFilter(Color.BLUE)

            // Invalidates the view to force a redraw in the new tint.
            v.invalidate()

            // Returns true; the value is ignored.
            true
        }
        DragEvent.ACTION_DROP -> {
            // Gets the item containing the dragged data.
            val item: ClipData.Item = e.clipData.getItemAt(0)

            // Gets the text data from the item.
            val dragData = item.text

            // Displays a message containing the dragged data.
            Toast.makeText(this, "Dragged data is $dragData", Toast.LENGTH_LONG).show()

            // Turns off any color tints.
            (v as? ImageView)?.clearColorFilter()

            // Invalidates the view to force a redraw.
            v.invalidate()

            // Returns true. DragEvent.getResult() will return true.
            true
        }

        DragEvent.ACTION_DRAG_ENDED -> {
            // Turns off any color tinting.
            (v as? ImageView)?.clearColorFilter()

            // Invalidates the view to force a redraw.
            v.invalidate()

            // Does a getResult(), and displays what happened.
            when(e.result) {
                true ->
                    Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG)
                else ->
                    Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG)
            }.show()

            // Returns true; the value is ignored.
            true
        }
        else -> {
            // An unknown action type was received.
            Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.")
            false
        }
    }
}

Java

View imageView = new ImageView(this);

// Set the drag event listener for the View.
imageView.setOnDragListener( (v, e) -> {

    // Handles each of the expected events.
    switch(e.getAction()) {

        case DragEvent.ACTION_DRAG_STARTED:

            // Determines if this View can accept the dragged data.
            if (e.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {

                // As an example of what your application might do, applies a blue color tint
                // to the View to indicate that it can accept data.
                ((ImageView)v).setColorFilter(Color.BLUE);

                // Invalidate the view to force a redraw in the new tint.
                v.invalidate();

                // Returns true to indicate that the View can accept the dragged data.
                return true;

            }

            // Returns false to indicate that, during the current drag and drop operation,
            // this View will not receive events again until ACTION_DRAG_ENDED is sent.
            return false;

        case DragEvent.ACTION_DRAG_ENTERED:

            // Applies a green tint to the View.
            ((ImageView)v).setColorFilter(Color.GREEN);

            // Invalidates the view to force a redraw in the new tint.
            v.invalidate();

            // Returns true; the value is ignored.
            return true;

        case DragEvent.ACTION_DRAG_LOCATION:

            // Ignore the event.
            return true;

        case DragEvent.ACTION_DRAG_EXITED:

            // Resets the color tint to blue.
            ((ImageView)v).setColorFilter(Color.BLUE);

            // Invalidates the view to force a redraw in the new tint.
            v.invalidate();

            // Returns true; the value is ignored.
            return true;

        case DragEvent.ACTION_DROP:

            // Gets the item containing the dragged data.
            ClipData.Item item = e.getClipData().getItemAt(0);

            // Gets the text data from the item.
            CharSequence dragData = item.getText();

            // Displays a message containing the dragged data.
            Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_LONG).show();

            // Turns off any color tints.
            ((ImageView)v).clearColorFilter();

            // Invalidates the view to force a redraw.
            v.invalidate();

            // Returns true. DragEvent.getResult() will return true.
            return true;

        case DragEvent.ACTION_DRAG_ENDED:

            // Turns off any color tinting.
            ((ImageView)v).clearColorFilter();

            // Invalidates the view to force a redraw.
            v.invalidate();

            // Does a getResult(), and displays what happened.
            if (e.getResult()) {
                Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG).show();
            }

            // Returns true; the value is ignored.
            return true;

        // An unknown action type was received.
        default:
            Log.e("DragDrop Example","Unknown action type received by View.OnDragListener.");
            break;
    }

    return false;

});

Drag and drop in multi-window mode

Devices that run Android 7.0 (API level 24) or higher support multi-window mode, which enables users to move data from one app to another using a drag and drop operation (see Multi-window support).

The source app provides the data. The drag and drop operation starts in the source app. The target app receives the data. The drag and drop operation ends in the target app.

When starting the drag and drop operation, the source app must set the DRAG_FLAG_GLOBAL flag to indicate that the user can drag data to another app.

Because the data moves across app boundaries, the apps share access to the data using a content URI:

  • The source app must set either or both of the DRAG_FLAG_GLOBAL_URI_READ and DRAG_FLAG_GLOBAL_URI_WRITE flags, depending on the read/write access to the data that the source app wants to grant to the target app.
  • The target app must call requestDragAndDropPermissions() immediately before handling the data that the user drags into the app. If the target app no longer needs access to the drag and drop data, the app can then call release() on the object that was returned from requestDragAndDropPermissions(). Otherwise, the permissions are released when the containing activity is destroyed.

The following code snippet demonstrates how to release read-only access to drag and drop data immediately after the drag and drop operation takes place. A more complete example appears in the DragAndDropAcrossApps sample, available on GitHub.

Source drag and drop activity

Kotlin

// Drag a file stored in internal storage. The file is in an "images/" directory.
val internalImagesDir = File(context.filesDir, "images")
val imageFile = File(internalImagesDir, imageFilename)
val uri = FileProvider.getUriForFile(context, contentAuthority, imageFile)

val listener = OnDragStartListener@{ view: View, _: DragStartHelper ->
    val clipData = ClipData(ClipDescription("Image Description",
                                            arrayOf("image/*")),
                            ClipData.Item(uri))
    // Must include DRAG_FLAG_GLOBAL to allow for dragging data between apps.
    // This example provides read-only access to the data.
    val flags = View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ
    return@OnDragStartListener view.startDragAndDrop(clipData,
                                                     View.DragShadowBuilder(view),
                                                     null,
                                                     flags)
}

// Container where the image originally appears in the source app.
val srcImageView = findViewById<ImageView>(R.id.imageView)

// Detect and start the drag event.
DragStartHelper(srcImageView, listener).apply {
    attach()
}

Java

// Drag a file stored under an "images/" directory in internal storage.
File internalImagesDir = new File(context.getFilesDir(), "images");
File imageFile = new File(internalImagesDir, imageFilename);
final Uri uri = FileProvider.getUriForFile(context, contentAuthority, imageFile);

// Container where the image originally appears in the source app.
ImageView srcImageView = findViewById(R.id.imageView);

// Enable the view to detect and start the drag event.
new DragStartHelper(srcImageView, (view, helper) -> {
    ClipData clipData = new ClipData(new ClipDescription("Image Description",
                                                          new String[] {"image/*"}),
                                     new ClipData.Item(uri));
    // Must include DRAG_FLAG_GLOBAL to allow for dragging data between apps.
    // This example provides read-only access to the data.
    int flags = View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ;
    return view.startDragAndDrop(clipData,
                                 new View.DragShadowBuilder(view),
                                 null,
                                 flags);
}).attach();

Target drag and drop activity

Kotlin

// Container for where the image is to be dropped in the target app.
val targetImageView = findViewById<ImageView>(R.id.imageView)

targetImageView.setOnDragListener { view, event ->

    when (event.action) {

        ACTION_DROP -> {
            val imageItem: ClipData.Item = event.clipData.getItemAt(0)
            val uri = imageItem.uri

            // Request permission to access the image data being dragged into
            // the target activity's ImageView element.
            val dropPermissions = requestDragAndDropPermissions(event)
            (view as ImageView).setImageURI(uri)

            // Release the permission immediately afterwards because it's
            // no longer needed.
            dropPermissions.release()
            return@setOnDragListener true
        }

        // Implement logic for other DragEvent cases here.

        // An unknown action type was received.
        else -> {
            Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.")
            return@setOnDragListener false
        }

    }
}

Java

// Container where the image is to be dropped in the target app.
ImageView targetImageView = findViewById(R.id.imageView);

targetImageView.setOnDragListener( (view, event) -> {

    switch (event.getAction()) {

        case ACTION_DROP:
            ClipData.Item imageItem = event.getClipData().getItemAt(0);
            Uri uri = imageItem.getUri();

            // Request permission to access the image data being
            // dragged into the target activity's ImageView element.
            DragAndDropPermissions dropPermissions =
                requestDragAndDropPermissions(event);

            ((ImageView)view).setImageURI(uri);

            // Release the permission immediately afterwards because
            // it's no longer needed.
            dropPermissions.release();

            return true;

        // Implement logic for other DragEvent cases here.

        // An unknown action type was received.
        default:
            Log.e("DragDrop Example","Unknown action type received by View.OnDragListener.");
            break;
    }

    return false;
});

DropHelper for simplified drag and drop

The DropHelper class simplifies implementation of drag and drop capabilities. A member of the Jetpack DragAndDrop library, DropHelper provides backward compatibility down to API level 24.

Use DropHelper to specify drop targets, customize drop target highlighting, and define how dropped data is handled.

Drop targets

DropHelper#configureView() is a static, overloaded method that enables you to specify drop targets. Parameters include:

For example, to create a drop target that accepts images, use either of the following method calls:

Kotlin

configureView(
    myActivity,
    targetView,
    arrayOf("image/*"),
    options,
    onReceiveContentListener)

// or

configureView(
    myActivity,
    targetView,
    arrayOf("image/*"),
    onReceiveContentListener)

Java

DropHelper.configureView(
    myActivity,
    targetView,
    new String[] {"image/*"},
    options,
    onReceiveContentlistener);

// or

DropHelper.configureView(
    myActivity,
    targetView,
    new String[] {"image/*"},
    onReceiveContentlistener);

The second call omits the drop target configuration options, in which case the drop target highlight color is set to the theme secondary (or accent) color, the highlight corner radius is set to 16dp, and the list of EditTexts is empty (see Drop target configuration below).

Drop target configuration

The DropHelper.Options inner class enables you to configure drop targets. You provide an instance of the class to the DropHelper.configureView(Activity, View, String[], Options, OnReceiveContentListener method (see Drop targets above).

Drop target highlighting

DropHelper configures drop targets to display a highlight as users drag content over the targets. DropHelper provides default styling, but DropHelper.Options enables you to set the color of the highlight and specify the corner radius of the highlight’s rectangle.

Use the DropHelper.Options.Builder class to create a DropHelper.Options instance and set configuration options, for example:

Kotlin

val options: DropHelper.Options = DropHelper.Options.Builder()
                                      .setHighlightColor(getColor(R.color.purple_300))
                                      .setHighlightCornerRadiusPx(resources.getDimensionPixelSize(R.dimen.drop_target_corner_radius))
                                      .build()

Java

DropHelper.Options options = new DropHelper.Options.Builder()
                                     .setHighlightColor(getColor(R.color.purple_300))
                                     .setHighlightCornerRadiusPx(getResources().getDimensionPixelSize(R.dimen.drop_target_corner_radius))
                                     .build();

EditText components in drop targets

DropHelper also controls focus within the drop target when the target contains editable text fields.

Drop targets can be a single view or a view hierarchy. If the drop target view hierarchy contains one or more EditText components, you must provide a list of the components to DropHelper.Options.Builder#addInnerEditTexts(EditText...) to ensure that drop target highlighting and text data handling work correctly.

DropHelper prevents EditText components within the drop target view hierarchy from stealing focus from the containing view during drag interactions.

Also, if the drag and drop ClipData includes text and URI data, DropHelper selects one of the EditText components in the drop target to handle the text data. Selection is based on the following order of precedence:

  1. The EditText on which the ClipData was dropped
  2. The EditText that contains the text cursor (caret)
  3. The first EditText provided to the call to DropHelper.Options.Builder#addInnerEditTexts(EditText...)

To set an EditText as the default text data handler, pass the EditText as the first argument of the call to DropHelper.Options.Builder#addInnerEditTexts(EditText...). For example, if your drop target handles images but contains editable text fields T1, T2, and T3, make T2 the default as follows:

Kotlin

val options: DropHelper.Options = DropHelper.Options.Builder()
                                      .addInnerEditTexts(T2, T1, T3)
                                      .build()

Java

DropHelper.Options options = new DropHelper.Options.Builder()
                                     .addInnerEditTexts(T2, T1, T3)
                                     .build();

Drop target data handling

The DropHelper#configureView() method accepts an OnReceiveContentListener which you create to handle the drag and drop ClipData. The drag and drop data is provided to the listener in a ContentInfoCompat object. Text data is present in the object; media, such as images, is represented by URIs.

MIME types, permissions, and content validation

DropHelper’s MIME type checking is based on the drag and drop ClipDescription, which is created by the app providing the drag and drop data. You should validate the ClipDescription to ensure the MIME types have been set correctly.

DropHelper requests all access permissions for content URIs contained in the drag and drop ClipData (see DragAndDropPermissions). The permissions enable you to resolve the content URIs when processing the drag and drop data.

DropHelper does not validate the data returned by content providers when resolving URIs in the dropped data. You should check for null and verify the correctness of any resolved data.

Additional resources