I have a problem with ItemTouchHelper of RecyclerView.
I am making a game. The game board is actually a RecyclerView. RecyclerView has GridLayoutManager with some span count. I want to implement drag & drop recyclerview's items. Any item can dragging over all directions (up, down, left, right).
private void initializeLayout() {
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutFrozen(true);
recyclerView.setNestedScrollingEnabled(false);
// set layout manager
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), BOARD_SIZE,
LinearLayoutManager.VERTICAL, true);
recyclerView.setLayoutManager(layoutManager);
// Extend the Callback class
ItemTouchHelper.Callback itemTouchCallback = new ItemTouchHelper.Callback() {
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
Log.w(TAG, "onMove");
return false;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
// Application does not include swipe feature.
}
@Override
public void onMoved(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
int fromPos, RecyclerView.ViewHolder target, int toPos, int x, int y) {
Log.d(TAG, "onMoved");
// this is calling every time, but I need only when user dropped item, not after every onMove function.
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.START | ItemTouchHelper.END;
int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
}
};
ItemTouchHelper touchHelper = new ItemTouchHelper(itemTouchCallback);
touchHelper.attachToRecyclerView(recyclerView);
}
SO, why ItemTouchHelper's onMoved function works when I still dragging item on the RecyclerView ? How can I achieve this ?
While dragging and dropping an item, the onMove() can be called more than once, but the clearView() will be called once. So you can use this to indicate the drag was over(drop was happened). And use two variables dragFrom and dragTo to trace the really position in a completed "drag & drop".
adapter.onItemMove(fromPosition, toPosition) was like below:
list.add(toPosition, list.remove(fromPosition)); notifyItemMoved(fromPosition, toPosition);
You must implement OnMove listener in you adapter:
Collections.swap(youCoolList, fromPosition, toPosition); notifyItemMoved(fromPosition, toPosition);
like this man doing https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-b9456d2b1aaf#.blviq6jxp
special grid example https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-6a6f0c422efd#.xb74uu7ke
The
onSelectedChanged(RecyclerView.ViewHolder, int)
callback provides information about the current actionState:-
ACTION_STATE_IDLE
:-
ACTION_STATE_DRAG
-
ACTION_STATE_SWIPE
So you could keep track whether the order changed, and when the state changes to
ACTION_STATE_IDLE
, you can do what you need to do!Example:
I did some tests and
onSelectedChanged(RecyclerView.ViewHolder?, Int)
seemed most reliable for me to detect end of the gesture (drop). The method is called whenever an item is being dragged and passed action state ofACTION_STATE_DRAG
. When the drag is over, it is called with action state ofACTION_STATE_IDLE
.See my solution below. The
onItemDrag(Int, Int)
callback is used for reordering items in an adapter as the item is being dragged. On the other hand theonItemDragged(Int, Int)
callback is intended for updating positions in a database at the end of the gesture.