ItemTouchHelper.SimpleCallback onMove() is not triggered

Viewed 3186

I want to realize drag and drop in my RecyclerView. As I found out, the best way to implement this is by attaching an ItemTouchHelper to the RecyclerView.

onSwiped() works fine, but onMove() is never triggered.

This is what I do:

EditIngredientsRecyclerViewAdapter premixableIngredientsAdapter = new EditIngredientsRecyclerViewAdapter(this, typeOfComponents);
        GridLayoutManager gridLayoutManager1 = new GridLayoutManager(getContext(), 1);
        recyclerView.setAdapter(premixableIngredientsAdapter);
        recyclerView.setLayoutManager(gridLayoutManager1);

        ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
            @Override
            public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
                Log.d(TAG, "onMove: ");
                return false;
            }

            @Override
            public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
                Log.d(TAG, "onSwiped: ");
            }
        };

        ItemTouchHelper touchHelper = new ItemTouchHelper(simpleItemTouchCallback);
        touchHelper.attachToRecyclerView(recyclerView);
4 Answers

I forgot two things:

**1. update the SimpleCallback to this:

ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
            @Override
            public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
                // Notify Adapter of the moved item!
                recyclerView.getAdapter().notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
                return true;
            }

            @Override
            public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
                // No swipe action
            }

            @Override
            public boolean isItemViewSwipeEnabled() {
                // Disable swipe (dont override this method or return true, if you want to have swipe)
                return false;
            }

            @Override
            public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
                // Set movement flags to specify the movement direction
                // final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT;  <-- for all directions
                // In this case only up and down is allowed
                final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                final int swipeFlags = 0;
                return makeMovementFlags(dragFlags, swipeFlags);
            }
        };

Especially important is to overwrite getMovementFlags() to specify the direction for dragging and notify the adapter of the moved Item, as I do in onMove()

2. Trigger dragging by itemTouchHelper.startDrag(viewHolderElement):

I did this by creating a small interface in the RecyclerViewAdapter:

public interface OnStartDragListener {
        void onStartDrag(RecyclerView.ViewHolder holder);
    }

When creating the Adapter pass in a new OnStartDragListenerElement like so:

EditIngredientsRecyclerViewAdapter premixableIngredientsAdapter = new EditIngredientsRecyclerViewAdapter(this, typeOfComponents, new EditIngredientsRecyclerViewAdapter.OnStartDragListener() {
            @Override
            public void onStartDrag(RecyclerView.ViewHolder holder) {
                touchHelper.startDrag(holder);
                Log.d(TAG, "onStartDrag: ");
            }
        });

and calling onStartDrag(viewHolder) in an Touchlistener put on a button in the view like so:

holder.itemBinding.dragBtn.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    mOnStartDragListener.onStartDrag(holder);
                    return false;
                }
            });

I am sure, there are more efficient implementations and once somebody posts one here, I will accept it. However, this at least made it working for me.

I've listed code for both ItemTouchHelper.SimpleCallback and ItemTouchHelper.Callback(). Comments are listed in the code to help break down whats happening.

ItemTouchHelper.SimpleCallback In the Activity/Fragment under the RecyclerView implementation Create a ItemTouchHelper.SimpleCallback object. You'll need to override onMove() and onSwiped() methods.

ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
    ItemTouchHelper.UP or ItemTouchHelper.DOWN,
    ItemTouchHelper.RIGHT or ItemTouchHelper.LEFT
) {


    // You'll need to Override
    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: ViewHolder,
        target: ViewHolder
    ): Boolean {
        // Get the position start position of the item
        val startPosition = viewHolder.adapterPosition
        // Get the endPosition, where you draged it to. 
        val endPosition = target.adapterPosition

        // Submit start and end position to notifyItemMoved. 
        recyclerView.adapter?.notifyItemMoved(startPosition, endPosition)

        // true if moved, false otherwise
        return true
    }

    override fun onSwiped ...

If you want to create a CustomItemTouchCallback

Create a customItemTouchHelper class that extends ItemTouchHelper.Callback(), you'll need to override getMovementFlags(), onMove(), and onSwiped()


class CustomItemTouchHelper(mContext: Context, val viewModel: MyViewModel) : ItemTouchHelper.Callback() {

    override fun getMovementFlags(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder
    ): Int {
        val  dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
        val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
        return makeMovementFlags(dragFlags, swipeFlags)
    }

    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean {
        val startPosition = viewHolder.adapterPosition
        val endPosition = target.adapterPosition
        
        recyclerView.adapter?.notifyItemMoved(fromPosition, toPosition)
        // true if moved, false otherwise 
        return true
    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        val editList: MutableList<User>? = viewModel.usersList?.value as MutableList<User>?
        val editListCopy = editList?.toMutableList()
        editListCopy?.removeAt(viewHolder.adapterPosition)
        viewModel.usersList?.postValue(editListCopy)
    }

    override fun isItemViewSwipeEnabled(): Boolean {
        // Allows items to be swiped left or right.
        return true
    }

    override fun isLongPressDragEnabled(): Boolean {
        // Allows for long click so items can be dragged, moved up or down in the list. 
        return true
    }
}

Now in your Activity or Fragment, you'll need to create a CustomItemHelper object and then attach the RecyclerView to it.

val rv = findViewById<RecyclerView>(R.id.recycler_view)
    rv.apply {
        ...
        val customItemTouchHelper = ItemTouchHelper(CustomItemTouchHelper(context, viewModel))
        customItemTouchHelper.attachToRecyclerView(rv)

        }

I'm sorry, I don't have enough reputation to comment under the question. Try changing return false to return true

Your ItemTouchHelper.SimpleCallback should set the move flags in the constructor and the onMove method should return true.

int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.LEFT;
ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(moveFlags, swipeFlags) {
            @Override
            public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
                Log.d(TAG, "onMove: ");
                int fromPosition = viewHolder.getAdapterPosition();
                int toPosition = target.getAdapterPosition();
                // Swap list items in adapter...
                return true;
            }

            @Override
            public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
                Log.d(TAG, "onSwiped: ");
            }
        }
Related