Theory
The Android System provides Drag and Drop framework, which facilitates users especially to move data from one View
to another within the current layout using a graphical drag and drop gesture. As of API 11 drag and drop of view onto other views or view groups is supported. The framework includes a drag event class, drag listeners, and helper methods and classes.
Key terms:
View.DragShadowBuilder
object, and then pass it to the system when you start a drag using startDrag()
(startDragAndDrop
for API 24).startDrag()
(startDragAndDrop
for API 24) and contains description about data being dragged.When you start a drag, you include both the data you are moving and metadata describing this data as part of the call to the system. During the drag, the system sends drag events to the drag event listeners or callback methods of each View
in the layout. The listeners or callback methods can use the metadata to decide if they want to accept the data when it is dropped.
Your application tells the system to start a drag by calling the startDrag()
(startDragAndDrop
for API 24) method. This tells the system to start sending drag events. The method also sends the data that you are dragging.
There are basically four steps or states in the drag and drop process:
startDrag()
(startDragAndDrop
for API 24) method to tell the system to start a drag. The arguments inside startDrag()
(startDragAndDrop
for API 24) method provide the data to be dragged, metadata for this data, and a callback for drawing the drag shadow. The system first responds by calling back to your application to get a drag shadow. It then displays the drag shadow on the device. Next, the system sends a drag event with action type ACTION_DRAG_STARTED
to the registered drag event listeners for all the View
objects in the current layout. To continue to receive drag events, including a possible drop event, a drag event listener must return true, If the drag event listener returns false, then it will not receive drag events for the current operation until the system sends a drag event with action type ACTION_DRAG_ENDED
.ACTION_DRAG_ENTERED
action followed by ACTION_DRAG_LOCATION
action to the registered drag event listener for the View
where dragging point enters. The listener may choose to alter its View
object's appearance in response to the event or can react by highlighting its View
. The drag event listener receives a ACTION_DRAG_EXITED
action after the user has moved the drag shadow outside the bounding box of the View
.View
. The system sends the View
object's listener a drag event with action type ACTION_DROP
.Ended
. Just after the action type ACTION_DROP
, the system sends out a drag event with action type ACTION_DRAG_ENDED
to indicate that the drag operation is over.The DragEvent
class represents an event that is sent out by the system at various times during a drag and drop operation. This class provides few constants and important methods which we use during Drag/Drop process.
Following are all constants integers available as a part of DragEvent
class.
ACTION_DRAG_STARTED
. Signals the start of a drag and drop operation.ACTION_DRAG_ENTERED
. Signals to a View
that the drag point has entered the bounding box of the View.ACTION_DRAG_LOCATION
. Sent to a View
after ACTION_DRAG_ENTERED
if the drag shadow is still within the View
object's bounding box.ACTION_DRAG_EXITED
. Signals that the user has moved the drag shadow outside the bounding box of the View
.ACTION_DROP
. Signals to a View
that the user has released the drag shadow, and the drag point is within the bounding box of the View
.ACTION_DRAG_ENDED
. Signals to a View
that the drag and drop operation has concluded.Following are few important and most frequently used methods available as a part of DragEvent
class.
getAction()
. Inspect the action value of this event.getClipData()
. Returns the ClipData
object sent to the system as part of the call to startDrag()
(startDragAndDrop
for API 24).getClipDescription()
. Returns the ClipDescription
object contained in the ClipData
.getResult()
. Returns an indication of the result of the drag and drop operation.getX()
. Gets the X coordinate of the drag point.getY()
. Gets the Y coordinate of the drag point.toString()
Returns a string representation of this DragEvent
object.Listening for Drag Event. If you want any of your views within a Layout
should respond Drag
event then your View
either implements View.OnDragListener
or setup onDragEvent(DragEvent)
callback method. When the system calls the method or listener, it passes to them a DragEvent
object explained above. You can have both a listener and a callback method for View
object. If this occurs, the system first calls the listener and then defined callback as long as listener returns true.
The combination of the onDragEvent(DragEvent)
method and View.OnDragListener
is analogous to the combination of the onTouchEvent()
and View.OnTouchListener
used with touch events in old versions of Android.
Starting a Drag Event. You start with creating a ClipData
and ClipData.Item
for the data being moved. As part of the ClipData
object, supply metadata that is stored in a ClipDescription
object within the ClipData
. For a drag and drop operation that does not represent data movement, you may want to use null
instead of an actual object.
Next either you can extend extend View.DragShadowBuilder
to create a drag shadow for dragging the view or simply you can use View.DragShadowBuilder(View)
to create a default drag shadow that's the same size as the View
argument passed to it, with the touch point centered in the drag shadow.
Example
This example shows a simple drag and drop functionality, in which an item can be moved from one View
to another.
First, we should make some drawables in order to distinguish the different views in the layout, when we drag and drop an item.
Open res/drawable/box1.xml and paste the following code.
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@android:color/holo_blue_light"/> <corners android:radius="5dp"/> </shape>
Open res/drawable/box2.xml and paste the following code.
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@android:color/holo_green_light"/> <corners android:radius="5dp"/> </shape>
Open res/drawable/box3.xml and paste the following code.
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@android:color/holo_red_light"/> <corners android:radius="5dp"/> </shape>
In main activity layout, res/layout/activity_main.xml, define two LinearLayout
to act as left and right target view. Initially, in our layout all three ImageView
boxes will be present in left view.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <LinearLayout android:id="@+id/leftContainer" android:layout_width="0dp" android:layout_height="match_parent" android:background="#E8E6E7" android:layout_gravity="center_vertical" android:layout_weight="1" android:orientation="vertical"> <ImageView android:id="@+id/box1" android:layout_width="70dp" android:layout_height="70dp" android:layout_gravity="center_vertical|center_horizontal" android:layout_margin="10dp" android:background="@drawable/box1" /> <ImageView android:id="@+id/box2" android:layout_width="70dp" android:layout_height="70dp" android:layout_gravity="center_vertical|center_horizontal" android:layout_margin="10dp" android:background="@drawable/box2" /> <ImageView android:id="@+id/box3" android:layout_width="70dp" android:layout_height="70dp" android:layout_gravity="center_vertical|center_horizontal" android:layout_margin="10dp" android:background="@drawable/box3" /> </LinearLayout> <LinearLayout android:id="@+id/rightContainer" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" android:background="#B1BEC4" android:gravity="center_vertical" android:orientation="vertical"> </LinearLayout> </LinearLayout>
A drag and drop event occurs when a user starts dragging data, so the application tells the system to start a drag by calling startDrag()
(startDragAndDrop
for API 24) method. After that, the system calls back the application, in order to take the drag shadow, and it sends a DragEvent
. If we want every View
object in the layout to respond to the sending drag event, we should register a listener (onDragListener
or onTouchListener
) for each one or setup a callback method (onDrag
or onTouch
respectively) and define other views as possible drop targets. If we use both listener and callback method for the View
, the system calls the listener first and then the callback, as long as the listener still handles successfully the drag event.
In our example, we will implement the OnTouchListener
OnDragListener
interfaces.
Open MainActivity.java file and paste the following code.
public class MainActivity extends AppCompatActivity implements View.OnTouchListener, View.OnDragListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.box1).setOnTouchListener(this); findViewById(R.id.box2).setOnTouchListener(this); findViewById(R.id.box3).setOnTouchListener(this); findViewById(R.id.leftContainer).setOnDragListener(this); findViewById(R.id.rightContainer).setOnDragListener(this); } @Override @SuppressWarnings("deprecation") public boolean onTouch(View view, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view); ClipData data = ClipData.newPlainText("id", view.getResources().getResourceEntryName(view.getId())); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { view.startDragAndDrop(data, shadowBuilder, view, 0); } else { view.startDrag(data, shadowBuilder, view, 0); } view.setVisibility(View.INVISIBLE); return true; } return false; } @Override public boolean onDrag(View v, DragEvent event) { switch (event.getAction()) { // signal for the start of a drag and drop operation case DragEvent.ACTION_DRAG_STARTED: // do nothing break; // the drag point has entered the bounding box of the View case DragEvent.ACTION_DRAG_ENTERED: v.setBackgroundColor(0xFFFFF6F9); break; // the user has moved the drag shadow outside the bounding box of the View case DragEvent.ACTION_DRAG_EXITED: v.setBackgroundColor(v.getId() == R.id.leftContainer ? 0xFFE8E6E7 : 0xFFB1BEC4); break; // the drag and drop operation has concluded case DragEvent.ACTION_DRAG_ENDED: v.setBackgroundColor(v.getId() == R.id.leftContainer ? 0xFFE8E6E7 : 0xFFB1BEC4); break; //drag shadow has been released,the drag point is within the bounding box of the View case DragEvent.ACTION_DROP: View view = (View) event.getLocalState(); // we want to make sure it is dropped only to left and right parent view if (v.getId() == R.id.leftContainer || v.getId() == R.id.rightContainer) { ViewGroup source = (ViewGroup) view.getParent(); source.removeView(view); LinearLayout target = (LinearLayout) v; target.addView(view); String id = event.getClipData().getItemAt(0).getText().toString(); Toast.makeText(this, id + " dropped!", Toast.LENGTH_SHORT).show(); } // make view visible as we set visibility to invisible while starting drag view.setVisibility(View.VISIBLE); break; } return true; } }
As you can notice in the code above, for the dragging data you can use the ClipData
or the ClipData.Item
, which contains data that is dropped to the target-drop views. The ClipData
object includes ClipDescription
, which stores important meta-data about the clip. In addition, DragShadowBuilder
is used in order to display an image during the drag and drop operation.