RecyclerView的拖动、滑动删除


前言

前些时候提到了下RecyclerView的点击事件的处理,今天再来说说RecyclerView的拖动、滑动删除。看完了以后你会发现RecyclerView是多么的犀利。比ListView简单的多了。

原理

主要是使用ItemTouchHelper.Callback类,在其中我们要用到的方法主要有

  • getMovementFlags()
  • onMove()
  • onSwiped()
  • isLongPressDragEnabled()
  • onSelectedChanged()
  • clearView()

借助该ItemTouchHelper.Callback与其中的方法就能很好的处理实现拖动、删除。
下面详细介绍这些方法的用处与功能实现。

拖动

getMovementFlags

既然要使用ItemTouchHelper.Callback中的方法,所以我们首先自定义自己的类来继承它,重写其中的相关方法。首先是getMovementFlags()方法,该方法主要作用是定义移动标识,所以针对拖动效果,我们可以首先定义拖动标识,通过ItemTouchHelper提供的参数来定义:

@Override
        public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            dragFlags = 0;
            swipeFlags = 0;
            if (recyclerView.getLayoutManager() instanceof GridLayoutManager
                    || recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {
                dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN
                        | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
            } else {
                dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
                //if (viewHolder.getAdapterPosition() != 0)
                  //  swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
            }
            return makeMovementFlags(dragFlags, swipeFlags);
        }

这里可以根据你的RecyclerViewLayoutManager情况来定义拖动方向。LinearLayoutManager可以定义上下拖动dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;至于注释代码swipeFlags这里先不要管,这是下面的删除标识。最后再调用makeMovementFlags返回。

onMove

在定义完MovementFlags以后,在我们拖动的时候回调用onMove方法,所以我们可以重写该方法,进行对数据的改变操作。

@Override
        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
            int fromPosition = viewHolder.getAdapterPosition();
            int toPosition = target.getAdapterPosition();
            if (toPosition != 0) {
                if (fromPosition < toPosition)
                    //向下拖动
                    for (int i = fromPosition; i < toPosition; i++) {
                        Collections.swap(mListData, i, i + 1);
                    }
                else {
                    //向上拖动
                    for (int i = fromPosition; i > toPosition; i--) {
                        Collections.swap(mListData, i, i - 1);
                    }
                }
                recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);
            }
            return true;
        }

通过提供的参数获取fromPosition初始位置toPosition目标位置,根据情况做相应的数据改变,最后调用recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);来更新adapter状态。

绑定

最后用下面代码来绑定目标RecyclerView对象。

final ItemTouchHelper helper = new ItemTouchHelper(new MyCallBack());
        helper.attachToRecyclerView(recyclerView);

(new MyCallBack()就是前面我自定义的类。这样基本的拖动就实现了。但是你会发现就这样的话你在拖动的时候回没有背景效果的所以我们再来进行优化一下

onSelectedChanged

通过重写该方法我们可以定义自己的拖动背景

@Override
        public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
            if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
                viewHolder.itemView.setPressed(true);
            }
            super.onSelectedChanged(viewHolder, actionState);
        }

当然前提是你的RecyclerViewItem已经添加了android:background="@drawable/item_bg属性。例如item_bg

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <solid android:color="@android:color/darker_gray"/>
        </shape>
    </item>
    <item android:state_pressed="false">
        <shape android:shape="rectangle">
            <solid android:color="@android:color/white"/>
        </shape>
    </item>
</selector>

clearView

最后再重写clearViwe()拖动完成之后会调用,用来清除背景状态。

@Override
        public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            super.clearView(recyclerView, viewHolder);
            viewHolder.itemView.setPressed(false);
        }

这样基本上就完成了日常我们所要的拖动效果。至于对个别的Item屏蔽拖动效果可以重写isLongPressDragEnabled()方法

isLongPressDragEnabled

该方法默认返回true就是不屏蔽拖动效果,所有的Item都能被拖动。如果要实现屏蔽的话,这里我们只要返回false就可以了。

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

再在我们前面文章中定义的Item点击事件的处理中的onItemLongPressClick()方法中调用helper.startDrag(vh)方法实现要拖动的Item,通过传入要拖动的ViewHolder来标识。

@Override
            public void onItemLongPressClick(RecyclerView.ViewHolder vh) {
                if (vh.getAdapterPosition() != 0) {
                    helper.startDrag(vh);
                }
            }

这里我固定了第一个位置是不能被拖动的。

如果对于RecyclerView的Item点击事件还不了解的可以阅读我前面的文章RecyclerView item点击你真的会么

滑动删除

到这里相信滑动删除应该有点眉目了吧。对,就是前面getMovementFlags()方法中所定义的swipeFlags标识,ItemTouchHelper为我们提供了左右删除标识ItemTouchHelper.STARTItemTouchHelper.END,这样我们就可以先打开前面注释的代码。

onSwiped

最后可以在onSwiped()中对数据进行删除操作

@Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
            int positon = viewHolder.getAdapterPosition();
            recyclerView.getAdapter().notifyItemRemoved(positon);
            mListData.remove(positon);
        }

这样基本的滑动删除就可以实现了。至于对个别的Item屏蔽删除效果可以重写isItemViewSwipeEnabled()方法

isItemViewSwipeEnabled

该方法与前面的拖动的屏蔽isItemViewSwipeEnabled()方法使用方式是一样的,它也是默认返回true,不对Item进行屏蔽滑动删除。如果要屏蔽的话也是重写方法再返回false,最后在通过对应的startSwipe(ViewHolder)方法实现滑动删除,具体的实现可以参考前面的屏蔽拖动。

总结

这里主要是要弄明白上面所提到的6个方法的作用与调用时机。主要就是getMovementFlags()方法,定义好拖动与滑动标识。拖动时会回调onMove()方法,滑动删除时要处理数据的删除,所以会回调onSwiped()方法,至于其它的状态效果方法可以视需求来灵活变化。

Demo地址:https://github.com/idisfkj/Re...

欢迎访问个人blog:https://idisfkj.github.io/arc...

关注

RecyclerView的拖动、滑动删除