Drag And Drop Recyclerview Item Android [Example]

In this post, I have explained how to make a recyclerview item drag and dropable or swipeable by using the Android Support Library ItemTouchHelper.

Drag and drop recyclerview item with animation

before getting started, check out my other post on recyclerview.

Android RecyclerView Item Animations in Kotlin [Example]

Android Recyclerview Search Filter Example

Implementing Pagination With Recyclerview


ItemTouchHelper

This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.

It works with a RecyclerView and a Callback class, which configures what type of interactions are enabled and also receives events when the user performs these actions.

Depending on which functionality you support, you should override ItemTouchHelper.Callback.onMove(RecyclerView, ViewHolder, ViewHolder) for drag and drop.

ItemTouchHelper.Callback.onSwiped(ViewHolder, int) for swipe to dismiss.

We will be using the onMove method of callback in this article to move items in our recycler view from one position to another.

Step to make recyclerview item drag and dropable

  1. setup recyclerview item
  2. Setup recyclerview adapter
  3. Create a class for drag and drop
  4. Attach TouchHelper to Recyclerview
  5. Animate the Drag and Drop

Setup Recyclerview Item

in your mainactivity.xml create a recyclview and setup the layoutManger and the adapter.

<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:listitem="@layout/adapter_item"
        tools:itemCount="8"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>

Now, we need to create the adapter layout for the recyclerview.

adapter_item.xml

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    app:cardElevation="4dp"
    app:cardCornerRadius="4dp"
    android:layout_margin="8dp">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="8dp">

        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/imgDragHandler"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:padding="8dp"
            app:tint="@color/black"
            android:src="@drawable/ic_baseline_drag_handle_24"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>


        <com.google.android.material.textview.MaterialTextView
            android:id="@+id/textTitle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            style="@style/TextAppearance.AppCompat.Medium"
            android:textColor="@color/black"
            app:layout_constraintStart_toEndOf="@id/imgDragHandler"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            tools:text="@tools:sample/full_names"
            android:layout_margin="8dp" />

        <com.google.android.material.textview.MaterialTextView
            android:id="@+id/textDesc"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            style="@style/TextAppearance.AppCompat.Small"
            android:textColor="@color/black"
            app:layout_constraintStart_toEndOf="@id/imgDragHandler"
            app:layout_constraintTop_toBottomOf="@id/textTitle"
            app:layout_constraintEnd_toEndOf="parent"
            tools:text="@tools:sample/full_names"
            android:layout_margin="8dp"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

</com.google.android.material.card.MaterialCardView>

its better the design the UI that having the handler for the drag and drop. its better for the understandability.

Recyclerview item design

Setup recyclerview adapter

Create the recyclerview adapter class and add the AsyncListDiffer() to handler the data in the adapter. also, we need to attached the DiffUtil.ItemCallback() to it.

RecyclerviewAdapter.kt

class RecyclerviewAdapter : RecyclerView.Adapter<RecyclerviewAdapter.ItemViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = AdapterItemBinding.inflate(inflater, parent, false)
return ItemViewHolder(binding)
}

override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val movieItem= differ.currentList[position]
holder.bindView(movieItem)
}

override fun getItemCount(): Int {
return differ.currentList.size
}

private val differCallback = object: DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}

override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}

}

val differ = AsyncListDiffer(this, differCallback)

fun moveItem(fromPosition: Int, toPosition: Int) {
val list = differ.currentList.toMutableList()
val fromItem = list[fromPosition]
list.removeAt(fromPosition)
if (toPosition < fromPosition) {
list.add(toPosition + 1 , fromItem)
} else {
list.add(toPosition - 1, fromItem)
}
differ.submitList(list)
}


inner class ItemViewHolder(val binding: AdapterItemBinding) : RecyclerView.ViewHolder(binding.root) {

fun bindView(item: User) {
binding.apply {
textTitle.text = item.name
textDesc.text = item.location
}
}
}
}

In the recyclerview adapter, I have added moveItem() function to rearrange the adapter list item based on the recyclerview item position change during the drag and drop.

Create a class for drag and drop

Now in our MainActivity.kt Create a class for dragging by using ItemTouchHelper.SimpleCallback() method. Simple callbacks need to parameters, one is the drag directions and another one is the swipe. Since we’re not implementing the swap directions in this tutorial so add “0” in the swipe parameter. And if you want to add the swipe parameter then go through my previous tutorial on swipe gestures in recyclerView.

For drag, if you’re using the down directions then add ItemTouchHelper.DOWN or if you want to use the up direction then we can use the ItemTouchHelper.UP. The application works even you use UP and DOWN gestures but we will also use START and END. For that use ItemTouchHelper.START and ItemTouchHelper.END.

private val itemTouchHelper by lazy {
val itemTouchCallback = object: ItemTouchHelper.SimpleCallback(UP or DOWN or START or END, 0) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {

return true
}

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {

}



}
ItemTouchHelper(itemTouchCallback)
}

Here onSwiped() method is used for swipe left and right. If you want to swipe your items in both directions in the recyclerView then we can use this method.

Since we’re only implementing the dragging behavior here, so will add our codes inside the onMove() method.

As we have moved the item between its initial and final position, we need to tell the adapter to change its position from these two points. So, we get the item’s position from the adapter and then the target position and let the adapter know that item has moved from position a to position b using notifyItemMoved().

private val itemTouchHelper by lazy {
val itemTouchCallback = object: ItemTouchHelper.SimpleCallback(UP or DOWN or START or END, 0) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val recyclerviewAdapter = recyclerView.adapter as RecyclerviewAdapter
val fromPosition = viewHolder.adapterPosition
val toPosition = target.adapterPosition
recyclerviewAdapter.moveItem(fromPosition, toPosition)
recyclerviewAdapter.notifyItemMoved(fromPosition,toPosition)
return true
}

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {

}

}
ItemTouchHelper(itemTouchCallback)
}

Attach TouchHelper to Recyclerview

Already, we have created recyclerview adapter and touchHelper(). now we need to add both into the recyclerview on oncreate() function.

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
itemTouchHelper.attachToRecyclerView(binding.recyclerview)
recyclerviewAdapter = RecyclerviewAdapter()
recyclerviewAdapter.differ.submitList(getUsers())
binding.recyclerview.adapter = recyclerviewAdapter
}

private fun getUsers() : List<User>{
val users = mutableListOf<User>()
users.add(User("John","Chennai",35))
users.add(User("Mahesh","Pune",32))
users.add(User("Palani","Bengalore",23))
users.add(User("Kumar","Delhi",45))
users.add(User("Dinesh","Mumbai",27))
return users
}

Also, I have created sample data for the recyclerview.

output:

Drag and drop recyclerview item

Animate the Drag and Drop

To animate the recyclerview item drag and drop, we need to override the onSelectedChanged() and clearView() of the ItemTouchHelper.SimpleCallback().

In this example, I have animated alpha and scale of the adapter view.

private val itemTouchHelper by lazy {
val itemTouchCallback = object: ItemTouchHelper.SimpleCallback(UP or DOWN or START or END, 0) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val recyclerviewAdapter = recyclerView.adapter as RecyclerviewAdapter
val fromPosition = viewHolder.adapterPosition
val toPosition = target.adapterPosition
recyclerviewAdapter.moveItem(fromPosition, toPosition)
recyclerviewAdapter.notifyItemMoved(fromPosition,toPosition)
return true
}

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {

}

override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
if(actionState == ACTION_STATE_DRAG) {
viewHolder?.itemView?.scaleY = 1.3f
viewHolder?.itemView?.alpha = 0.7f

}
}

override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
super.clearView(recyclerView, viewHolder)
viewHolder.itemView.scaleY = 1.0f
viewHolder?.itemView?.alpha = 1.0f
}

}
ItemTouchHelper(itemTouchCallback)
}

output:

Drag and drop recyclerview item with animation

Thanks for reading. you can download this example on GitHub.

Leave a Reply

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