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 anonLongClickListener
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 methodonDrag
which gets called whenever anyDragEvent
occurs.
Following are the events that get triggered during a drag and drop operation.
ACTION_DRAG_STARTED
: Once the user touches/clicks on the view to be dragged,startDrag
method is invoked inside theonTouch
/onLongClick
method thereby indicates that the dragging has started.ACTION_DRAG_STARTED
is eventually called inside theonDrag
method.ACTION_DRAG_ENTERED
: This event gets triggered when the dragged view enters the bounds of the dropped view.ACTION_DRAG_LOCATION
: This event gets triggered after the eventACTION_DRAG_ENTERED
is triggered and it’s used to fetch the dragged view’s current location using thegetX()
andgetY()
methods.ACTION_DRAG_EXITED
: This is triggered when the dragged view exits the bounds of the dropped view.ACTION_DROP
: This is triggered when the user releases the dragged view.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 Code
The code for the activity_main.xml
layout file is given below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:tools="https://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.
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="https://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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
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.
- 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 theonDragListener
and subsequently the DragEvents are registered on it. - The
onTouch
method is where we pass theClipData
and create an instance of theDragShadowBuilder
view(which would eventually be dragged in the first place). - With the introduction of Android Nougat(API 24),
startDrag()
method stands deprecated. Hence we use the methodstartDragAndDrop()
for API>=24. - The
startDrag
/startDragAndDrop
methods require theClipData
as well as theDragShadow
instances. - As the drag starts, we add a background tint on the dropped view inside the
ACTION_DRAG_STARTED
case in theonDrag
method using the methodsetColorFilter
. - Each of the switch cases
return true
. Returning a false would indicate that theonDrag
method doesn’t want to be triggered for that particular DragEvent. - 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. - To retrieve the ClipData, we chain up the following methods,
event.getClipDescription().getLabel().toString()
. event.getResult()
returns a boolean that determines if the DragShadow is dropped within the bounds it was supposed to or not.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.
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