前言
从Android 5.0开始,谷歌推出了新的控件RecyclerView,相对于早它之前的ListView,优点多多,功能强大,也给我们的开发着提供了极大的便利,今天自己学习一下RecyclerView轻松实现滑动删除及拖拽的效果,如下图。
相信研究过RecyclerView的同学,应该很清楚该怎么实现这样的效果,若是用ListView,这样的效果实现起来可能就有点麻烦,但是在强大的RecyclerView面前这样的的效果只需很少的代码,因为谷歌给我们提供了强大的工具类ItemTouchHelper,它已经处理了关于RecyclerView拖动和滑动的实现,并且我们可以在其中实现我们自己的动画,以及定制我们想要的效果。
ItemTouchHelper.Callback有几个重要的抽象方法,我们继承该抽象类,并重写抽象方法。它是我们实现滑动和拖拽重要的回调。
1
|
int
getMovementFlags
(
RecyclerView
recyclerView
,
RecyclerView
.
ViewHolder
viewHolder
)
|
该方法返回一个整数,用来指定拖拽和滑动在哪个方向是被允许的。在其中使用makeMovementFlags(int dragFlags, int swipeFlags)返回,该方法第一个参数用来指定拖动,第二个参数用来指定滑动。对于方向参数有6种
1
2
3
4
5
6
|
ItemTouchHelper
.
UP
//滑动拖拽向上方向
ItemTouchHelper
.
DOWN
//向下
ItemTouchHelper
.
LEFT
//向左
ItemTouchHelper
.
RIGHT
//向右
ItemTouchHelper
.
START
//依赖布局方向的水平开始方向
ItemTouchHelper
.
END
//依赖布局方向的水平结束方向
|
1
|
boolean
onMove
(
RecyclerView
recyclerView
,
RecyclerView
.
ViewHolder
viewHolder
,
RecyclerView
.
ViewHolder
target
)
|
onMove方法是拖拽的回调,参数viewHolder是拖动的Item,target是拖动的目标位置的Item,该方法如果返回true表示切换了位置,反之返回false。
1
|
void
onSwiped
(
RecyclerView
.
ViewHolder
viewHolder
,
int
direction
)
|
onSwiped方法为Item滑动回调,viewHolder为滑动的item,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
27
28
29
30
31
32
33
34
|
/**
* Item是否支持长按拖动
*
* @return
* true 支持长按操作
* false 不支持长按操作
*/
boolean
isLongPressDragEnabled
(
)
/**
* Item是否支持滑动
*
* @return
* true 支持滑动操作
* false 不支持滑动操作
*/
boolean
isItemViewSwipeEnabled
(
)
/**
* 移动过程中绘制Item
*
* @param c
* @param recyclerView
* @param viewHolder
* @param dX
* X轴移动的距离
* @param dY
* Y轴移动的距离
* @param actionState
* 当前Item的状态
* @param isCurrentlyActive
* 如果当前被用户操作为true,反之为false
*/
onChildDraw
(
Canvas
c
,
RecyclerView
recyclerView
,
RecyclerView
.
ViewHolder
viewHolder
,
float
dX
,
float
dY
,
int
actionState
,
boolean
isCurrentlyActive
)
|
需要注意的是,如果我们想实现拖动或者滑动必须将上面是否支持拖动或者滑动的方法返回true,否则onMove或者onSwiped方法不会执行。
功能实现
1
2
3
4
5
|
adapter
=
new
CustomAdapter
(
getActivity
(
)
,
strings
)
;
recycleview
.
setAdapter
(
adapter
)
;
ItemTouchHelper
.
Callback
callback
=
new
RecycleItemTouchHelper
(
adapter
)
;
ItemTouchHelper
itemTouchHelper
=
new
ItemTouchHelper
(
callback
)
;
itemTouchHelper
.
attachToRecyclerView
(
recycleview
)
;
|
对于ItemTouchHelper 构造方法接收一个ItemTouchHelper.Callback参数,而这个Callback就是我们在在上述讲到的工具类,初始化ItemTouchHelper 后通过其attachToRecyclerView(@Nullable RecyclerView recyclerView)方法将我们实现的ItemTouchHelper.Callback和RecyclerView关联,最终达到我们的效果,代码看起来是不是很简单,接下来我们看下我们自定义的Callback。
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
|
package
com
.
example
.
xh
.
adapter
;
import
android
.
content
.
res
.
Resources
;
import
android
.
graphics
.
Bitmap
;
import
android
.
graphics
.
BitmapFactory
;
import
android
.
graphics
.
Canvas
;
import
android
.
graphics
.
Paint
;
import
android
.
graphics
.
Rect
;
import
android
.
support
.
v7
.
widget
.
RecyclerView
;
import
android
.
support
.
v7
.
widget
.
helper
.
ItemTouchHelper
;
import
android
.
util
.
Log
;
import
android
.
view
.
View
;
import
com
.
example
.
xh
.
R
;
import
com
.
example
.
xh
.
utils
.
MyApplication
;
/**
* Created by xiehui on 2017/2/23.
*/
public
class
RecycleItemTouchHelper
extends
ItemTouchHelper
.
Callback
{
private
static
final
String
TAG
=
"RecycleItemTouchHelper"
;
private
final
ItemTouchHelperCallback
helperCallback
;
public
RecycleItemTouchHelper
(
ItemTouchHelperCallback
helperCallback
)
{
this
.
helperCallback
=
helperCallback
;
}
/**
* 设置滑动类型标记
*
* @param recyclerView
* @param viewHolder
* @return
* 返回一个整数类型的标识,用于判断Item那种移动行为是允许的
*/
@
Override
public
int
getMovementFlags
(
RecyclerView
recyclerView
,
RecyclerView
.
ViewHolder
viewHolder
)
{
Log
.
e
(
TAG
,
"getMovementFlags: "
)
;
//START 右向左 END左向右 LEFT 向左 RIGHT向右 UP向上
//如果某个值传0,表示不触发该操作,次数设置支持上下拖拽,支持向右滑动
return
makeMovementFlags
(
ItemTouchHelper
.
UP
|
ItemTouchHelper
.
DOWN
,
ItemTouchHelper
.
END
)
;
}
/**
* Item是否支持长按拖动
*
* @return
* true 支持长按操作
* false 不支持长按操作
*/
@
Override
public
boolean
isLongPressDragEnabled
(
)
{
return
super
.
isLongPressDragEnabled
(
)
;
}
/**
* Item是否支持滑动
*
* @return
* true 支持滑动操作
* false 不支持滑动操作
*/
@
Override
public
boolean
isItemViewSwipeEnabled
(
)
{
return
super
.
isItemViewSwipeEnabled
(
)
;
}
/**
* 拖拽切换Item的回调
*
* @param recyclerView
* @param viewHolder
* @param target
* @return
* 如果Item切换了位置,返回true;反之,返回false
*/
@
Override
public
boolean
onMove
(
RecyclerView
recyclerView
,
RecyclerView
.
ViewHolder
viewHolder
,
RecyclerView
.
ViewHolder
target
)
{
Log
.
e
(
TAG
,
"onMove: "
)
;
helperCallback
.
onMove
(
viewHolder
.
getAdapterPosition
(
)
,
target
.
getAdapterPosition
(
)
)
;
return
true
;
}
/**
* 滑动Item
*
* @param viewHolder
* @param direction
* Item滑动的方向
*/
@
Override
public
void
onSwiped
(
RecyclerView
.
ViewHolder
viewHolder
,
int
direction
)
{
Log
.
e
(
TAG
,
"onSwiped: "
)
;
helperCallback
.
onItemDelete
(
viewHolder
.
getAdapterPosition
(
)
)
;
}
/**
* Item被选中时候回调
*
* @param viewHolder
* @param actionState
* 当前Item的状态
* ItemTouchHelper.ACTION_STATE_IDLE 闲置状态
* ItemTouchHelper.ACTION_STATE_SWIPE 滑动中状态
* ItemTouchHelper#ACTION_STATE_DRAG 拖拽中状态
*/
@
Override
public
void
onSelectedChanged
(
RecyclerView
.
ViewHolder
viewHolder
,
int
actionState
)
{
super
.
onSelectedChanged
(
viewHolder
,
actionState
)
;
}
public
interface
ItemTouchHelperCallback
{
void
onItemDelete
(
int
positon
)
;
void
onMove
(
int
fromPosition
,
int
toPosition
)
;
}
}
|
在默认情况下是支持拖动和滑动的,也就是isLongPressDragEnabled()和isItemViewSwipeEnabled()是返回true的。在该类中我们创建了一个接口ItemTouchHelperCallback并创建两个抽象方法,分别表示拖拽和滑动。在onMove方法中回调创建我们创建的接口方法接口onMove(int fromPosition,int toPosition),并将拖拽和 Item 的posion和目标posion传入,posion通过ViewHolder的getAdapterPosition()获得,然后在滑动回调方法onSwiped中回调onItemDelete(int positon)。到这里我们自定义的ItemTouchHelper.Callback创建完成。
上面完成后我们只需要在我们自定义的Adapter中实现RecycleItemTouchHelper.ItemTouchHelperCallback接口,然后在回调方法中更新界面,如下Apdater中回调方法实现。
1
2
3
4
5
6
7
8
9
10
11
|
@
Override
public
void
onItemDelete
(
int
positon
)
{
list
.
remove
(
positon
)
;
notifyItemRemoved
(
positon
)
;
}
@
Override
public
void
onMove
(
int
fromPosition
,
int
toPosition
)
{
Collections
.
swap
(
list
,
fromPosition
,
toPosition
)
;
//交换数据
notifyItemMoved
(
fromPosition
,
toPosition
)
;
}
|
我们在onItemDelete方法中删除对应posion的数据,在onMove方法中通过Collections.swap方法交换对应项数据,然后分别调用notifyItemRemoved和notifyItemMoved通过适配器更新UI.
好了到这里功能已经实现了,是不是发现代码很少,当然啰嗦的比较多而已。
功能升级
通过上面简单代码的实现,已经可以滑动删除和拖拽了,当然不满足的你可能发现滑动删除的时候没有动画没有背景,但是我想更改下背景并且在滑动的过程会出现一个删除的图标,给用户反馈,让其明白该操作是删除数据的。当然你还会想再删除的过程中增加一个动画。其实实现这个效果也并不是很麻烦,接下来新的方法实现登场。哦,不对,前面提到过的。
1
|
onChildDraw
(
Canvas
c
,
RecyclerView
recyclerView
,
RecyclerView
.
ViewHolder
viewHolder
,
float
dX
,
float
dY
,
int
actionState
,
boolean
isCurrentlyActive
)
|
该方法就是移动过程中绘制Item的回调。我们在actionState==ItemTouchHelper.ACTION_STATE_SWIPE时,即为滑动的时候绘制背景和删除图片。
初始化
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
|
/**
* 移动过程中绘制Item
*
* @param c
* @param recyclerView
* @param viewHolder
* @param dX
* X轴移动的距离
* @param dY
* Y轴移动的距离
* @param actionState
* 当前Item的状态
* @param isCurrentlyActive
* 如果当前被用户操作为true,反之为false
*/
@
Override
public
void
onChildDraw
(
Canvas
c
,
RecyclerView
recyclerView
,
RecyclerView
.
ViewHolder
viewHolder
,
float
dX
,
float
dY
,
int
actionState
,
boolean
isCurrentlyActive
)
{
//滑动时自己实现背景及图片
if
(
actionState
==
ItemTouchHelper
.
ACTION_STATE_SWIPE
)
{
//dX大于0时向右滑动,小于0向左滑动
View
itemView
=
viewHolder
.
itemView
;
//获取滑动的view
Resources
resources
=
MyApplication
.
getAppContext
(
)
.
getResources
(
)
;
Bitmap
bitmap
=
BitmapFactory
.
decodeResource
(
resources
,
R
.
drawable
.
delete
)
;
//获取删除指示的背景图片
int
padding
=
10
;
//图片绘制的padding
int
maxDrawWidth
=
2
*
padding
+
bitmap
.
getWidth
(
)
;
//最大的绘制宽度
Paint
paint
=
new
Paint
(
)
;
paint
.
setColor
(
resources
.
getColor
(
R
.
color
.
btninvalid
)
)
;
int
x
=
Math
.
round
(
Math
.
abs
(
dX
)
)
;
int
drawWidth
=
Math
.
min
(
x
,
maxDrawWidth
)
;
//实际的绘制宽度,取实时滑动距离x和最大绘制距离maxDrawWidth最小值
int
itemTop
=
itemView
.
getBottom
(
)
-
itemView
.
getHeight
(
)
;
//绘制的top位置
//向右滑动
if
(
dX
>
0
)
{
//根据滑动实时绘制一个背景
c
.
drawRect
(
itemView
.
getLeft
(
)
,
itemTop
,
drawWidth
,
itemView
.
getBottom
(
)
,
paint
)
;
//在背景上面绘制图片
if
(
x
>
padding
)
{
//滑动距离大于padding时开始绘制图片
//指定图片绘制的位置
Rect
rect
=
new
Rect
(
)
;
//画图的位置
rect
.
left
=
itemView
.
getLeft
(
)
+
padding
;
rect
.
top
=
itemTop
+
(
itemView
.
getBottom
(
)
-
itemTop
-
bitmap
.
getHeight
(
)
)
/
2
;
//图片居中
int
maxRight
=
rect
.
left
+
bitmap
.
getWidth
(
)
;
rect
.
right
=
Math
.
min
(
x
,
maxRight
)
;
rect
.
bottom
=
rect
.
top
+
bitmap
.
getHeight
(
)
;
//指定图片的绘制区域
Rect
rect1
=
null
;
if
(
x
|
经过上面的处理,发现此时滑动可以看到我们定制的蓝低的删除背景了,此时可能还有疑问,这样删除是不是很生硬,那就再给它加一个透明度的动画,如下。
1
2
|
float
alpha
=
1.0f
-
Math
.
abs
(
dX
)
/
(
float
)
itemView
.
getWidth
(
)
;
itemView
.
setAlpha
(
alpha
)
;
|
好了,到这里RecyclerView实现滑动删除和拖拽功能的已经介绍完毕了。有问题欢迎留言指出,Have a wonderful day .
打赏支持我写出更多好文章,谢谢!
打赏作者
打赏支持我写出更多好文章,谢谢!
任选一种支付方式