您的位置:首页 > 其它

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

2017-05-23 20:04 441 查看
转载地址: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


需求和技术分析

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


实现方案与技术

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


实现步骤

继承抽象类
ItemTouchHelper
,并在构造方法传入实现的
ItemTouchHelper.Callback

recyclerView绑定ItemTouchHelper:
itemTouchHelper.attachToRecyclerView(recyclerView)

自定义
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

好,其实上面最重要的就是五个方法:
/**
* 是否可以长按拖拽排序。
*/
@Override
public boolean isLongPressDragEnabled() {}
/**
* Item是否可以被滑动(H:左右滑动,V:上下滑动)
*/
@Override
public boolean isItemViewSwipeEnabled() {}
/**
* 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {}
/**
* 当Item被拖拽的时候被回调
*/
@Override
public boolean onMove(RecyclerView r, ViewHolder rholer, ViewHolder tholder) {}

/**
* 当View被滑动删除的时候
*/
@Override
public 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的时候需要我们告诉系统滑动或者拖拽的方向,那我们又知道支持拖拽和滑动删除的无非就是
LinearLayoutManager
GridLayoutManager
了,相当于我们老早的时候用的
ListView
GridView
了。所以我们根据布局管理器的不同做了响应的区分。

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.Callback
ItemTouchHelper
了,只需要传我们在
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)
绑定
recyclerView
ItemTouchHelper
,并且只是允许拖拽和滑动删除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

到这里就结束了,不信你去试试,源码传送门
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: