Android Drag and Drop with Examples

In android, the Drag and Drop framework allows users to move data from one view to another using a graphical drag and drop gesture. 

 

The Drag and Drop framework will include the following functionalities to support the data movement in android applications.

 

  • Drag Event Class
  • Drag Listeners
  • Helper Methods and Classes

Generally, the Drag and Drop process starts when users making gestures recognized as a signal to start dragging data and the application tells the system that the drag is starting.

 

Once the drag is starting, the system call-back to our application to get the state of data being dragged and it sends drag events to the drag event listeners or call-back methods of each View in the layout.

Android Drag / Drop Process

In android, the Drag and Drop process contains 4 steps or states, those are

 

  • Started
  • Continuing
  • Dropped
  • Ended

Started

This event will occur when we start dragging an item in layout and our application will call the startDrag() method 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.

 

The system will respond back to our application to get a drag shadow and it will display the drag shadow on the device.

 

After that, the system will send a drag event with action type ACTION_DRAG_STARTED to the drag event listeners for all the View objects in the current layout. To receive drag events continuously, including a possible drop event, the drag event listener must return true and it registers the listener with the system because only registered listeners continue to receive drag events. At this point, listeners can also change the appearance of their View object to show that the listener can accept a drop event.

 

In case if the drag event listener returns false, then it won’t receive any drag events for the current operation until the system sends a drag event with action type ACTION_DRAG_ENDED. By sending false, the listener tells the system that it is not interested in the drag operation and does not want to accept the dragged data.

Continuing

When the user continues to drag, the drag shadow intersects with the bounding box of a View object and the system sends one or more drag events to the View object's drag event listener (In case if it is registered to receive events).

In response to the event, the listener may choose to alter its View object’s appearance. For example, if the event indicates that the drag shadow has entered the bounding box of the View (action type ACTION_DRAG_ENTERED), the listener can react by highlighting its View.

Dropped

Whenever the user releases the drag shadow within the bounding box of a View that can accept the data. The system sends the View object's listener a drag event with action type ACTION_DROP.

 

The drag event will contain the data that is passed to the system while starting the operation by using the startDrag() method and the listener will return true to the system in case if drops success.

Ended

After completion of 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. This is done regardless of where the user released the drag shadow.

Drag Event Listener and Call-back Method

In android, the View object receives drag events either from a drag event listener that implements View.OnDragListener or with its onDragEvent(DragEvent) call-back method. When the system calls a method or listener, it passes to a DragEvent object.

 

We can use both listener and call-back method for View objects but in most cases listener is preferable. In case if we use both method and listener, the first system calls the listener and then defined the call-back method as long as the listener returns true.

 

The combination of onDragEvent(DragEvent) method and View.OnDragListener is analogous to the combination of onTouchEvent() and View.OnTouchListener used with touch events.

Android Drag Events

Generally, the system sends a drag event object (DragEvent) to perform the drag/drop process and the DragEvent object contains an action type that tells the listener what is happening in the drag/drop process.

 

The listener calls getAction() method to get the action type from the DragEvent object. The following are the different types of action types available in the DragEvent object.

 

Action TypeDescription
ACTION_DRAG_STARTED A View object's drag event listener receives this action type event just after the application calls startDrag() and gets a drag shadow.
ACTION_DRAG_ENTERED A View object's drag event listener receives this action type when the drag shadow has entered the bounding box of the View. This is the first action type event, the listener receives when the drag shadow enters the bounding box.
ACTION_DRAG_LOCATION A View object's drag event listener receives this action type event after it receives an ACTION_DRAG_ENTERED event while the drag shadow is still within the bounding box of the View.
ACTION_DRAG_EXITED A View object's drag event listener receives this action type event after it receives an 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 A View object's drag event listener receives this action type event when the user releases the drag shadow over the View object.
ACTION_DRAG_ENDED A View object's drag event listener receives this action type event when the system is ending the drag operation.

The DragEvent object also contains the data that our application provided to the system in the call to startDrag(). Some of the data is valid only for certain action types.

Android Drag Shadow

In android, while performing a drag and drop operation, the system will display an image that the user drags and we can call it as a drag shadow. During the data movement, images are used to represent the data being dragged.

 

By using View.DragShadowBuilder object methods, we can display an image of View that the user drags and then pass it to the system when we start a drag using the startDrag() method. As part of its response to startDrag(), the system invokes the methods that we defined in View.DragShadowBuilder to obtain a drag shadow.

Android Designing a Drag and Drop Operation

Following is the step-by-step how to start a drag event, how to respond to events during the drag, how to respond to a drop event, and how to end the drag and drop operation.

Starting a Drag Event

In android, we can start a drag event by using a drag gesture, usually a long press on View object. In response, we need to do the following things.

 

We need to create a ClipData and ClipData.item for the data that is being moved. As a part of the ClipData object, we need to send metadata that is stored in a ClipDescription object within the ClipData. For a drag and drop operation that does not represent data movement, we may need to use null instead of an actual object.

 

After that, we need to use View.DragShadowBuilder(View) to create a drag shadow for the View objects that are being moved. The View.DragShadowBuilder will create a default drag shadow that’s the same size as the View argument passed to it. In case, if we want to customize the drag shadow, then we need to extend the View.DragShadowBuilder functionality based on our requirements.

 

For example, the following is the code snippet which shows how to respond for a long press on View object by creating a ClipData object that contains the tag or label of a View object.

 

@Override
public boolean onLongClick(View v) {

    // Create a new ClipData.Item from the View 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 will create a new ClipDescription object within the
    // ClipData, and set its MIME type entry to "text/plain"
   
String[] mimeTypes = {ClipDescription.MIMETYPE_TEXT_PLAIN};
    ClipData data =
new ClipData(v.getTag().toString(), mimeTypes, item);

   
// Instantiates the drag shadow builder.
   
View.DragShadowBuilder dragshadow = new View.DragShadowBuilder(v);

   
// Starts the drag
   
v.startDrag(data       // data to be dragged
           
, dragshadow  // drag shadow
           
, v            // local data about the drag and drop operation
           
, 0          // flags set to 0 because not using currently
   
);
   
return true;
}

This is how we can respond for a long press on View object by creating a ClipData object in android applications.

Responding to a Drag Start Event

During the drag operation, the system dispatches drag events to the drag event listeners of View objects in the current layout. The listeners should react by calling getAction() to get the action type. At the start of a drag, the getAction() method returns ACTION_DRAG_STARTED.

 

In response to an event with the action type ACTION_DRAG_STARTED, the listener will do the following things.

 

  • The listener will call getClipDescription() to get the ClipDescription details and it uses a MIME type methods in ClientDescription to decide whether to accept the data being dragged or not. In case if the drag and drop operation does not represent any data movement, then it does not require.
  • If the listener can accept a drop, it should return true. This tells the system to continue to send drag events to the listener. If it can't accept a drop, it should return false, and the system will stop sending drag events until it sends out ACTION_DRAG_ENDED.

Handling an Events during the Drag

During the drag, listeners primarily use drag events such as ACTION_DRAG_ENTERED, ACTION_DRAG_LOCATION, ACTION_DRAG_EXITED, etc. to decide whether to change the appearance of the View to indicate that it is about to receive a drop.

Responding to a Drop Event

Whenever we release a drag shadow on the View that accepts a content being dragged, the system dispatches a drag event to that View with action type ACTION_DROP and the listener will do the following things.

 

  • The listener will call getClipData() to get the ClipData object that was originally supplied in the call to startDrag() and store it. If the drag and drop operation does not represent data movement, this may not be necessary.
  • It will return either true or false to indicate whether the drop was processed successfully or not and it returns the value from the getResult() method in the ACTION_DRAG_ENDED event.

Responding to a Drag End

Whenever we release the drag shadow, the system will send a drag event to all the drag event listeners in our application, with an action type of ACTION_DRAG_ENDED to indicate that the drag operation is over and the listener will do the following things.

 

  • During the operation, in case if the listener changed its View object's appearance, it should reset the View to its default appearance to indicate that the operation is over.
  • The listener can optionally call getResult() to find out more about the operation.

For example, the following is the code snippet which shows how to respond for drag events in a listener.

 

// This is the method that the system calls when it dispatches a drag event to the listener.
@Override
public boolean onDrag(View v, DragEvent event) {
   
// Defines a variable to store the action type for the incoming event
   
int action = event.getAction();
   
// Handles each of the expected events
   
switch (action) {


       
case DragEvent.ACTION_DRAG_STARTED:

            // Determines if this View can accept the dragged data
           
if (event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
               
// applies a blue color tint to the View to indicate that it can accept the data
                 
v.getBackground().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN);
                // 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. 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 YELLOW or any color tint to the View. Return true; the return value is ignored.
           
v.getBackground().setColorFilter(Color.YELLOW, PorterDuff.Mode.SRC_IN);
           
// Invalidate the view to force a redraw in the new tint
           
v.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.
           
v.getBackground().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN);
            //If u had not provided any color in ACTION_DRAG_STARTED then clear color filter.
           
v.getBackground().clearColorFilter();
           
// Invalidate the view to force a redraw in the new tint
           
v.invalidate();
           
return true;
       
case DragEvent.ACTION_DROP:
           
// Gets the item containing the dragged data
           
ClipData.Item item = event.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
           
v.getBackground().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
           
v.getBackground().clearColorFilter();
           
// Invalidates the view to force a redraw
           
v.invalidate();
           
// Does a getResult(), and displays what happened.
           
if (event.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;
}

This is how we can implement the drag and drop operation in our applications based on our requirements.

 

Now we will see how to drag and drop View objects such as button, imageview or textview, etc. in android applications with example. 

Android Drag and Drop Example

Following is the example of defining UI controls such as button, imageview, and textview in LinearLayout to implement a drag and drop functionality in the android application.

 

Create a new android application using android studio and give names as DragDropExample. In case if you are not aware of creating an app in android studio check this article Android Hello World App.

 

Now open an activity_main.xml file from \res\layout path and write the code like as shown below

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/layout1"
       
android:layout_width="match_parent"
       
android:layout_height="match_parent"
       
android:layout_weight="1"
       
android:background="#EE501B"
       
android:gravity="center"
       
android:orientation="vertical">
        <
TextView android:id="@+id/lbl"
           
android:layout_width="wrap_content"
           
android:layout_height="wrap_content"
           
android:text="Draggable Text"
            
android:textSize="20sp" />
        <
ImageView android:id="@+id/ingvw"
           
android:layout_width="wrap_content"
           
android:layout_height="wrap_content"
           
android:src="@mipmap/ic_launcher" />
        <
Button android:id="@+id/btnDrag"
           
android:layout_width="wrap_content"
           
android:layout_height="wrap_content"
           
android:text="Draggable Button" />
    </
LinearLayout>
    <
LinearLayout android:id="@+id/layout2"
       
android:layout_width="match_parent"
       
android:layout_height="match_parent"
       
android:layout_weight="1"
       
android:background="#00ADEF"
       
android:gravity="center"
       
android:orientation="vertical" />
    <
LinearLayout android:id="@+id/layout3"
       
android:layout_width="match_parent"
       
android:layout_height="match_parent"
       
android:layout_weight="1"
       
android:background="#80CC28"
       
android:gravity="center"
       
android:orientation="vertical" />
</
LinearLayout>

If you observe above code we created a multiple linear layouts with required UI controls to perform drag and drop execution.

 

Once we are done with the creation of layouts with required controls, we need to load the XML layout resource from our activity onCreate() callback method, for that open main activity file MainActivity.java from \java\com.tutlane.dragdropexample path and write the code like as shown below.

MainActivity.java

package com.tutlane.draganddropexample;
import android.content.ClipData;
import android.content.ClipDescription;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.support.v7.app.AppCompatActivity;
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.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnDragListener, View.OnLongClickListener {
   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
        setContentView(R.layout.
activity_main);
       
//Find all views and set Tag to all draggable views
       
TextView txtVw = (TextView) findViewById(R.id.lbl);
        txtVw.setTag(
"DRAGGABLE TEXTVIEW");
        txtVw.setOnLongClickListener(
this);
        ImageView imgVw = (ImageView) findViewById(R.id.
ingvw);
        imgVw.setTag(
"ANDROID ICON");
        imgVw.setOnLongClickListener(
this);
        Button btn = (Button) findViewById(R.id.
btnDrag);
        btn.setTag(
"DRAGGABLE BUTTON");
        btn.setOnLongClickListener(
this);
       
//Set Drag Event Listeners for defined layouts
       
findViewById(R.id.layout1).setOnDragListener(this);
        findViewById(R.id.
layout2).setOnDragListener(this);
        findViewById(R.id.
layout3).setOnDragListener(this);
    }
   
@Override
   
public boolean onLongClick(View v) {
       
// 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 will create a new ClipDescription object within the
        // ClipData, and set its MIME type entry to "text/plain"
       
String[] mimeTypes = {ClipDescription.MIMETYPE_TEXT_PLAIN};
        ClipData data =
new ClipData(v.getTag().toString(), mimeTypes, item);
       
// Instantiates the drag shadow builder.
       
View.DragShadowBuilder dragshadow = new View.DragShadowBuilder(v);
       
// Starts the drag
       
v.startDrag(data        // data to be dragged
               
, dragshadow   // drag shadow builder
               
, v           // local data about the drag and drop operation
               
, 0          // flags (not currently used, set to 0)
       
);
       
return true;
    }
   
// This is the method that the system calls when it dispatches a drag event to the listener.
   
@Override
   
public boolean onDrag(View v, DragEvent event) {
       
// Defines a variable to store the action type for the incoming event
       
int action = event.getAction();
       
// Handles each of the expected events
       
switch (action) {

           
case DragEvent.ACTION_DRAG_STARTED:
               
// Determines if this View can accept the dragged data
               
if (event.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.
                    // v.getBackground().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN);
                    // 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. 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 GRAY or any color tint to the View. Return true; the return value is ignored.
               
v.getBackground().setColorFilter(Color.GRAY, PorterDuff.Mode.SRC_IN);
                
// Invalidate the view to force a redraw in the new tint
               
v.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. Returns true; the return value is ignored.
                // view.getBackground().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_IN);
                //It will clear a color filter .
               
v.getBackground().clearColorFilter();
               
// Invalidate the view to force a redraw in the new tint
               
v.invalidate();
               
return true;

           
case DragEvent.ACTION_DROP:
               
// Gets the item containing the dragged data
               
ClipData.Item item = event.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
               
v.getBackground().clearColorFilter();
               
// Invalidates the view to force a redraw
               
v.invalidate();

                View vw = (View) event.getLocalState();
                ViewGroup owner = (ViewGroup) vw.getParent();
                owner.removeView(vw);
//remove the dragged view
                //caste the view into LinearLayout as our drag acceptable layout is LinearLayout
               
LinearLayout container = (LinearLayout) v;
                container.addView(vw);
//Add the dragged view
               
vw.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
               
v.getBackground().clearColorFilter();
               
// Invalidates the view to force a redraw
               
v.invalidate();
               
// Does a getResult(), and displays what happened.
               
if (event.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;
    }
}

If you observe above code, we registered our View objects and implemented a required Drag and Long press events to implement drag and drop functionality.

 

Generally, during the launch of our activity, the onCreate() callback method will be called by the android framework to get the required layout for an activity.

Output of Android Drag and Drop Example

When we run the above example using an android virtual device (AVD) we will get a result like as shown below.

 

Android Drag and Drop Example Result

 

If you observe the above result, we are able to drag and drop View’s such as Button, Imageview, and Textview to different layouts in our application.

 

This is how we can implement drag and drop functionality in our application to move data from one view to another based on our requirements.