Zum Hauptinhalt springen

Drag and Drop Implementation in Android

Drag and Drop is a popular feature in many modern applications. It allows users to move elements around the screen, making it easier to organize and manipulate data. In Android, Drag and Drop can be implemented in many different ways. In this article, we will explore the basics of Drag and Drop and how to implement this feature in Android applications.

What is Drag and Drop?

Drag and Drop is a user interface feature that allows users to move items from one location to another on the screen. This can be done by clicking on an item, dragging it to a new location, and dropping it there. Drag and Drop is a convenient way to organize data and can be used in a variety of applications, including calendars, task lists, and photo editors.

Implementing Drag and Drop in Android

Implementing Drag and Drop in Android requires a few basic steps. These steps are outlined below.

Step 1: Create a View

The first step in implementing Drag and Drop in Android is to create a View. This View will be the item that the user will be able to drag and drop. There are many different types of Views that can be used, including TextViews, ImageViews, and Buttons.

Step 2: Enable Dragging

Once the View has been created, the next step is to enable dragging. This can be done by setting the View's OnLongClickListener. When the user long presses the View, the OnLongClickListener will be triggered, and the View will become draggable.

Step 3: Implement Dragging

After the View has been enabled for dragging, the next step is to implement dragging. This can be done by overriding the View's OnDragListener. The OnDragListener will be triggered when the user starts dragging the View. In the OnDragListener, you can specify what should happen when the View is dragged, including changing its position on the screen.

Step 4: Implement Dropping

The final step in implementing Drag and Drop in Android is to implement dropping. This can be done by overriding the OnDragListener's onDrop method. When the user drops the View, the onDrop method will be triggered, and you can specify what should happen when the View is dropped. This can include updating data or changing the appearance of the View.

Example Code

Here is an example of how to implement Drag and Drop in Android:

// Create a TextView
TextView textView = new TextView(this);
textView.setText("Drag me!");

// Enable Dragging
textView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
ClipData.Item item = new ClipData.Item("text");
ClipData dragData = new ClipData("text", new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }, item);
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(v);
v.startDrag(dragData, shadowBuilder, null, 0);
return true;
}
});

// Implement Dragging
textView.setOnDragListener(new View.OnDragListener() {
@Override
public boolean onDrag(View v, DragEvent event) {
int action = event.getAction();
switch (action) {
case DragEvent.ACTION_DRAG_STARTED:
break;
case DragEvent.ACTION_DRAG_ENTERED:
break;
case DragEvent.ACTION_DRAG_LOCATION:
break;
case DragEvent.ACTION_DRAG_EXITED:
break;
case DragEvent.ACTION_DROP:
// Handle the drop
break;
case DragEvent.ACTION_DRAG_ENDED:
break;
default:
break;
}
return true;
}
});

In this next section you will learn about a DragEvent using several Drag and Drop step by examples.

What is DragEvent?

The system transmits a DragEvent when a drag and drop operation is performed. This dragEvent is a data structure that contains several pieces of information about the operation and the data that is being moved.

An object receiving a drag event calls the getAction() method, which then returns the state of the drag and drop operation. By doing this, a View object can change its appearance or perform other actions when its state changes. For example, a View can react to the ACTION_DRAG_ENTERED action type by by changing one or more colors in its displayed image.

This image is called a drag shadow. Several action types reflect the position of the drag shadow relative to the View receiving the event when a user drags and drops an image.

Examples

Let's assume you are creating some cool notes app and you want to give users the ability to drag and drop the individual notes item and save their final positions let's say in a database. Well the first step in that is understanding how to drag drop items in an adapterview, e.g listview, gridview, recyclerview.

In this example you will learn:

  1. How to drag and drop items in a grid.
  2. How to get the items final position.

We use the following languages:

  1. Kotlin
  2. Java

NB/= This article is broken down into several pages. Each page teaches a unique concept. Here are the taught concepts:

  1. Drag Drop in a Grid
  2. Drag Drop in a RecyclerView

Example 1: Drap Drop Items in a GridView

Let us see how to drag drop items in a GridView. This is a simple example.

Step 1: Dependencies

We use no third party dependencies.

Step 2: Code

We write our code in Java.

We have two classes, a fragment hosted inside an activity:

MainFragment.java

package info.camposha.dragdropexample;

import android.content.ClipData;
import android.graphics.Color;
import android.os.Bundle;
import android.view.DragEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import androidx.fragment.app.Fragment;

public class MainFragment extends Fragment {

private View[] views;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_main, container, false);
views = new View[rootView.getChildCount()];
for (int i = 0; i < rootView.getChildCount(); i++) {
final TextView view = (TextView) rootView.getChildAt(i);
initView(view);
views[i] = view;
}
return rootView;
}

private void initView(final TextView view) {
view.setOnDragListener((v, event) -> {
switch(event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
return true;

case DragEvent.ACTION_DRAG_ENTERED:
v.setBackgroundColor(Color.LTGRAY);
return true;

case DragEvent.ACTION_DRAG_LOCATION:
return true;

case DragEvent.ACTION_DRAG_EXITED:
v.setBackgroundColor(Color.TRANSPARENT);
return true;

case DragEvent.ACTION_DROP:
v.setBackgroundColor(Color.TRANSPARENT);
int dragVal = Integer.parseInt(event.getClipData().getItemAt(0).getText().toString());
int viewVal = Integer.parseInt(((TextView) v).getText().toString());
((TextView) v).setText("" + (dragVal + viewVal));
Toast.makeText(getContext(), "Added", Toast.LENGTH_SHORT).show();
return true;

case DragEvent.ACTION_DRAG_ENDED:
return true;

default:
break;
}
return false;
});

view.setOnLongClickListener(v -> {
ClipData data = ClipData.newPlainText("value", view.getText());
view.startDrag(data, new View.DragShadowBuilder(v), null, 0);
return true;
});
}

}

MainActivity

To host our fragment:

package info.camposha.dragdropexample;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

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

Step 3: Layouts

Copy the layout from the source code.

Step 4: Run

If your run the project you'll get the following:

Step 5: Download

Download the full source code here.

Example 2: Kotlin Android Drag Drop

This is a simple example showing howto implement drag and drop in Kotlin.

Step 1: Create Project

Start by creating an empty Android Studio project.

Step 2: Dependencies

No third party library is needed for our implementation of Drag Drop.

Step 3: Create a Drag and Drop Container

You need to create a custom Drag and drop layout. This layout is based on the FrameLayout and will be used in our layout:

DragAndDropContainer.kt

package supahsoftware.androidexampledragdrop

import android.content.ClipData
import android.content.ClipDescription
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout

class DragAndDropContainer(
context: Context,
attrs: AttributeSet?
) : FrameLayout(context, attrs) {

private val dragAndDropListener by lazy { DragAndDropListener() }
private var content: View? = null

init {
setOnDragListener(dragAndDropListener)
}

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
updateChild()
}

fun setContent(view: View) {
removeAllViews()
addView(view)
updateChild()
}

fun removeContent(view: View) {
view.setOnLongClickListener(null)
removeView(view)
updateChild()
}

private fun updateChild() {
validateChildCount()
content = getFirstChild()
content?.setOnLongClickListener { startDrag() }
}

private fun getFirstChild() = if (childCount == 1) getChildAt(0) else null

private fun validateChildCount() = check(childCount <= 1) {
"There should be a maximum of 1 child inside of a DragAndDropContainer, but there were $childCount"
}

private fun startDrag(): Boolean {
content?.let {
val tag = it.tag as? CharSequence
val item = ClipData.Item(tag)
val data = ClipData(tag, arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN), item)
val shadow = DragShadowBuilder(it)

if (Build.VERSION.SDK_INT >= 24) {
it.startDragAndDrop(data, shadow, it, 0)
} else {
it.startDrag(data, shadow, it, 0)
}
return true
} ?: return false
}
}

Step 4: Design Layout

Design your MainActivity's layout as follows:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<supahsoftware.androidexampledragdrop.DragAndDropContainer
android:id="@+id/container_1"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/outline_gray_solid"
app:layout_constraintTop_toTopOf="parent">

<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:id="@+id/content_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/banner"
android:scaleType="center" />

</FrameLayout>

</supahsoftware.androidexampledragdrop.DragAndDropContainer>

<supahsoftware.androidexampledragdrop.DragAndDropContainer
android:id="@+id/container_2"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/outline_gray_solid"
app:layout_constraintTop_toBottomOf="@id/container_1" />

<supahsoftware.androidexampledragdrop.DragAndDropContainer
android:id="@+id/container_3"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/outline_gray_solid"
app:layout_constraintTop_toBottomOf="@id/container_2" />

<supahsoftware.androidexampledragdrop.DragAndDropContainer
android:id="@+id/container_4"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/outline_gray_solid"
app:layout_constraintTop_toBottomOf="@id/container_3" />

</androidx.constraintlayout.widget.ConstraintLayout>

Step 5: Create a Drag and Drop Listener

As follows:

DragAndDropListener.kt

package supahsoftware.androidexampledragdrop

import android.content.ClipDescription
import android.view.DragEvent
import android.view.View
import androidx.core.content.ContextCompat

class DragAndDropListener : View.OnDragListener {
override fun onDrag(view: View, event: DragEvent): Boolean {
return when (event.action) {
DragEvent.ACTION_DRAG_ENTERED -> {
view.setDashedOutline(); true
}
DragEvent.ACTION_DRAG_EXITED -> {
view.setSolidOutline(); true
}
DragEvent.ACTION_DROP -> {
val draggingView = event.localState as View
val draggingViewParent = draggingView.parent as DragAndDropContainer
draggingViewParent.removeContent(draggingView)

val landingContainer = view as DragAndDropContainer
landingContainer.setContent(draggingView)
landingContainer.setSolidOutline()
true
}
DragEvent.ACTION_DRAG_STARTED -> event.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)
else -> true
}
}

private fun View.setSolidOutline() {
background = ContextCompat.getDrawable(context, R.drawable.outline_gray_solid)
}

private fun View.setDashedOutline() {
background = ContextCompat.getDrawable(context, R.drawable.outline_gray_dashed)
}
}

Step 6: Create MainActivity

Create MainActivity as follows:

MainActivity.kt

package supahsoftware.androidexampledragdrop

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}

Run

Copy the code or download it in the link below, build and run.

Reference

Here are the reference links:

NumberLink
1.Download Example
2.Follow code author
3.Code: Apache 2.0 License

Example 1: Implement Drag and Drop in Recyclerview

In this section we will look at Drag and Drop implementation in a RecyclerView.

Here we see a Words and sentence app that teaches implementation of Drag and drop in a recyclerview.

Here is the demo of the project:

RecyclerView Drag and Drop Example

Step 1: Create Project

Start by creating an empty Android Studio project.

Step 2: Dependencies

Add dependencies as follows:

dependencies {
kotlin("stdlib")
implementation("com.google.android.material:material:1.4.0")
implementation("androidx.appcompat:appcompat:1.3.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.0")
implementation("com.google.android:flexbox:2.0.1")
}

Step 3: Design Layouts

Design two layouts as follows:

(a). item_word.xml

Layout for a single word:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tvWord"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="@drawable/bg_word"
android:elevation="4dp"
android:padding="12dp"
android:textColor="@android:color/black"
tools:text="Drag and Drop" />

(b). activity_main.xml

Will contain two recyclerviews. Item will be dragged from one recyclerview and dropped onto another:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_gradient"
tools:context=".MainActivity">

<androidx.cardview.widget.CardView
android:id="@+id/rvSentenceCard"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:layout_constraintBottom_toTopOf="@id/rvWords"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSentence"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never" />
</androidx.cardview.widget.CardView>

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvWords"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:overScrollMode="never"
android:padding="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rvSentenceCard" />
</androidx.constraintlayout.widget.ConstraintLayout>

Step 4: Create CallBacks

(a). DragListener.kt

A DragListener callback for the draggable view to update it's visibility:

class DragListener : View.OnDragListener {
override fun onDrag(view: View, dragEvent: DragEvent): Boolean {
when (dragEvent.action) {
// hide view when drag has been started
DragEvent.ACTION_DRAG_ENTERED -> view.visibility = View.INVISIBLE
// show view when drag has been ended
DragEvent.ACTION_DRAG_ENDED -> view.visibility = View.VISIBLE
}

return true
}
}

(a). DropListener.kt

A DropListener Callback for the target view where dragged items will be dropped

class DropListener(private val onDrop: () -> Unit) : View.OnDragListener {
override fun onDrag(view: View, dragEvent: DragEvent): Boolean {
when (dragEvent.action) {
// animate and highlight a background under the view to show the borders of the drop area
DragEvent.ACTION_DRAG_ENTERED -> {
val bgColor = ContextCompat.getColor(view.context, R.color.colorWhiteTransparent)
if (view.background is ColorDrawable && (view.background as ColorDrawable).color == bgColor) return true

ValueAnimator.ofArgb(Color.TRANSPARENT, bgColor).apply {
addUpdateListener {
val color = it.animatedValue as Int
view.setBackgroundColor(color)
}
duration = 500
}.start()
}
// animate and hide the highlight under the drop area
DragEvent.ACTION_DRAG_ENDED -> {
val bgColor = ContextCompat.getColor(view.context, R.color.colorWhiteTransparent)
if (view.background is ColorDrawable && (view.background as ColorDrawable).color == Color.TRANSPARENT) return true

ValueAnimator.ofArgb(bgColor, Color.TRANSPARENT).apply {
addUpdateListener {
val color = it.animatedValue as Int
view.setBackgroundColor(color)
}
duration = 500
}.start()
}
// when item has been dropped, notify about it
DragEvent.ACTION_DROP -> onDrop()
}

return true
}
}

(c). WordsDiffCallback.kt

This is a DiffUtil.ItemCallback for our adapters:

class WordsDiffCallback : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean = oldItem == newItem

override fun areContentsTheSame(oldItem: String, newItem: String): Boolean = oldItem == newItem
}

Step 5: Create Adapters

(a). WordsAdapter.kt

A androidx.recyclerview.widget.RecyclerView adapter to show draggable items. The @param onDragStarted will provide the current draggable view value, String in this case:

class WordsAdapter(private val onDragStarted: (String) -> Unit) : ListAdapter<String, WordsAdapter.WordsViewHolder>(WordsDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_word, parent, false)
return WordsViewHolder(view)
}

override fun onBindViewHolder(holder: WordsViewHolder, position: Int) {
holder.bind(getItem(position))
}

fun removeItem(selectedWord: String) {
val list = ArrayList(currentList)
list.remove(selectedWord)
submitList(list)
}

inner class WordsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(word: String) = itemView.run {
findViewById<TextView>(R.id.tvWord).text = word

setOnLongClickListener { view ->
// when user is long clicking on a view, drag process will start
val data = ClipData.newPlainText("", word)
val shadowBuilder = View.DragShadowBuilder(view)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
view.startDragAndDrop(data, shadowBuilder, view, 0)
} else {
view.startDrag(data, shadowBuilder, view, 0)
}
onDragStarted(word)
true
}

setOnDragListener(DragListener())
}
}
}

(b). SentenceAdapter.kt

A androidx.recyclerview.widget.RecyclerView adapter to show dropped items:

class SentenceAdapter : ListAdapter<String, SentenceAdapter.WordsViewHolder>(WordsDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_word, parent, false)
return WordsViewHolder(view)
}

override fun onBindViewHolder(holder: WordsViewHolder, position: Int) {
holder.bind(getItem(position))
}

fun addItem(selectedWord: String) {
val list = ArrayList(currentList)
list.add(selectedWord)
submitList(list)
}

inner class WordsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(word: String) {
itemView.findViewById<TextView>(R.id.tvWord).text = word
}
}
}

Step 6: Create MainActivity

Lastly create mainactivity as follows:

MainActivity.kt

class MainActivity : AppCompatActivity() {
// values of the draggable views (usually this should come from a data source)
private val words = mutableListOf("world", "a", "!", "What", "wonderful")
// last selected word
private var selectedWord = ""

private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)

val sentenceAdapter = SentenceAdapter()
val wordsAdapter = WordsAdapter {
selectedWord = it
}.apply {
submitList(words)
}

binding.rvSentence.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
binding.rvSentence.adapter = sentenceAdapter

binding.rvSentence.setOnDragListener(
DropListener {
wordsAdapter.removeItem(selectedWord)
sentenceAdapter.addItem(selectedWord)
}
)

binding.rvWords.layoutManager = FlexboxLayoutManager(this, FlexDirection.ROW, FlexWrap.WRAP).apply {
justifyContent = JustifyContent.SPACE_EVENLY
alignItems = AlignItems.CENTER
}

binding.rvWords.adapter = wordsAdapter
}
}

Run

Copy the code or download it in the link below, build and run.

Reference

Here are the reference links:

NumberLink
1.Download Example
2.Follow code author
3.Code: Apache 2.0 License

Conclusion

Drag and Drop is a useful feature that can be implemented in many Android applications. By following the steps outlined in this article, you can easily enable Drag and Drop in your own applications. Remember to experiment with different Views and Drag and Drop implementations to find the best solution for your specific needs.