4

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);
        }
    };
Zain
  • 37,492
  • 7
  • 60
  • 84
Kratos
  • 681
  • 1
  • 13
  • 30
  • It would be helpful to include an embedded video demonstration or link showing the behavior you want. For example, it's hard to imagine what you mean by "where it is dropped" ... since normally the item being dragged would be suspended in between *two* elements. – Nerdy Bunz Sep 29 '19 at 22:55

2 Answers2

4

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

Zain
  • 37,492
  • 7
  • 60
  • 84
  • Zain i think you didn't understand my question. My code is right now working just like result of code you suggest. But I dont want to do that. i want to change positions only of dragged and dropped item. not change positions of all elements between them – Kratos Sep 30 '19 at 08:50
  • @Haris please check my updated code .. it works with me now – Zain Sep 30 '19 at 09:38
4

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);
}
Ankit Chandora
  • 606
  • 6
  • 8