This tutorial is about Android drag and drop feature and how to use it in an Android application with a simple example.
Android Drag and Drop framework, allows you to move data from one View to another using a graphical drag and drop gesture. Although the framework is primarily designed for data movement, you can use it for other UI actions. For example, you could create an app that mixes colors when the user drags a color icon over another icon.
Android drag/drop process
In android, the Drag and Drop process contains 4 steps or states:
- Started
- Continuing
- Dropped
- Ended
Started
In response to the user’s gesture (for example, on a long press) to begin a drag, your application calls startDrag() to tell the system to start a drag.
The startDrag() method arguments will provide data to be dragged, metadata for this data and a call-back for drawing the drag shadow.
After that, the system sends a drag event with action type ACTION_DRAG_STARTED to the drag event listeners for all the View objects in the current layout.
Continuing
The user continues the drag, and the system now sends ACTION_DRAG_ENTERED action followed by ACTION_DRAG_LOCATION action to the registered drag event listener for the View. 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.
Dropped
User drops the dragged item, and the system sends the ACTION_DROP event only if the user drops the drag shadow within the bounding box of a View which listener is registered to receive drag events.
For example: If there are two views View1 and View2 and View1 is registered for dragListener but view2 is not, then if user drops the item in View1 then the drop action will be called for View1 but if user drops the item in view2 then no drop action will be called for View2.
Ended
The drag-and-drop operation has ended, and the system sends out a drag event with action type ACTION_DRAG_ENDED. All of the views that were listening for the drag-and-drop events now get the ACTION_DRAG_ENDED event.
OnDragListener
To receive the drag-and-drop events, we need to register a View.OnDragListener on the View using setOnDragListener().
The View.OnDragListener having a callback method, onDrag(View v, DragEvent event).
- v: the view that received the event.
- event: DragEvent object, which contains all the information about the drag-and-drop event, including the location of the event, the drag action (state), and the data it carries.
Drag events
When startDrag() function is called, then the system identifies the type of drag event that is currently taking place and return the drag event to the application. It sends out a drag event in the form of a DragEvent object.
getAction() is used to identify which type of drag event is taking place.
The DragEvent object contains the following different action types:
- ACTION_DRAG_STARTED: receives this event action type just after the application calls startDrag() and gets a drag shadow.
- ACTION_DRAG_ENTERED: receives this event action type when the drag shadow has just entered the bounding box of the View.
- ACTION_DRAG_LOCATION: receives this event action type after it receives a ACTION_DRAG_ENTERED event, when the drag is taking place i.e. the user is dragging the item from one place to other.
- ACTION_DRAG_EXITED: receives this event action type after it receives a ACTION_DRAG_ENTERED and at least one ACTION_DRAG_LOCATION event, and after the user has moved the drag shadow outside the bounding box of the View.
- ACTION_DROP: receives this event action type when the user releases the drag shadow over the View object. This action type is not sent if the user releases the drag shadow on a View whose listener is not registered. The listener is expected to return boolean true if it successfully processes the drop. Otherwise, it should return false.
- ACTION_DRAG_ENDED: receives this event action type when the system is ending the drag operation. This action type is not necessarily preceded by an ACTION_DROP event. The listener must call getResult() to get the value that was returned in response to ACTION_DROP. If an ACTION_DROP event was not sent, then getResult() returns false.
let’s implement the drag and drop feature in our app.
Android Drag and Drop Example
In this example, we will drag and drop text view and button from one place to another in a bounding area and outside the bounding area (in another view).
1 . Open the layout file activity_main.xml and add the below code. It consist of two LinearLayouts (with equal weight) and the upper LinearLayout consist of a textview and a button.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:id="@+id/top_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:background="@color/colorPrimary" android:gravity="center" android:orientation="vertical"> <Button android:id="@+id/btn_draggable" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/btn_text" /> <TextView android:id="@+id/tv_draggable" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:layout_marginTop="@dimen/margin_10dp" android:text="@string/tv_text" android:textColor="@android:color/white" android:textSize="20sp" android:textStyle="bold" /> </LinearLayout> <LinearLayout android:id="@+id/bottom_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:background="@color/colorAccent" android:orientation="vertical" /> </LinearLayout>
2 . Open MainActivity.java and add the below code.
MainActivity.java
package com.c1ctech.draganddropdemo; import android.content.ClipData; import android.content.ClipDescription; import android.graphics.Color; import android.graphics.PorterDuff; import android.os.Bundle; import android.util.Log; import android.view.DragEvent; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity implements View.OnLongClickListener, View.OnDragListener { TextView textView; Button button; // Create a string for the TextView and Button label private static final String TEXTVIEW_TAG = "DRAG TEXT"; private static final String BUTTON_TAG = "DRAG BUTTON"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //find views by id textView = findViewById(R.id.tv_draggable); button = findViewById(R.id.btn_draggable); // Sets the tag textView.setTag(TEXTVIEW_TAG); button.setTag(BUTTON_TAG); implementEvents(); } //Implement long click and drag listener private void implementEvents() { textView.setOnLongClickListener(this); button.setOnLongClickListener(this); findViewById(R.id.top_layout).setOnDragListener(this); findViewById(R.id.bottom_layout).setOnDragListener(this); } // This is the method that the system calls when it dispatches a drag event to the // listener. @Override public boolean onDrag(View view, DragEvent dragEvent) { // Defines a variable to store the action type for the incoming event int action = dragEvent.getAction(); // Handles each of the expected events switch (action) { case DragEvent.ACTION_DRAG_STARTED: // Determines if this View can accept the dragged data if (dragEvent.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { // if you want to apply color when drag started to your view you can uncomment below lines // to give any color tint to the View to indicate that it can accept // data. //view.getBackground().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN); // Invalidate the view to force a redraw in the new tint // view.invalidate(); // returns true to indicate that the View can accept the dragged data. return true; } // Returns false. 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 MAGENTA or any color tint to the View, // Return true; the return value is ignored. view.getBackground().setColorFilter(Color.MAGENTA, PorterDuff.Mode.SRC_IN); // Invalidate the view to force a redraw in the new tint view.invalidate(); return true; case DragEvent.ACTION_DRAG_LOCATION: // Ignore the event return true; case DragEvent.ACTION_DRAG_EXITED: // Re-sets the color tint to blue, if you had set the BLUE color or any color in ACTION_DRAG_STARTED. Returns true; the return value is ignored. // view.getBackground().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN); //If u had not provided any color in ACTION_DRAG_STARTED then clear color filter. view.getBackground().clearColorFilter(); // Invalidate the view to force a redraw in the new tint view.invalidate(); return true; case DragEvent.ACTION_DROP: // Gets the item containing the dragged data ClipData.Item item = dragEvent.getClipData().getItemAt(0); // Gets the text data from the item. String dragData = item.getText().toString(); // Displays a message containing the dragged data. Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_SHORT).show(); // Turns off any color tints view.getBackground().clearColorFilter(); // Invalidates the view to force a redraw view.invalidate(); //get dragged view View v = (View) dragEvent.getLocalState(); ViewGroup owner = (ViewGroup) v.getParent(); owner.removeView(v); //remove the dragged view LinearLayout container = (LinearLayout) view; //caste the view into LinearLayout as our drag acceptable layout is LinearLayout container.addView(v); //Add the dragged view v.setVisibility(View.VISIBLE);//finally set Visibility to VISIBLE // Returns true. DragEvent.getResult() will return true. return true; case DragEvent.ACTION_DRAG_ENDED: // Turns off any color tinting view.getBackground().clearColorFilter(); // Invalidates the view to force a redraw view.invalidate(); // invoke getResult(), and displays what happened. if (dragEvent.getResult()) Toast.makeText(this, "The drop was handled.", Toast.LENGTH_SHORT).show(); else Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_SHORT).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 OnDragListener."); break; } return false; } @Override public boolean onLongClick(View view) { // Create a new ClipData.Item from the tag ClipData.Item item = new ClipData.Item((CharSequence) view.getTag()); String[] mimeTypes = {ClipDescription.MIMETYPE_TEXT_PLAIN}; // Create a new ClipData using the tag as a label, the plain text MIME type, and // the already-created item. This will create a new ClipDescription object within the // ClipData, and set its MIME type entry to "text/plain" ClipData data = new ClipData(view.getTag().toString(), mimeTypes, item); // Instantiates the drag shadow builder. View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view); // Starts the drag view.startDrag(data //data to be dragged , shadowBuilder //drag shadow , view //local data about the drag and drop operation , 0 //flags (not currently used, set to 0) ); //Set view visibility to INVISIBLE as we are going to drag the view view.setVisibility(View.INVISIBLE); return true; } }
3. When you run the app it will look like this as shown below:
