android - how to catch Drop action of ItemTouchHelper which is used with RecyclerView

Viewed 23659

I have a problem with ItemTouchHelper of RecyclerView.

I am making a game. The game board is actually a RecyclerView. RecyclerView has GridLayoutManager with some span count. I want to implement drag & drop recyclerview's items. Any item can dragging over all directions (up, down, left, right).

private void initializeLayout() {
    recyclerView.setHasFixedSize(true);
    recyclerView.setLayoutFrozen(true);
    recyclerView.setNestedScrollingEnabled(false);

    // set layout manager
    GridLayoutManager layoutManager = new GridLayoutManager(getContext(), BOARD_SIZE,
        LinearLayoutManager.VERTICAL, true);
    recyclerView.setLayoutManager(layoutManager);

    // Extend the Callback class
    ItemTouchHelper.Callback itemTouchCallback = new ItemTouchHelper.Callback() {

    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        Log.w(TAG, "onMove");
        return false;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        // Application does not include swipe feature.
    }

    @Override
    public void onMoved(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
                        int fromPos, RecyclerView.ViewHolder target, int toPos, int x, int y) {
        Log.d(TAG, "onMoved");
        // this is calling every time, but I need only when user dropped item, not after every onMove function.
    }

    @Override
    public boolean isItemViewSwipeEnabled() {
        return false;
    }

    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.START | ItemTouchHelper.END;
        int swipeFlags = 0;
        return makeMovementFlags(dragFlags, swipeFlags);
    }
    };

    ItemTouchHelper touchHelper = new ItemTouchHelper(itemTouchCallback);
    touchHelper.attachToRecyclerView(recyclerView);
}

SO, why ItemTouchHelper's onMoved function works when I still dragging item on the RecyclerView ? How can I achieve this ?

4 Answers

I did some tests and onSelectedChanged(RecyclerView.ViewHolder?, Int) seemed most reliable for me to detect end of the gesture (drop). The method is called whenever an item is being dragged and passed action state of ACTION_STATE_DRAG. When the drag is over, it is called with action state of ACTION_STATE_IDLE.

See my solution below. The onItemDrag(Int, Int) callback is used for reordering items in an adapter as the item is being dragged. On the other hand the onItemDragged(Int, Int) callback is intended for updating positions in a database at the end of the gesture.

class ItemGestureHelper(private val listener: OnItemGestureListener) : ItemTouchHelper.Callback() {

    interface OnItemGestureListener {

        fun onItemDrag(fromPosition: Int, toPosition: Int): Boolean

        fun onItemDragged(fromPosition: Int, toPosition: Int)

        fun onItemSwiped(position: Int)
    }

    private var dragFromPosition = -1
    private var dragToPosition = -1

    // Other methods omitted...

    override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
        // Item is being dragged, keep the current target position
        dragToPosition = target.adapterPosition
        return listener.onItemDrag(viewHolder.adapterPosition, target.adapterPosition)
    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        listener.onItemSwiped(viewHolder.adapterPosition)
    }

    override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
        super.onSelectedChanged(viewHolder, actionState)

        when (actionState) {
            ItemTouchHelper.ACTION_STATE_DRAG -> {
                viewHolder?.also { dragFromPosition = it.adapterPosition }
            }
            ItemTouchHelper.ACTION_STATE_IDLE -> {
                if (dragFromPosition != -1 && dragToPosition != -1 && dragFromPosition != dragToPosition) {
                    // Item successfully dragged
                    listener.onItemDragged(dragFromPosition, dragToPosition)
                    // Reset drag positions
                    dragFromPosition = -1
                    dragToPosition = -1
                }
            }
        }
    }

}
Related