Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
        }
    };
like image 730
Kratos Avatar asked Mar 04 '23 08:03

Kratos


2 Answers

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

like image 175
Zain Avatar answered Mar 16 '23 00:03

Zain


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);
}
like image 40
Ankit Chandora Avatar answered Mar 16 '23 00:03

Ankit Chandora