RecyclerView使用ItemTouchHelper实现横向滑动删除

RecyclerView使用ItemTouchHelper实现横向滑动删除

一、目标

实现删除文件夹收藏功能。

  • 普通状态下,通过横向滑动,移除收藏

  • 编辑状态下,点击删除图标,再次点击移除按钮,移除收藏

二、体验地址

神马笔记最新版本:【神马笔记 版本1.3.0.apk

三、功能设计

我们需要在普通状态和编辑状态下,均可以移除文件夹收藏。

  • 普通状态下的移除过程
    1. 从右向左滑动,移除按钮从右侧进入;
    2. 继续滑动,移除按钮固定在右侧;
    3. 继续滑动距离超过0.8,移除按钮移动到左侧,同时自动向左侧滑动一段距离,提醒用户松开即可移除;
  • 编辑状态下的移除过程
    1. 点击左侧删除按钮;
    2. 移除按钮从右侧进入;
    3. 点击移除按钮,移除文件夹收藏

四、准备工作

1. ItemTouchHelper

参考《RecyclerView使用ItemTouchHelper实现拖拽排序》一文,介绍了如何使用ItemTouchHelper以及通过实现ItemTouchHelper.Callback实现侧滑删除功能。

2. SwipeActionHelper

因为ItemTouchHelper是对ViewHolderitemView设置translationX实现滑动。

这样会导致列表项控件整体移动,因此,需要做一些定制才能完成功能设计。

这里,使用了SwipeActionHelper来完成这个工作。

你可以在WhatsAndroid项目中找到这份代码。

GitHub项目地址:https://github.com/jicanghai37927/WhatsAndroid

  • SwipeActionHelper.Adapter
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
public interface Adapter {

boolean isSwiped(SwipeActionHelper helper);

Adapter getActive(SwipeActionHelper helper);

int getDirection(SwipeActionHelper helper);

View getSwipeView(SwipeActionHelper helper);

List<View> getTouchable(SwipeActionHelper helper);

void clear(SwipeActionHelper helper);

void onBegin(SwipeActionHelper helper);

void onMove(SwipeActionHelper helper, float deltaX);

void onEnd(SwipeActionHelper helper, float velocityX);

void onDraw(SwipeActionHelper helper, Canvas canvas);

void onDrawOver(SwipeActionHelper helper, Canvas canvas);

void onActionBegin(SwipeActionHelper helper, int action);

void onActionEnd(SwipeActionHelper helper, int action);

void onClear(SwipeActionHelper helper, int direction);
}

子类ViewHolder需要实现Adapter接口,完成SwipeActionHelperViewHolder之间的通信。

  • SwipeViewHolder
1
2
3
public abstract class SwipeViewHolder<T> extends BridgeViewHolder<T> implements SwipeActionHelper.Adapter, MarginDividerDecoration.Adapter {

}

提供了SwipeActionHelper.Adapter的实现。

  • SwipeHolder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SwipeHolder {

View container;
View swipeView;

boolean isDrag;

SwipeRunner activeRunner;
ArrayList<SwipeRunner> list;

Rect bounds = new Rect();
SwipeActionHelper helper;

}

横向滑动的容器。

  • SwipeRunner

定义了具体的滑动功能,目前支持5种滑动模式。

SwipeRunner子类 滑动行为
DeleteRunner 支持右侧删除按钮
RightDeleteRunner 支持右侧删除及菜单
RightActionRunner 支持右侧菜单
SnapRightRunner 支持按钮吸附在右侧
SnapLeftRunner 支持按钮吸附在左侧

五、组合起来

1. 定义布局资源

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
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorWindowBackground">

<FrameLayout
android:id="@+id/btn_delete"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_red_light"
android:foreground="?selectableItemBackground">

<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:layout_gravity="left"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:text="移除"
android:textStyle="bold"
android:textColor="@android:color/white"
android:textAppearance="@style/TextAppearance.AppCompat.Button"/>

</FrameLayout>

<LinearLayout
android:id="@+id/swipe_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="@dimen/entranceItemHeight"
android:paddingLeft="?listPreferredItemPaddingLeft"
android:paddingRight="?listPreferredItemPaddingRight"
android:background="@color/colorWindowBackground"
android:foreground="?selectableItemBackground"
android:clipToPadding="false"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_gravity="center">

<ImageView
android:id="@+id/btn_remove"
android:layout_width="28dp"
android:layout_height="28dp"
android:src="@drawable/ic_remove_circle_white_24dp"
android:tint="@android:color/holo_red_light"
android:scaleType="centerInside"
android:layout_gravity="left|center_vertical"
android:visibility="gone"
android:layout_marginRight="?listPreferredItemPaddingRight"/>

<ImageView
android:id="@+id/iv_icon"
android:layout_marginRight="?listPreferredItemPaddingRight"
android:layout_width="28dp"
android:src="@drawable/ic_folder_white_24dp"
android:tint="@color/colorFolder"
android:layout_height="28dp"/>

<TextView
android:id="@+id/tv_name"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
android:layout_weight="1"/>

<ImageView
android:id="@+id/btn_drag"
android:layout_width="28dp"
android:layout_height="28dp"
android:src="@drawable/ic_drag_handle_white_24dp"
android:tint="@color/colorDrag"
android:scaleType="centerInside"
android:visibility="gone"
android:layout_gravity="right|center_vertical"
android:layout_marginLeft="?listPreferredItemPaddingLeft"/>

</LinearLayout>

</FrameLayout>

注意移除按钮的定义方式,必须使用FrameLayout作为外层容器。

2. 创建SwipeActionHelper

1
2
3
4
this.swipeActionHelper = new SwipeActionHelper();
swipeActionHelper.setOnSwipeActionListener(new SwipeActionListener());

swipeActionHelper.attach(recyclerView);

3. 创建SwipeHolder

1
2
3
4
5
6
7
8
9
10
11
12
13
SwipeHolder swipeHolder = new SwipeHolder(parent.swipeActionHelper, view, view.findViewById(R.id.swipe_view));

{
View btnAction = view.findViewById(R.id.btn_delete);
btnAction.setOnClickListener(this::onDeleteClick);

DeleteRunner r = new DeleteRunner();
r.add(btnAction);

swipeHolder.add(r);
}

this.setSwipeHolder(swipeHolder);

使用DeleteRunner来实现侧滑动能。

4. 创建ItemDecoration

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
public class RemoveDecoration extends RecyclerView.ItemDecoration {

ArrayList<RemoveInfo> list;

public RemoveDecoration(Context context) {
this.list = new ArrayList<>();
}

public void add(Drawable d, long duration) {
this.list.add(new RemoveInfo(d, System.currentTimeMillis() + duration));
}

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);

{
drawVertical(c, parent, state);
}
}

@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);

}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}

void drawVertical(Canvas canvas, RecyclerView parent, RecyclerView.State state) {

if (parent.getLayoutManager() == null) {
return;
}

canvas.save();

{
final int left;
final int right;

//noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
if (parent.getClipToPadding()) {
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
canvas.clipRect(left, parent.getPaddingTop(), right,
parent.getHeight() - parent.getPaddingBottom());
}
}

{
long time = System.currentTimeMillis();
for (int i = list.size() - 1; i >= 0; i--) {
RemoveInfo info = list.get(i);
if (time >= info.expire) {
list.remove(i);
}
}
}

{
for (RemoveInfo info: list) {
info.drawable.draw(canvas);
}
}

canvas.restore();
}

/**
*
*/
private static class RemoveInfo {

Drawable drawable;
long expire;

RemoveInfo(Drawable d, long time) {
this.drawable = d;
this.expire = time;
}
}
}

通过RemoveDecoration实现列表项被移除时的红色背景。

六、Finally

王维的诗句结束这篇文章。

~随意春芳歇~王孙自可留~