Android Drag and Drop

Filed Under: Android

In this tutorial, we’ll be implementing Android Drag and Drop functionality in our application. The Android Framework has a built-in mechanism for implementing Drag and drop feature in an application.

Android Drag and Drop

  • To drag a view we need to register it with an onTouchListener or an onLongClickListener first.
  • We also need to add a listener to the view where the dragged view would be dropped. We’ll register an onDragListener on it.
  • The onDragListener interface contains the method onDrag which gets called whenever any DragEvent occurs.

Following are the events that get triggered during a drag and drop operation.

  1. ACTION_DRAG_STARTED: Once the user touches/clicks on the view to be dragged, startDrag method is invoked inside the onTouch/onLongClick method thereby indicates that the dragging has started. ACTION_DRAG_STARTED is eventually called inside the onDrag method.
  2. ACTION_DRAG_ENTERED: This event gets triggered when the dragged view enters the bounds of the dropped view.
  3. ACTION_DRAG_LOCATION: This event gets triggered after the event ACTION_DRAG_ENTERED is triggered and it’s used to fetch the dragged view’s current location using the getX() and getY() methods.
  4. ACTION_DRAG_EXITED: This is triggered when the dragged view exits the bounds of the dropped view.
  5. ACTION_DROP: This is triggered when the user releases the dragged view.
  6. ACTION_DRAG_ENDED: This concludes that the drag and drop operation has ended.

Note: During the drag and drop process, the view that’s being dragged is a shadow of the original view. The original view stays at its place and isn’t changed. Instead, an instance of it is created using the DragShadow class. Hence the dragged view that we’ve been referring above is, in fact, a drag shadow.

To pass data from the dragged view to the dropped view we use ClipData.

Let’s get down to the business end of this tutorial, where we’ll develop drag and drop functionality in an application.

Android Drag and Drop Example Project Structure

android drag and drop example

Android Drag and Drop Code

The code for the activity_main.xml layout file is given below.


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context="com.journaldev.draganddrop.MainActivity">


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Are you happy with the way drag and drop functionality is taught in this tutorial?"
        android:textColor="#FFF"
        android:gravity="center"
        android:layout_margin="16dp"
        android:textSize="20sp"
        android:layout_above="@+id/btnNo"
        android:layout_centerHorizontal="true" />


    <Button
        android:id="@+id/btnNo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_margin="8dp"
        android:tag="NO"
        android:text="NO" />


    <Button
        android:id="@+id/btnYes"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_centerVertical="true"
        android:layout_margin="8dp"
        android:tag="YES"
        android:text="YES" />

    <ImageView
        android:id="@+id/imgDestination"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_above="@+id/textView"
        android:src="@drawable/circle_border"
        android:tag="Destination" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="DROP ABOVE"
        android:layout_margin="16dp"
        android:textColor="#FFF"/>


</RelativeLayout>

The layout contains two Buttons that’ll be used for dragging inside an ImageView. The ImageView displays a shape drawable that resides in the file circle_border.xml shown below.


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
    <solid android:color="@android:color/transparent"/>
    <stroke android:width="2dp" android:color="#fff" />
    <size android:width="100dp" android:height="100dp"/>
</shape>

The code for the MainActivity.java class is given below.


package com.journaldev.draganddrop;

import android.content.ClipData;
import android.content.ClipDescription;
import android.graphics.Color;
import android.os.Build;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnTouchListener, View.OnDragListener {

    Button btnYes, btnNo;
    ImageView imgDestination;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnYes = findViewById(R.id.btnYes);
        btnNo = findViewById(R.id.btnNo);
        imgDestination = findViewById(R.id.imgDestination);

        btnYes.setOnTouchListener(this);
        btnNo.setOnTouchListener(this);
        imgDestination.setOnDragListener(this);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {


        View.DragShadowBuilder mShadow = new View.DragShadowBuilder(v);
        ClipData.Item item = new ClipData.Item(v.getTag().toString());
        String[] mimeTypes = {ClipDescription.MIMETYPE_TEXT_PLAIN};
        ClipData data = new ClipData(v.getTag().toString(), mimeTypes, item);

        switch (v.getId()) {
            case R.id.btnYes:


                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    v.startDragAndDrop(data, mShadow, null, 0);
                } else {
                    v.startDrag(data, mShadow, null, 0);
                }

                break;
            case R.id.btnNo:

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    v.startDragAndDrop(data, mShadow, null, 0);
                } else {
                    v.startDrag(data, mShadow, null, 0);
                }
                break;

        }

        return false;
    }

    @Override
    public boolean onDrag(View v, DragEvent event) {
        switch (event.getAction()) {
            case DragEvent.ACTION_DRAG_STARTED:

                ((ImageView) v).setColorFilter(Color.YELLOW);


                v.invalidate();
                return true;

            case DragEvent.ACTION_DRAG_ENTERED:

                String clipData = event.getClipDescription().getLabel().toString();
                switch (clipData) {
                    case "YES":
                        ((ImageView) v).setColorFilter(ContextCompat.getColor(MainActivity.this, R.color.green), android.graphics.PorterDuff.Mode.MULTIPLY);
                        break;
                    case "NO":
                        ((ImageView) v).setColorFilter(ContextCompat.getColor(MainActivity.this, R.color.colorAccent), android.graphics.PorterDuff.Mode.MULTIPLY);
                        break;
                }

                v.invalidate();
                return true;

            case DragEvent.ACTION_DRAG_LOCATION:
                return true;

            case DragEvent.ACTION_DRAG_EXITED:

                ((ImageView) v).clearColorFilter();
                ((ImageView) v).setColorFilter(Color.YELLOW);

                v.invalidate();
                return true;

            case DragEvent.ACTION_DROP:


                clipData = event.getClipDescription().getLabel().toString();
                Toast.makeText(getApplicationContext(),clipData, Toast.LENGTH_SHORT).show();

                v.invalidate();
                return true;

            case DragEvent.ACTION_DRAG_ENDED:


                ((ImageView) v).clearColorFilter();
                if (event.getResult()) {
                    Toast.makeText(MainActivity.this, "Awesome!", Toast.LENGTH_SHORT).show();

                } else {
                    Toast.makeText(MainActivity.this, "Aw Snap! Try dropping it again", Toast.LENGTH_SHORT).show();
                }
                return true;

            default:
                return false;
        }
    }
}

Let’s analyse how we came up with the above piece of code.

  1. As discussed earlier, we’ve registered the views to de dragged(Buttons) with the onTouchListener and the ImageView is the view where they should be dropped. Hence the onDragListener and subsequently the DragEvents are registered on it.
  2. The onTouch method is where we pass the ClipData and create an instance of the DragShadowBuilder view(which would eventually be dragged in the first place).
  3. With the introduction of Android Nougat(API 24), startDrag() method stands deprecated. Hence we use the method startDragAndDrop() for API>=24.
  4. The startDrag/startDragAndDrop methods require the ClipData as well as the DragShadow instances.
  5. As the drag starts, we add a background tint on the dropped view inside the ACTION_DRAG_STARTED case in the onDrag method using the method setColorFilter.
  6. Each of the switch cases return true. Returning a false would indicate that the onDrag method doesn’t want to be triggered for that particular DragEvent.
  7. In the ACTION_DRAG_ENTERED case, we change the background tint of the dropped view based on which of the Button enters the dropped view bounds. The type of button is determined using the ClipData.
  8. To retrieve the ClipData, we chain up the following methods, event.getClipDescription().getLabel().toString().
  9. event.getResult() returns a boolean that determines if the DragShadow is dropped within the bounds it was supposed to or not.
  10. v.invalidate() is used to force a redraw of the ImageView.

Android Drag and Drop App Output

The output of the our android drag and drop application in action is shown below.
android drag drop app

In case we need to implement drag and drop wherein it looks like the original view is being dragged, we’d need to toggle the visibility of the view before the drag starts and after it’s done.

This brings an end to this tutorial. You can download the final Android DragAndDrop Project from the link below.

Reference: Android Documentation

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages