Search code examples
javaandroidandroid-recyclerviewdrag-and-dropitemtouchhelper

how to create RecyclerView drag and drop (swap 2 item positions version)


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);
        }
    };

Solution

  • 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