Android Drag and Drop Example

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:

  1. ACTION_DRAG_STARTED: receives this event action type just after the application calls startDrag() and gets a drag shadow.
  2. ACTION_DRAG_ENTERED: receives this event action type when the drag shadow has just entered the bounding box of the View.
  3. ACTION_DRAG_LOCATIONreceives 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.
  4. 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.
  5. 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.
  6. 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:

Leave a Reply