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:
Make the
RecyclerView
's layout height tomatch_parent
and to be on top of your upper view (is it aToolbar
?) 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.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
Android Path to Asset Txt File
Android: Determine Active Input Method from Code
Android: Open Activity Without Save into the Stack
How to Create Android Spinner Without Down Triangle on the Right Side of the Widget
Starting Frame-By-Frame Animation
How to Create a .CSV on Android
Sharing a Png Image in Drawable Folder
Getting MAC Address in Android 6.0
Theme/Style Is Not Applied When Inflater Used with Applicationcontext
How to Call Methods of a Service from Activity
Android: How to Center Title in Toolbar
Autocompletetextview with Custom Adapter and Filter
Horizontal Scrolling Grid View
Android Cancel Toast When Exiting the App and When Toast Is Being Shown