I have ItemTouchHelper class that uses swiping and drag and drop for performing actions. But i want to change drag and drop behavior. It should swap positions of 2 elements, the first one I dragged and the other one where it is dropped on. i want to exchange the items of the dragged & dropped positions. not to change positions of all items among both of them.
How to do it
this is my class for drag and drop
public class ItemTouchHelper extends
androidx.recyclerview.widget.ItemTouchHelper.Callback {
private Drawable icon;
private Context context;
private ColorDrawable background;
private final ItemTouchHelperListener dragDropListener;
public ItemTouchHelper(Context context, Drawable icon,
ItemTouchHelperListener dragDropListener) {
this.icon = icon;
this.dragDropListener = dragDropListener;
this.context = context;
this.background = new ColorDrawable(context.getResources().getColor(R.color.deleteItem));
}
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dX, float dY,
int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX,
dY, actionState, isCurrentlyActive);
View itemView = viewHolder.itemView;
int iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2;
int iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2;
int iconBottom = iconTop + icon.getIntrinsicHeight();
if (dX > 0) {
background = new ColorDrawable(context.getResources().getColor(R.color.deleteItem));
iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2;
iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2;
iconBottom = iconTop + icon.getIntrinsicHeight();
int iconRight = itemView.getLeft() + iconMargin + icon.getIntrinsicWidth();
int iconLeft = itemView.getLeft() + iconMargin;
icon.setBounds(iconLeft, iconTop, iconRight, iconBottom);
background.setBounds(itemView.getLeft(), itemView.getTop(),
itemView.getLeft() + ((int) dX),
itemView.getBottom());
} else if (dX < 0) {
int iconRight = itemView.getRight() - iconMargin;
int iconLeft = itemView.getRight() - iconMargin - icon.getIntrinsicWidth();
icon.setBounds(iconLeft, iconTop, iconRight, iconBottom);
background.setBounds(itemView.getRight(), itemView.getTop(),
itemView.getRight() + ((int) dX),
itemView.getBottom());
} else {
background.setBounds(0, 0, 0, 0);
icon.setBounds(0, 0, 0, 0);
}
background.draw(c);
icon.draw(c);
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder,
int direction) {
dragDropListener.deleteElementDialog(viewHolder.getAdapterPosition());
}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
int dragFlags = UP | DOWN;
int swipeFlags = START | END;
return makeMovementFlags(dragFlags, swipeFlags);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target) {
dragDropListener.onRowMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder,
int actionState) {
if (actionState != ACTION_STATE_IDLE && actionState != ACTION_STATE_SWIPE) {
dragDropListener.onRowSelected(viewHolder);
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, Objects.requireNonNull(viewHolder));
dragDropListener.onRowClear(Objects.requireNonNull(viewHolder));
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
}
and this is my ItemTouchHelperListener:
public void setItemTouchHelperListener() {
ItemTouchHelperListener itemTouchHelperListener = new
ItemTouchHelperListener() {
@Override
public void onRowMoved(int fromPosition, int toPosition) {
presenter.rowMoved(fromPosition, toPosition);
}
@Override
public void onRowSelected(RecyclerView.ViewHolder myViewHolder) {
if (myViewHolder instanceof ElementsAdapter.ElementsViewHolder) {
elementsAdapter.rowSelected((ElementsAdapter.ElementsViewHolder) myViewHolder);
presenter.rowSelected(myViewHolder.getAdapterPosition());
}
}
@Override
public void onRowClear(RecyclerView.ViewHolder myViewHolder) {
if (myViewHolder instanceof ElementsAdapter.ElementsViewHolder) {
elementsAdapter.rowClear((ElementsAdapter.ElementsViewHolder) myViewHolder);
presenter.rowClear(myViewHolder.getAdapterPosition());
}
}
@Override
public void deleteElementDialog(int adapterPosition) {
createDeleteDialog(adapterPosition);
}
};
You can achieve this by registering both the dragged item using onMove() method, and the dropped item using clearView() method; then modify the data source of your RecyclerView
adapter; so you can use a temp item that stores the dragged item; then set the dropped-by item with the dragged one; and finally put the temp item on the dropped one.
Then utilize RecyclerView
adapter notifyItemChanged()
for both items to update the layout with this change
Note: here I disabled the swiping as your question mainly on the drag & drop
final int[] oldPos = new int[1];
final int[] newPos = new int[1];
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP |
ItemTouchHelper.DOWN |
ItemTouchHelper.LEFT |
ItemTouchHelper.RIGHT,
0) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
oldPos[0] = viewHolder.getAdapterPosition();
newPos[0] = target.getAdapterPosition();
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
moveItem(oldPos[0], newPos[0]);
}
});
private void moveItem(int oldPos, int newPos) {
Item temp = mItems.get(oldPos);
mItems.set(oldPos, mItems.get(newPos));
mItems.set(newPos, temp);
mAdapter.notifyItemChanged(oldPos);
mAdapter.notifyItemChanged(newPos);
}
The result
I totally agree with @Zain answer but there is one problem i.e suppose you want to replace the 1st item with 3rd then you will click and hold the 3rd item and drag it over the 1st item. Once you dropped, you will notice the 3rd item will get again shifted to its original position and after that, both items will get updated properly. It doesn't look good.
I've just slightly modified the @Zain answer.
private int fromPos = -1;
private int toPos = -1;
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new
ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP | ItemTouchHelper.DOWN |
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT, 0) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target) {
toPos = target.getAdapterPosition();
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder,
int direction) {}
@Override
public void onSelectedChange(@NonNull RecyclerView.ViewHolder
viewHolder, int actionState) {
switch(actionState){
case ItemTouchHelper.ACTION_STATE_DRAG:{
fromPos = viewHolder.getAdapterPosition();
break;
}
case ItemTouchHelper.ACTION_STATE_IDLE: {
//Execute when the user dropped the item after dragging.
if(fromPos != -1 && toPos != -1
&& fromPos != toPos) {
moveItem(fromPos, toPos);
fromPos = -1;
toPos = -1;
}
break;
}
}
private void moveItem(int oldPos, int newPos) {
Item temp = mItems.get(oldPos);
mItems.set(oldPos, mItems.get(newPos));
mItems.set(newPos, temp);
mAdapter.notifyItemChanged(oldPos);
mAdapter.notifyItemChanged(newPos);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With