RecyclerView实现条目Item拖拽排序与滑动删除

来源:互联网 发布:ndcg java 编辑:程序博客网 时间:2024/05/14 15:40

转载地址:http://blog.csdn.net/yanzhenjie1003/article/details/51935982


RecyclerView实现条目Item拖拽排序与滑动删除

版权声明:转载请注明本文转自严振杰的博客: http://blog.csdn.net/yanzhenjie1003


欢迎使用我的另一个更强大的库:RecyclerView侧滑菜单,长按拖拽,滑动删除,下拉刷新上拉加载。

效果演示

效果演示

直播视频讲解:[http://pan.baidu.com/s/1miEOtwG1 
推荐大家结合我直播的视频看效果更好。

本博客源码传送门。

RecyclerView侧滑菜单 
RecyclerView滑动删除 
RecyclerView长按拖拽 
RecyclerView下拉刷新上拉加载,请看下面这篇博客: 
http://blog.csdn.net/yanzhenjie1003/article/details/52115566

需求和技术分析

  1. RecyclerView Item拖拽排序::长按RecyclerView的Item或者触摸Item的某个按钮。
  2. RecyclerView Item滑动删除:RecyclerView Item滑动删除:RecyclerView的Item滑动删除。

实现方案与技术

利用ItemTouchHelper绑定RecyclerViewItemTouchHelper.Callback来实现UI更新,并且实现动态控制是否开启拖拽功能和滑动删除功能。

实现步骤

  1. 继承抽象类ItemTouchHelper,并在构造方法传入实现的ItemTouchHelper.Callback
  2. recyclerView绑定ItemTouchHelper:itemTouchHelper.attachToRecyclerView(recyclerView)
  3. 自定义ItemTouchHelper.Callback的实现接口OnItemTouchCallbackListener,由外部更新RecyclerView的Item。

几个主要的布局

activity_main.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <android.support.v7.widget.RecyclerView        android:id="@+id/rv_main"        android:layout_width="match_parent"        android:layout_height="wrap_content" /></LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这个没啥好说的了吧,就是一个RecyclerView啦。

接下来是RecyclerView的Item的布局item.xml:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="?android:listPreferredItemHeight"    android:background="?selectableItemBackground">    <ImageView        android:id="@+id/iv_touch"        style="@style/ItemStyle"        android:layout_height="match_parent"        android:layout_alignParentEnd="true"        android:layout_alignParentRight="true"        android:src="@android:drawable/alert_dark_frame" />    <CheckBox        android:id="@+id/cb_item_check"        style="@style/ItemStyle"        android:layout_alignParentLeft="true"        android:layout_alignParentStart="true" />    <TextView        android:id="@+id/tv_name"        style="@style/ItemStyle"        android:layout_toEndOf="@id/cb_item_check"        android:layout_toRightOf="@id/cb_item_check" />    <TextView        android:id="@+id/tv_sex"        style="@style/ItemStyle"        android:layout_marginLeft="@dimen/dp_10"        android:layout_marginStart="@dimen/dp_10"        android:layout_toEndOf="@id/tv_name"        android:layout_toRightOf="@id/tv_name" /></RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

这个也不用解释了,到时候下载看源码,就是普通item,展示数据而已。

实现自己的DefaultItemTouchHelper:继承ItemTouchHelper

public class DefaultItemTouchHelper extends ItemTouchHelper {    public DefaultItemTouchHelper(ItemTouchHelp.Callback callback) {        super(callback);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

好嘛,这个太简单了,基本上一行代码都不用写。但是这里需要一个ItemTouchHelp.Callback啊,所以我们还是要实现一个ItemTouchHelp.Callback,客观且看下文分解。

实现自己的ItemTouchHelper.Callback:继承ItemTouchHelper.Callback

这里是全文最重要的部分啦,要认真点看噢,先上代码,后解释,其他看注释和视频。

public class DefaultItemTouchHelpCallback extends ItemTouchHelper.Callback {    /**     * Item操作的回调     */    private OnItemTouchCallbackListener onItemTouchCallbackListener;    /**     * 是否可以拖拽     */    private boolean isCanDrag = false;    /**     * 是否可以被滑动     */    private boolean isCanSwipe = false;    public DefaultItemTouchHelpCallback(OnItemTouchCallbackListener onItemTouchCallbackListener) {        this.onItemTouchCallbackListener = onItemTouchCallbackListener;    }    /**     * 设置Item操作的回调,去更新UI和数据源     *     * @param onItemTouchCallbackListener     */    public void setOnItemTouchCallbackListener(OnItemTouchCallbackListener onItemTouchCallbackListener) {        this.onItemTouchCallbackListener = onItemTouchCallbackListener;    }    /**     * 设置是否可以被拖拽     *     * @param canDrag 是true,否false     */    public void setDragEnable(boolean canDrag) {        isCanDrag = canDrag;    }    /**     * 设置是否可以被滑动     *     * @param canSwipe 是true,否false     */    public void setSwipeEnable(boolean canSwipe) {        isCanSwipe = canSwipe;    }    /**     * 当Item被长按的时候是否可以被拖拽     *     * @return     */    @Override    public boolean isLongPressDragEnabled() {        return isCanDrag;    }    /**     * Item是否可以被滑动(H:左右滑动,V:上下滑动)     *     * @return     */    @Override    public boolean isItemViewSwipeEnabled() {        return isCanSwipe;    }    /**     * 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向     *     * @param recyclerView     * @param viewHolder     * @return     */    @Override    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();        if (layoutManager instanceof GridLayoutManager) {// GridLayoutManager            // flag如果值是0,相当于这个功能被关闭            int dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN;            int swipeFlag = 0;            // create make            return makeMovementFlags(dragFlag, swipeFlag);        } else if (layoutManager instanceof LinearLayoutManager) {// linearLayoutManager            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;            int orientation = linearLayoutManager.getOrientation();            int dragFlag = 0;            int swipeFlag = 0;            // 为了方便理解,相当于分为横着的ListView和竖着的ListView            if (orientation == LinearLayoutManager.HORIZONTAL) {// 如果是横向的布局                swipeFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;                dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;            } else if (orientation == LinearLayoutManager.VERTICAL) {// 如果是竖向的布局,相当于ListView                dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;                swipeFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;            }            return makeMovementFlags(dragFlag, swipeFlag);        }        return 0;    }    /**     * 当Item被拖拽的时候被回调     *     * @param recyclerView     recyclerView     * @param srcViewHolder    拖拽的ViewHolder     * @param targetViewHolder 目的地的viewHolder     * @return     */    @Override    public boolean onMove(RecyclerView recyclerView, ViewHolder srcViewHolder, ViewHolder targetViewHolder) {        if (onItemTouchCallbackListener != null) {            return onItemTouchCallbackListener.onMove(srcViewHolder.getAdapterPosition(), targetViewHolder.getAdapterPosition());        }        return false;    }    @Override    public void onSwiped(ViewHolder viewHolder, int direction) {        if (onItemTouchCallbackListener != null) {            onItemTouchCallbackListener.onSwiped(viewHolder.getAdapterPosition());        }    }    public interface OnItemTouchCallbackListener {        /**         * 当某个Item被滑动删除的时候         *         * @param adapterPosition item的position         */        void onSwiped(int adapterPosition);        /**         * 当两个Item位置互换的时候被回调         *         * @param srcPosition    拖拽的item的position         * @param targetPosition 目的地的Item的position         * @return 开发者处理了操作应该返回true,开发者没有处理就返回false         */        boolean onMove(int srcPosition, int targetPosition);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144

好,其实上面最重要的就是五个方法:

/** * 是否可以长按拖拽排序。 */@Overridepublic boolean isLongPressDragEnabled() {}/** * Item是否可以被滑动(H:左右滑动,V:上下滑动) */@Overridepublic boolean isItemViewSwipeEnabled() {}/** * 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向 */@Overridepublic int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {}/** * 当Item被拖拽的时候被回调 */@Overridepublic boolean onMove(RecyclerView r, ViewHolder rholer, ViewHolder tholder) {}/** * 当View被滑动删除的时候 */@Overridepublic void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

isItemViewSwipeEnabled()返回值是否可以拖拽排序,true可以,false不可以,isItemViewSwipeEnabled()是否可以滑动删除,true可以,false不可以;这两个方法都是配置是否可以操作的。我们上面的代码中返回了一个成员变量值,并且这个值通过外部可以修改,所以提供了外部控制的方法。

onMove()当Item被拖拽排序移动到另一个Item的位置的时候被回调,onSwiped()当Item被滑动删除到不见;这两个方法是当用户操作了,来回调我们,我们就该去更新UI了。这里我们提供了一个Listener去通知外部,并且返回出去了必要的值,来降低代码耦合度。

getMovementFlags()说明一:是当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向,那我们又知道支持拖拽和滑动删除的无非就是LinearLayoutManagerGridLayoutManager了,相当于我们老早的时候用的ListViewGridView了。所以我们根据布局管理器的不同做了响应的区分。

getMovementFlags()说明二:其他都好理解,就是这里的return makeMovementFlags(dragFlag, swipeFlag);这句话是最终的返回值,也就是它决定了我们的拖拽或者滑动的方法。第一个参数是拖拽flag,第二个是滑动的flag。

重新定义DefaultItemTouchHelper

我们记得上面定义了一个DefaultItemTouchHelper,它的构造中需要传一个ItemTouchHelper.Callback,既然我们实现礼了,我们再把DefaultItemTouchHelper做个封装,使使用者更傻瓜式的调用。

public class DefaultItemTouchHelper extends YolandaItemTouchHelper {    private DefaultItemTouchHelpCallback itemTouchHelpCallback;    public DefaultItemTouchHelper(DefaultItemTouchHelpCallback.OnItemTouchCallbackListener onItemTouchCallbackListener) {        super(new DefaultItemTouchHelpCallback(onItemTouchCallbackListener));        itemTouchHelpCallback = (DefaultItemTouchHelpCallback) getCallback();    }    /**     * 设置是否可以被拖拽     *     * @param canDrag 是true,否false     */    public void setDragEnable(boolean canDrag) {        itemTouchHelpCallback.setDragEnable(canDrag);    }    /**     * 设置是否可以被滑动     *     * @param canSwipe 是true,否false     */    public void setSwipeEnable(boolean canSwipe) {        itemTouchHelpCallback.setSwipeEnable(canSwipe);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

现在我们看到已经不需要传ItemTouchHelper.CallbackItemTouchHelper了,只需要传我们在DefaultItemTouchHelpCallback中定义好的OnItemTouchCallbackListener就好了,而且提供了设置是否可以滑动和是否可以拖拽的方法,而OnItemTouchCallbackListener只是通知外部滑动了、删除了,你去更新UI吧。

这里可以有的同学会有疑问,上面原来不是继承ItemTouchHelper吗?这里咋就变成了YolandaItemTouchHelper了呢?因为我们看到这里多了一句itemTouchHelpCallback = getCallback();,这个getCallback();这个方法是没有的,是我们在YolandaItemTouchHelper中自定义的,因为我们想在DefaultItemTouchHelper中提供外部设置是否可以拖拽和滑动删除的方法,就得拿到这个Callback,所以我看了下源码,我们在ItemTouchHelper构造中把Callback穿进去,它保存的时候一个package级别的成员变量,所以我在Android.support.v7.widget.helper包下新建了一个YolandaItemTouchHelper类:

public class YolandaItemTouchHelper extends ItemTouchHelper {    public YolandaItemTouchHelper(Callback callback) {        super(callback);    }    public Callback getCallback() {        return mCallback;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如何投入使用

好扯淡也扯完了,封装也封装完了,那么接下来就来在Activity中使用下咯:

recyclerView绑定ItemTouchHelper

没啥好说的用itemTouchHelper.attachToRecyclerView(recyclerView)绑定recyclerViewItemTouchHelper,并且只是允许拖拽和滑动删除Item:

DefaultItemTouchHelper itemTouchHelper = new DefaultItemTouchHelper(onItemTouchCallbackListener);itemTouchHelper.attachToRecyclerView(recyclerView);itemTouchHelper.setDragEnable(true);itemTouchHelper.setSwipeEnable(true);
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

看到上面还缺少一个onItemTouchCallbackListener吧,这个也比较重要。

使用Callback自定义的OnItemTouchCallbackListener刷新UI

我们在自定义Callback的时候不是在onMove()onSwiped()方法中回调OnItemTouchCallbackListener去更新UI吗?这里就是OnItemTouchCallbackListener如何更新UI的操作了,完成这个操作,那么我们的目的就达到了:

private DefaultItemTouchHelpCallback.OnItemTouchCallbackListener onItemTouchCallbackListener = new DefaultItemTouchHelpCallback.OnItemTouchCallbackListener() {    @Override    public void onSwiped(int adapterPosition) {        // 滑动删除的时候,从数据源移除,并刷新这个Item。        if (userInfoList != null) {            userInfoList.remove(adapterPosition);            mainAdapter.notifyItemRemoved(adapterPosition);        }    }    @Override    public boolean onMove(int srcPosition, int targetPosition) {        if (userInfoList != null) {            // 更换数据源中的数据Item的位置            Collections.swap(userInfoList, srcPosition, targetPosition);            // 更新UI中的Item的位置,主要是给用户看到交互效果            mainAdapter.notifyItemMoved(srcPosition, targetPosition);            return true;        }        return false;    }};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

到这里就结束了,不信你去试试,源码传送门。


阅读全文
0 0