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 I**temTouchHelper.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 set up the layout manager 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>

It’s better the design the UI that has the handler for the drag and drop. it’s better for understandability.

Recyclerview item design

Setup recyclerview adapter

Create the recyclerview adapter class and add them AsyncListDiffer() to handle the data in the adapter. also, we need to attach them 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 the 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 the ItemTouchHelper.SimpleCallback() method. Simple callbacks need two 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 if 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 the onSwiped() a method used for swiping 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 a 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 cle**arView()** of the ItemTouchHelper.SimpleCallback().

In this example, I have animated the 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.


Comments

Leave a Reply

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


Latest Posts