I am trying to implement ItemTouchHelper for Horizontal Recyclerview. (setting layout manager to LinearLayoutManager with orientation LinearLayoutManager.HORIZONTAL). Example, I want to delete an item when swiped down and drag to left or right for swapping items.
All the samples I have gone through explains ItemTouchHelper for Vertical Recyclerview or items in grid.
Followed samples from following links:
https://medium.com/#ipaulpro/drag-and-swipe-with-recyclerview-b9456d2b1aaf
https://medium.com/#xabaras/recyclerview-swiping-with-style-151e21b1af07
How can I achieve swipe down to delete and drag sideways to swap items in Horizontal Recyclerview?
You can use this simple code to achieve the swipe down to remove.
ItemTouchHelper.SimpleCallback simpleCallback = new ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.DOWN) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
final int position = viewHolder.getLayoutPosition();
if (direction == ItemTouchHelper.DOWN) {
//your code for deleting the item from database or from the list
adapter.removeNote(position);
adapter.notifyItemRemoved(position)
}
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
For Kotlin geeks below is the code for the same -
val simpleCallback = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.DOWN) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
if (direction == ItemTouchHelper.DOWN) {
//your code for deleting the item from database or from the list
val position = viewHolder.adapterPosition
noteList.removeAt(position)
adapter.notifyItemRemoved(position)
}
}
}
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(recycler_view)
You only need to change the method "getMovementFlags".
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
to
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
final int swipeFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
final int dragFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
To delete an item, create a new method in "ItemTouchHelperAdapter" called "onSwiped" and implement it to remove the item
#Override
public boolean onSwiped(int itemPosition, int direction) {
if(direction == SimpleItemTouchHelperCallback.SWIPED_TO_END) {
list.remove(itemPosition);
notifyItemRemoved(itemPosition);
}else{
notifyItemChanged(itemPosition);
}
return true;
}
Related
This is my implementation of ItemTouchHelper.callback
I implemented getMovementFlags so that it could drag and drop in UP, Down, left, and right directions but didn't execute the onMove method
I know where the problem is because I'm in the setOnLongClickListener notifyDataSetChanged on the item and that causes it to fail to drag but I do need to go through setOnLongClickListener, okay NotifyDataSetChanged updates the item to editable style
#Override
public int getMovementFlags(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
}
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();
int toPosition = target.getAdapterPosition();
dragAdapter.itemMove(fromPosition, toPosition);
return true;
}
LEFT and RIGHT should be used for swipe, so keep UP and DOWN only for move position.
And, onMove will be called many many times, so you should implement clearView and do the move in clearView. onMove is just used to record positions.
private val _callback = object:ItemTouchHelper.Callback() {
override fun getMovementFlags(rv: RecyclerView, h: RecyclerView.ViewHolder): Int {
val drag = ItemTouchHelper.UP or ItemTouchHelper.DOWN
val pos = h.layoutPosition
val swipe = if (pos == _a._current) 0 else ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT // don't allow swipe current row
return makeMovementFlags(drag, swipe)
}
// onMove will be called many many times if dragging toward top/bottom
// to avoid this, make real swap at clearView which will be called only once
private var _moveFrom = -1
private var _moveTo = -1
override fun onMove(rv: RecyclerView, h: RecyclerView.ViewHolder, dst: RecyclerView.ViewHolder): Boolean {
if (_moveFrom == -1) {
_moveFrom = h.layoutPosition
}
_moveTo = dst.layoutPosition
return true
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
if (_moveFrom >= 0 && _moveTo >= 0 && _moveFrom != _moveTo) {
_layers.removeAt(_moveFrom)
_layers.add(_moveTo, layer)
if (_moveFrom < _moveTo) { // drag down
...
} else { // drag up
...
}
}
_moveFrom = -1
_moveTo = -1
Handler(Looper.getMainLooper()).post { _adapter.notifyDataSetChanged() }
}
}
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".
private ItemTouchHelper.Callback dragCallback = new ItemTouchHelper.Callback() {
int dragFrom = -1;
int dragTo = -1;
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(ItemTouchHelper.UP|ItemTouchHelper.DOWN|ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT,
0);
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();
int toPosition = target.getAdapterPosition();
if(dragFrom == -1) {
dragFrom = fromPosition;
}
dragTo = toPosition;
adapter.onItemMove(fromPosition, toPosition);
return true;
}
private void reallyMoved(int from, int to) {
// I guessed this was what you want...
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
#Override
public boolean isLongPressDragEnabled() {
return true;
}
#Override
public boolean isItemViewSwipeEnabled() {
return false;
}
#Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if(dragFrom != -1 && dragTo != -1 && dragFrom != dragTo) {
reallyMoved(dragFrom, dragTo);
}
dragFrom = dragTo = -1;
}
};
adapter.onItemMove(fromPosition, toPosition) was like below:
public void onItemMove(int fromPosition, int toPosition) {
list.add(toPosition, list.remove(fromPosition));
notifyItemMoved(fromPosition, toPosition);
}
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:
private final class MyCallback extends ItemTouchHelper.Callback {
private boolean mOrderChanged;
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
// Check if positions of viewHolders correspond to underlying model, and if not, flip the items in the model and set the mOrderChanged flag
mOrderChanged = true;
}
#Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (actionState == ItemTouchHelper.ACTION_STATE_IDLE && mOrderChanged) {
doSomething();
mOrderChanged = false;
}
}
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 of ACTION_STATE_DRAG. When the drag is over, it is called with action state of ACTION_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 the onItemDragged(Int, Int) callback is intended for updating positions in a database at the end of the gesture.
class ItemGestureHelper(private val listener: OnItemGestureListener) : ItemTouchHelper.Callback() {
interface OnItemGestureListener {
fun onItemDrag(fromPosition: Int, toPosition: Int): Boolean
fun onItemDragged(fromPosition: Int, toPosition: Int)
fun onItemSwiped(position: Int)
}
private var dragFromPosition = -1
private var dragToPosition = -1
// Other methods omitted...
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
// Item is being dragged, keep the current target position
dragToPosition = target.adapterPosition
return listener.onItemDrag(viewHolder.adapterPosition, target.adapterPosition)
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
listener.onItemSwiped(viewHolder.adapterPosition)
}
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
when (actionState) {
ItemTouchHelper.ACTION_STATE_DRAG -> {
viewHolder?.also { dragFromPosition = it.adapterPosition }
}
ItemTouchHelper.ACTION_STATE_IDLE -> {
if (dragFromPosition != -1 && dragToPosition != -1 && dragFromPosition != dragToPosition) {
// Item successfully dragged
listener.onItemDragged(dragFromPosition, dragToPosition)
// Reset drag positions
dragFromPosition = -1
dragToPosition = -1
}
}
}
}
}
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
so i have implemented this itemtouchhelper.simple callback on a recyclerview rv.
now in this rv i have set 2 kinds on layout as a row depending on the content type.
so as i set this touchhelper on the rv it is being implemented on both of these layouts even though i did'nt want to do that.i only want to apply that swipe to only one type of this layout.
ItemTouchHelper.SimpleCallback ith = new ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, #NonNull RecyclerView.ViewHolder viewHolder1) {
return false;
}
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int i) {
if(viewHolder.itemView.findViewById(R.id.messageholder_from) != null)
{
Log.d("texts", "onSwiped: "+viewHolder.itemView.findViewById(R.id.messageholder_from).findViewById(R.id.crfrom));
}
adapter.notifyDataSetChanged();
}
};
new ItemTouchHelper(ith).attachToRecyclerView(rv);
as you can see this code i only want to implement the swipe on the row which has this messageholder_from child in it otherwise i don't want to implement the swipe.
is there any way to remove the swipe animation and the listener on this other child messageholder_to.
my app shows either a to_layout or a from_layout depending on he message id.
thanks for any kind of help.
Inside your ItemTouchHelper.SimpleCallback, override the getSwipeDirs() method and return 0 for any row that you want to disable swiping on.
#Override
public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
if (viewHolder.itemView.findViewById(R.id.messageholder_from) == null) {
return 0;
}
return super.getSwipeDirs(recyclerView, viewHolder);
}
Depending on exactly how your app is set up, there might be a better way to detect that viewHolder is the kind you want to disallow swiping on. For example, maybe you could have
if (viewHolder instanceof WrongKindOfViewHolder)
or
if (viewHolder.isNotSwipeable)
int makeMovementFlags (int dragFlags,int swipeFlags) is responsible for each row move, swipe or drag.
From documentation :
Convenience method to create movement flags.
For instance, if you want to let your items be drag & dropped
vertically and swiped left to be dismissed, you can call this method
with: makeMovementFlags(UP | DOWN, LEFT);
Implement getMovementFlags() method and return with makeMovementFlags(0, 0); for which row or viewholder you don't want to add swipe or drag.
Example Code:
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = 0;
int swipeFlags = 0;
if (recyclerView.getLayoutManager().getClass() == LinearLayoutManager.class ) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int orientation = linearLayoutManager.getOrientation();
if (orientation == LinearLayoutManager.VERTICAL) {
swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
}else {
// horizontal
dragFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
// no swipe for horizontal layout
swipeFlags = 0;
}
}
if (viewHolder.getAdapterPosition() == 3) {
// disable swipe feature for position 3
return makeMovementFlags(0, 0);
}
return makeMovementFlags(dragFlags, swipeFlags);
}
I created drag and drop in recylerview using ItemTouchHelper.Callback,it is working.But i have header and footer in recylerview i dont want to drag header and footer.how to solve this,this is my code
recylerviewActivity
ItemTouchHelper.Callback callback = new SwipeAndDrag(pick_up_mAdapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(recyclerView);
SwipeAndDrag.java
public class SwipeAndDrag extends ItemTouchHelper.Callback {
private final ItemTouchHelperAdapter mAdapter;
private RouteInformation points;
public SwipeAndDrag(ItemTouchHelperAdapter adapter) {
mAdapter = adapter;
}
#Override
public boolean isLongPressDragEnabled() {
return false;
}
#Override
public boolean isItemViewSwipeEnabled() {
return true;
}
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
}
}
You should modify your getMovementFlags method like this (tested it and it seems ok) :
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
if(viewHolder instanceof yourFooterOrHeaderViewholderClass){
int dragFlags = 0; // then, they can't be dragged
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}else{ // if(viewHolder instanceof yourNormalItemViewHolderClass)
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
}
Then, only your items should be drag&dropped, whereas your header and footer couldn't be manually moved.
I was wondering if you could use the itemtouchhelper to dismiss items , ONLY when user swipes to right? I'm using this code, but the direction does not work
public class CrimeTouchHelper extends ItemTouchHelper.SimpleCallback {
private CrimeAdapter mMovieAdapter;
public CrimeTouchHelper(CrimeAdapter movieAdapter){
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
this.mMovieAdapter = movieAdapter;
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
//TODO: Not implemented here
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
//Remove item
Log.e("DIRECTION", direction + "");
if(direction == 8) {
mMovieAdapter.remove(viewHolder.getAdapterPosition());
}
}
}`
This is simple to do. You can use this code,
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new
ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(
final RecyclerView recyclerView,
final RecyclerView.ViewHolder viewHolder,
final RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onSwiped(
final RecyclerView.ViewHolder viewHolder,
final int swipeDir) {
adapter.remove(viewHolder.getAdapterPosition());
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(
simpleItemTouchCallback
);
itemTouchHelper.attachToRecyclerView(itemsRecyclerView);
You can pass ItemTouchHelper.RIGHT as the second parameter of the constructor which actaully takes the direction.
Use the onSwipe() method to perform the operation you want to do, like removing the item from the adapter.
Hope this helps. Do let me know if you face any problem with it.
Set it in the super method in the constructor of CrimeTouchHelper.
The second parameter is swipe directions, so just pass ItemTouchHelper.RIGHT
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.RIGHT);
After that, you can remove the 'if' check in #onSwiped
Your current if check in #onSwiped will not prevent the swipe. Assuming 8 is the int value for swiping right, all you're doing now is preventing the adapter from removing the item (but that item will still visually be swiped away).
You should override getMovementFlags method and set swipe flag to ItemTouchHelper.START in your customItemTouchHelper.Callback class
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
// Set movement flags based on the layout manager
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
final int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
} else {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
final int swipeFlags = ItemTouchHelper.START;
return makeMovementFlags(dragFlags, swipeFlags);
}
}
This will block the swipe from left to right.To do the opposite direction set flag to ItemTouchHelper.END