The Android drag and drop framework enables you to add interactive drag and
drop capabilities to your app. With drag and drop, users can copy or move
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.
![]() |
![]() |
|
|
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 viewThe data is passed to the method as a
ContentInfo
object. The method invokes theOnReceiveContentListener
.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 allView
s in the current layout. To continue to receive drag events—including a possible drop event—the drag event listener must returntrue
. 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 targetView
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 typeACTION_DRAG_ENDED
. By returningfalse
, 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 typeACTION_DRAG_ENTERED
), the listener can react by highlighting theView
.- 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 tostartDragAndDrop()
that started the operation. The listener is expected to return booleantrue
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, noACTION_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 typeACTION_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 theACTION_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 |
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 theView
object in theView.DragShadowBuilder
object, so the callbacks can access it to construct the drag shadow. The view doesn't have to be theView
(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 theView
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 theView.DragShadowBuilder
object (the field is set tonull
). You must extendView.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:onDrawShadow()
Immediately after the call to
onProvideShadowMetrics()
the system callsonDrawShadow()
to create the drag shadow. The method has a single argument, aCanvas
object that the system constructs from the parameters you provide inonProvideShadowMetrics()
. The method draws the drag shadow on the providedCanvas
.
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:
Create a
ClipData
object andClipData.Item
object for the data being moved. As part of theClipData
, supply metadata that is stored in aClipDescription
object within theClipData
. For a drag and drop operation that does not represent data movement, you may want to usenull
instead of an actual object.For example, this code snippet shows how to respond to a touch & hold gesture on an
ImageView
by creating aClipData
object that contains the tag (or label) of anImageView
: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; });
The following code snippet defines
myDragShadowBuilder
by overriding the methods inView.DragShadowBuilder
. The code creates a small, gray, rectangular drag shadow for aTextView
: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:
Call
DragEvent#getClipDescription()
and use the MIME type methods in the returnedClipDescription
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.
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 returnfalse
, and the system will stop sending drag events to the listener until the system sendsACTION_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'sView
.ACTION_DRAG_LOCATION
: Once the listener receives anACTION_DRAG_ENTERED
event, and before it receives anACTION_DRAG_EXITED
event, it receives a newACTION_DRAG_LOCATION
event every time the touch point moves. ThegetX()
andgetY()
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 receivedACTION_DRAG_ENTERED
. The event is sent when the drag shadow touch point moves from within the bounding box of the listener'sView
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
orACTION_DRAG_LOCATION
, the listener can change the appearance of theView
to indicate that the view is a potential drop target. - An event with the action type
ACTION_DRAG_LOCATION
contains valid data forgetX()
andgetY()
, corresponding to the location of the touch point. The listener can use this information to alter theView
'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 toACTION_DRAG_ENTERED
orACTION_DRAG_LOCATION
. This indicates to the user that theView
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:
Call
getClipData()
to get theClipData
object that was originally supplied in the call tostartDragAndDrop()
and process the data.If the drag and drop operation does not represent data movement, this is not necessary.
Return boolean
true
to indicate that the drop was processed successfully, orfalse
if it was not. The returned value becomes the value returned bygetResult()
for the eventualACTION_DRAG_ENDED
event.Note that if the system does not send out an
ACTION_DROP
event, the value returned bygetResult()
for anACTION_DRAG_ENDED
event isfalse
.
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:
- If the listener changed its
View
object's appearance during the operation, the listener should reset theView
to its default appearance. This is a visual indication to the user that the operation is over. - The listener can optionally call
getResult()
to find out more about the operation. If a listener returnedtrue
in response to an event of action typeACTION_DROP
, thengetResult()
returns booleantrue
. In all other cases,getResult()
returns booleanfalse
, including the case in which the system did not send anACTION_DROP
event. - 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
andDRAG_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 callrelease()
on the object that was returned fromrequestDragAndDropPermissions()
. 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. See the DragAndDrop sample on GitHub for a more complete example.
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:
- The current
Activity
(used for URI permissions) - A
View
that serves as the drop target - The MIME types the drop target can accept from the dropped data
- Configuration options for the drop target (in particular, a list of embedded EditText fields)
- An
OnReceiveContentListener
to handle dropped data
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:
- The
EditText
on which theClipData
was dropped - The
EditText
that contains the text cursor (caret) - The first
EditText
provided to the call toDropHelper.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.
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
down to Android 7.0
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.