Enable drag and drop

The Android drag and drop framework lets you add interactive drag and drop capabilities to your app. With drag and drop, users can copy or move text, images, objects, and any content that can be represented by a URI, from one View to another within an app, or between apps in multi-window mode.

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, you can use the framework for other UI actions. For example, you can create an app that mixes colors when the user drags a color icon over another icon. However, the rest of document 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, called 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.

Create a drag event listener by implementing View.OnDragListener. 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 send 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 accepts 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 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, you can alter the default implementation of onDragEvent() without overriding the method. Set an OnReceiveContentListener on a view; for more details, see setOnReceiveContentListener(). The onDragEvent() method then does the following by default:

  • 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.

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 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 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 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 View objects 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 doesn't 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 isn't interested in the drag and drop operation and doesn't 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 might alter the appearance of the drop target View in response to the event. For example, if the event indicates that the drag shadow enters 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 passes to the system in the call to startDragAndDrop() that starts the operation. The listener is expected to return boolean true to the system if the listener successfully processes the dropped data. : 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 a drag event with action type ACTION_DROP, if necessary, 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 releases the drag shadow. The event is sent to every listener that is registered to receive drag events, even if the listener also receives the ACTION_DROP event.

Each of these steps is described in more detail in the section called 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, which are described in table 1:

Table 1. DragEvent action types

Action type Meaning
ACTION_DRAG_STARTED The application calls startDragAndDrop() and obtains 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 enters 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 moves outside the bounding box of the drag event listener's View.
ACTION_DROP The drag shadow releases over the drag event listener's View. This action type is sent to a View object's listener only if the listener returns boolean true in response to the ACTION_DRAG_STARTED drag event. This action type isn't sent if the user releases the drag shadow over a View whose listener isn't registered or if the user releases the drag shadow over anything that isn't part of the current layout.

The listener returns boolean true if it successfully processes the drop. Otherwise, it must return false.

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

The DragEvent object also contains the data and metadata that your application provides 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 the section called 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 methods getAction(), describeContents(), writeToParcel(), and toString() always return valid data.

If a method doesn't contain valid data for a particular action type, it returns 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 define 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 a View that the user selects 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 get a drag shadow that has the same appearance as the View you pass as an argument, centered under the location where the user touches 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 else you get an invisible drag shadow. The system doesn't 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 must 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, keep the size of the drag shadow small. For a single item, you might want to use an icon. For a multiple-item selection, you might 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, respond to events during the drag, respond to a drop event, and 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 must 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 doesn't represent data movement, you might 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(context).apply {
    // Set the bitmap for the ImageView from an icon bitmap 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(view: 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 is 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(context);
    
    // Set the bitmap for the ImageView from an icon bitmap defined elsewhere.
    imageView.setImageBitmap(iconBitmap);
    
    // Set the tag.
    imageView.setTag(IMAGEVIEW_TAG);
    
    // Set 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 is handled.
    return true;
    });
    
  2. Define myDragShadowBuilder by overriding the methods in View.DragShadowBuilder. The following code snippet creates a small, rectangular, gray drag shadow for a TextView:

    Kotlin

    private class MyDragShadowBuilder(view: View) : View.DragShadowBuilder(view) {
    
    private val shadow = ColorDrawable(Color.LTGRAY)
    
    // Define 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. Set 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)
    }
    
    // Define 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 view) {
    
            // Store the View parameter.
            super(view);
    
            // Create a draggable image that fills the Canvas provided by the
            // system.
            shadow = new ColorDrawable(Color.LTGRAY);
    }
    
    // Define a callback that sends the drag shadow dimensions and touch point
    // back to the system.
    @Override
    public void onProvideShadowMetrics (Point size, Point touch) {
    
            // Define 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. Set 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);
    }
    
    // Define 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 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 must do the following:

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

    If the drag and drop operation doesn't represent data movement, this might be unnecessary.

  2. If the drag event listener can accept a drop, it must 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 must return false, and the system stops sending drag events to the listener until the system sends ACTION_DRAG_ENDED to conclude the drag and drop operation.

For an ACTION_DRAG_STARTED event, the following DragEvent methods aren't valid: getClipData(), getX(), getY(), and getResult().

Handle events during the drag

During the drag action, drag event listeners that return 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 must 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—enters the bounding box of the listener's View.
  • ACTION_DRAG_LOCATION: once the listener receives an ACTION_DRAG_ENTERED event, it receives a new ACTION_DRAG_LOCATION event every time the touch point moves until it receives an ACTION_DRAG_EXITED event. 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 receives 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 doesn't 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 appearance at the touch point or to determine the exact position where the user can release the drag shadow—that is, drop the data.
  • In response to ACTION_DRAG_EXITED, the listener must reset any appearance changes it applies 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 reports that it can accept the content being dragged, the system dispatches a drag event to the View with the action type ACTION_DROP.

The drag event listener must do the following:

  1. Call getClipData() to get the ClipData object that is originally supplied in the call to startDragAndDrop() and process the data. If the drag and drop operation doesn't represent data movement, this is unnecessary.

  2. Return boolean true to indicate that the drop is processed successfully, or false if it isn't. The returned value becomes the value returned by getResult() for the eventual ACTION_DRAG_ENDED event. If the system doesn't 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 receives the drop to return the X and Y position of the touch point at the moment of the drop.

The system lets the user release the drag shadow over a View whose drag event listener isn't receiving drag events. It also lets the user 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 doesn't 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 the drag event listeners in your application. This indicates that the drag and drop operation is over.

Each drag event listener must do the following:

  1. If the listener changes its View object's appearance during the operation, the listener must 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 returns 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 when the system doesn't send an ACTION_DROP event.
  3. To indicate successful completion of the drag and drop operation, the listener must 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 ->

    // Handle each of the expected events.
    when (e.action) {
        DragEvent.ACTION_DRAG_STARTED -> {
            // Determine whether this View can accept the dragged data.
            if (e.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
                // As an example, apply 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()

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

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

            // Return true. The value is ignored.
            true
        }

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

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

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

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

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

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

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

            // Return true. DragEvent.getResult() returns true.
            true
        }

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

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

            // Do a getResult() and display what happens.
            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()

            // Return true. The value is ignored.
            true
        }
        else -> {
            // An unknown action type is 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) -> {

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

        case DragEvent.ACTION_DRAG_STARTED:

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

                // As an example, apply 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();

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

            }

            // Return false to indicate that, during the current drag and drop
            // operation, this View doesn't receive events again until
            // ACTION_DRAG_ENDED is sent.
            return false;

        case DragEvent.ACTION_DRAG_ENTERED:

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

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

            // Return true. The value is ignored.
            return true;

        case DragEvent.ACTION_DRAG_LOCATION:

            // Ignore the event.
            return true;

        case DragEvent.ACTION_DRAG_EXITED:

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

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

            // Return true. The value is ignored.
            return true;

        case DragEvent.ACTION_DROP:

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

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

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

            // Turn off color tints.
            ((ImageView)v).clearColorFilter();

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

            // Return true. DragEvent.getResult() returns true.
            return true;

        case DragEvent.ACTION_DRAG_ENDED:

            // Turn off color tinting.
            ((ImageView)v).clearColorFilter();

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

            // Do a getResult() and displays what happens.
            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();
            }

            // Return true. The value is ignored.
            return true;

        // An unknown action type is 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 lets users move data from one app to another using a drag and drop operation. For more information, see Multi-window support.

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

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. This requires the following:

  • 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 or 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. If your implementation involves starting a new Activity to process the dropped URIs, you will need to grant the new Activity the same permissions. You must set the clip data and a flag:

    Kotlin

    intent.setClipData(clipData)
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    

    Java

    intent.setClipData(clipData);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    

The following code snippets demonstrate how to release read-only access to drag and drop data immediately after the drag and drop operation takes place. See the DragAndDrop sample on GitHub for a more complete example.

Source drag and drop activity

Kotlin

// Drag a file stored in an images/ directory in internal storage.
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 permit 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 in 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 permit 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 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 afterward because it's no
            // longer needed.
            dropPermissions.release()
            return@setOnDragListener true
        }

        // Implement logic for other DragEvent cases here.

        // An unknown action type is 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 afterward 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.

Specify drop targets

DropHelper.configureView() is a static, overloaded method that lets you specify drop targets. Its parameters include the following:

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's secondary (or accent) color, the highlight corner radius is set to 16 dp, and the list of EditText components is empty. See the following section for details.

Configure drop targets

The DropHelper.Options inner class lets you configure drop targets. Provide an instance of the class to the DropHelper.configureView(Activity, View, String[], Options, OnReceiveContentListener) method. See the previous section for more information.

Customize drop target highlighting

DropHelper configures drop targets to display a highlight as users drag content over the targets. DropHelper provides default styling, and DropHelper.Options lets you 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, as shown in the following 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();

Handle 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, 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 is 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();

Handle data in drop targets

The DropHelper.configureView() method accepts an OnReceiveContentListener that 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.

The OnReceiveContentListener also handles data provided to the drop target by user interactions other than drag and drop—such as copy and paste—when DropHelper.configureView() is used to configure the following types of views:

  • All views, if the user is running Android 12 or higher.
  • AppCompatEditText, if the user is running a version of Android down to Android 7.0.

MIME types, permissions, and content validation

The MIME type checking by DropHelper is based on the drag and drop ClipDescription, which is created by the app providing the drag and drop data. Validate the ClipDescription to ensure the MIME types are set correctly.

DropHelper requests all access permissions for content URIs contained in the drag and drop ClipData. For more information, see DragAndDropPermissions. The permissions let you resolve the content URIs when processing the drag and drop data.

DropHelper doesn't validate the data returned by content providers when resolving URIs in the dropped data. Check for null and verify the correctness of any resolved data.