Drag and Drop Items in Recyclerview with Gridlayoutmanager

Drag and drop items in RecyclerView with GridLayoutManager

There is actually a better way to achieve this. You can use some of the RecyclerView's "companion" classes:

ItemTouchHelper, which is

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

and its ItemTouchHelper.Callback, which is

the contract between ItemTouchHelper and your application

// Create an `ItemTouchHelper` and attach it to the `RecyclerView`
ItemTouchHelper ith = new ItemTouchHelper(_ithCallback);
ith.attachToRecyclerView(rv);

// Extend the Callback class
ItemTouchHelper.Callback _ithCallback = new ItemTouchHelper.Callback() {
//and in your imlpementaion of
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
// get the viewHolder's and target's positions in your adapter data, swap them
Collections.swap(/*RecyclerView.Adapter's data collection*/, viewHolder.getAdapterPosition(), target.getAdapterPosition());
// and notify the adapter that its dataset has changed
_adapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}

@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
//TODO
}

//defines the enabled move directions in each state (idle, swiping, dragging).
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return makeFlag(ItemTouchHelper.ACTION_STATE_DRAG,
ItemTouchHelper.DOWN | ItemTouchHelper.UP | ItemTouchHelper.START | ItemTouchHelper.END);
}
};

For more details check their documentation.

Add Drag and Drop on RecyclerView with DiffUtil

I ended up implementing a new adapter and use it instead of ListAdapter, as mentioned on Martin Marconcini's answer. I added two separate functions. One for receiving updates from Room database (replacement for submitList from ListAdapter) and another for every position change from drag

MyListAdapter.kt

class MyListAdapter(list: ArrayList<Item>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

// save instance instead of creating a new one every submit
// list to save some allocation time. Thanks to Martin Marconcini
private val diffCallback = DiffCallback(list, ArrayList())

fun submitList(updatedList: List<Item>) {
diffCallback.newList = updatedList
val diffResult = DiffUtil.calculateDiff(diffCallback)

list.clear()
list.addAll(updatedList)
diffResult.dispatchUpdatesTo(this)
}

fun itemMoved(from: Int, to: Int) {
Collections.swap(list, from, to)
notifyItemMoved(from, to)
}

}

DiffCallback.kt

class DiffCallback(
val oldList: List<Item>,
var newList: List<Item>
) : DiffUtil.Callback() {

override fun getOldListSize(): Int {
return oldList.size
}

override fun getNewListSize(): Int {
return newList.size
}

override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
return oldItem.id == newItem.id
}

override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
return compareContents(oldItem, newItem)
}
}

Call itemMoved every position change:

override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val from = viewHolder.bindingAdapterPosition
val to = target.bindingAdapterPosition
itemListAdapter.itemMoved(from, to)

// Update database as well if needed

return true
}

When receiving updates from Room database:

You may also want to check if currently dragging using onSelectedChanged if you are also updating your database every position change to prevent unnecessary calls to submitList

list.observe(viewLifecycleOwner, { updatedList ->
listAdapter.submitList(updatedList)
})

Drag Drop halt for Specific Position in RecyclerView

Finally, after lots of research in ItemTouchHelper class i resolved it like this.

What i have done is set the flag as ACTION_STATE_IDLE where i want to halt the Drag and Drop. Hope it helps someone like me.

Below is the Kotlin version. If anyone needs the same in JAVA i can convert it.

//defines the enabled move directions in each state (idle, swiping, dragging).
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
// The position i want to lock/halt
if (list[viewHolder.adapterPosition].trim().isEmpty()){
return makeFlag(ItemTouchHelper.ACTION_STATE_IDLE, ItemTouchHelper.DOWN or ItemTouchHelper.UP)
}
// The position i want to lock/halt
if (viewHolder.adapterPosition == list.size - 1){
return makeFlag(ItemTouchHelper.ACTION_STATE_IDLE, ItemTouchHelper.DOWN or ItemTouchHelper.UP)
}
// else enabling ACTION_STATE_DRAG
return makeFlag(ItemTouchHelper.ACTION_STATE_DRAG,
ItemTouchHelper.DOWN or ItemTouchHelper.UP or ItemTouchHelper.START or ItemTouchHelper.END)
}

Drag and Drop view outside of Recyclerview

You are bound by the RecyclerView boundries. You have several options:

  1. Make the RecyclerView's layout height to match_parent and to be on top of your upper view (is it a Toolbar?) and add a sticky header of the same size and have an empty transparent layout. That way you could drag ther and see the item floating over there.

  2. Instead of dragging an item to a garbage can icon which is located too close to a legitemate upper-right item, make a long click to select the item (and apply a signal like a check mark or a red mask) and make the garbage can appear and delete uppon click (and maybe allow multi item deleting)



Related Topics



Leave a reply



Submit