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.
![]() |
![]() |
|
|
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 aContentInfo
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.
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 allView
objects 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 returnsfalse
, it doesn't 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 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 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 passes to the system in the call tostartDragAndDrop()
that starts the operation. The listener is expected to return booleantrue
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 aView
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 a drag event with action type
ACTION_DROP
, if necessary, 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 releases the drag shadow. The event is sent to every listener that is registered to receive drag events, even if the listener also receives theACTION_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 |
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 theView
object in theView.DragShadowBuilder
object, so the callbacks can access it to construct the drag shadow. The view doesn't have to be aView
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 theView
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 theView.DragShadowBuilder
object. The field is set tonull
. You must extendView.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
: aPoint
object. The drag shadow width goes inx
, and its height goes iny
.outShadowTouchPoint
: aPoint
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 inx
and its Y position goes iny
.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, 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:
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 doesn't represent data movement, you might 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(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; });
Define
myDragShadowBuilder
by overriding the methods inView.DragShadowBuilder
. The following code snippet creates a small, rectangular, gray drag shadow for aTextView
: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:
Call
DragEvent.getClipDescription()
and use the MIME type methods in the returnedClipDescription
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.
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 returnfalse
, and the system stops sending drag events to the listener until the system sendsACTION_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'sView
.ACTION_DRAG_LOCATION
: once the listener receives anACTION_DRAG_ENTERED
event, it receives a newACTION_DRAG_LOCATION
event every time the touch point moves until it receives anACTION_DRAG_EXITED
event. 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 receivesACTION_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 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
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
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 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
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:
Call
getClipData()
to get theClipData
object that is originally supplied in the call tostartDragAndDrop()
and process the data. If the drag and drop operation doesn't represent data movement, this is unnecessary.Return boolean
true
to indicate that the drop is processed successfully, orfalse
if it isn't. The returned value becomes the value returned bygetResult()
for the eventualACTION_DRAG_ENDED
event. If the system doesn't send out anACTION_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 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:
- If the listener changes its
View
object's appearance during the operation, the listener must 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 returnstrue
in response to an event of action typeACTION_DROP
, thengetResult()
returns booleantrue
. In all other cases,getResult()
returns booleanfalse
, including when the system doesn't send anACTION_DROP
event. - 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
andDRAG_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 callrelease()
on the object that was returned fromrequestDragAndDropPermissions()
. 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:
- 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.
- A
- 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'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:
- The
EditText
on which theClipData
is 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();
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.