Android ItemTouchHelper 实现交互动画

01.拖拽需要实现功能

  • 需要实现拖拽的功能如下所示
    • 长按item后拖动,与其他item交换位置
    • 按住item右面的图标后拖动,与其他item交换位置
    • 左滑item变透明并缩小,超出屏幕后,其他item补上
    • 右滑item变透明并缩小,超出屏幕后,其他item补上

02.几个重要的方法说明

  • 几个重要的方法说明

    • 需要自定义类实现ItemTouchHelper.Callback类,并重写其中几个方法
    1
    2
    3
    4
    5
    6
    isLongPressDragEnabled                  是否可以长按拖拽排序
    isItemViewSwipeEnabled Item是否可以被滑动
    getMovementFlags 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向
    onMove 当Item被拖拽的时候被回调
    onSwiped 当View被滑动删除的时候
    onSelectedChanged 当item被拖拽或侧滑时触发

03.简单实现思路

  • 几个方法中代码思路
    • 要想达到上面功能需求,在getMovementFlags方法中,当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向,那我们知道支持拖拽和滑动删除的无非就是LinearLayoutManager和GridLayoutManager了,所以可以根据布局管理器的不同做了响应的区分。
    • 在onMove方法中处理拖拽的回调逻辑,那么什么时候被调用?当Item被拖拽排序移动到另一个Item的位置的时候被调用。在onSwiped方法[当Item被滑动删除到不见]中处理被删除后的逻辑。为了降低代码耦合度,可以通过接口listener回调的方式交给外部处理。
  • 上下拖动时与其他item进行位置交换
    • ItemTouchHelper.Callback本身不具备将两个item互换位置的功能,但RecyclerView可以,我们可以在item拖动的时候把当前item与另一个item的数据位置交换,再调用RecyclerView的notifyItemMoved()方法刷新布局,同时,因为RecyclerView自带item动画,就可以完成上面的交互效果。
  • 左右滑出屏幕时其他item补上
    • 只要在item滑出屏幕时,将对应的数据删掉,再调用RecyclerView的notifyItemRemoved()方法刷新布局即可。

04.拖拽效果上优化

  • 拖拽效果优化
    • 在item被拖拽或侧滑时修改背景色,当动作结束后将背景色恢复回来,而ItemTouchHelper.Callback中正好有对应这两个状态的方法,分别是:onSelectedChanged()、clearView()。那么优化处理其实可以放到这两个方法中处理。
    • 左右滑动使item透明度变浅且缩小该如何实现呢?让item执行了两种属性动画而已,在ItemTouchHelper.Callback中有一个方法可以拿到item被拖拽或滑动时的位移变化,那就是onChildDraw()方法,在该方法中设置item渐变和缩放属性动画。
    • 出现问题,按照上面做法会出现删除后有空白item留出来,那么为什么会出现这种情况呢?并不是多出了两条空白数据,它们是正常的数据,只是看不到了,这是因为RecyclerView条目(itemView)覆用导致的,前面在onChildDraw()方法中对itemView设置了透明和缩小,而一个列表中固定只有几个itemView而已,当那两个透明缩小的itemView被再次使用时,之前设置的透明度和高度比例已经是0,所以就出现了这种情况,解决方法也很简单,只要在item被移除后,将itemView的透明度和高度比例设置回来即可

05.完整代码展示

完整代码如下所示

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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
public class ItemTouchHelpCallback extends ItemTouchHelper.Callback {

/**
* Item操作的回调,去更新UI和数据源
*/
private OnItemTouchCallbackListener onItemTouchCallbackListener;
/**
* 是否可以拖拽
*/
private boolean isCanDrag = false;
/**
* 是否可以被滑动
*/
private boolean isCanSwipe = false;
/**
* 按住拖动item的颜色
*/
private int color = 0;

public ItemTouchHelpCallback(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的颜色
* @param color 颜色
*/
public void setColor(@ColorInt int color){
this.color = color;
}

/**
* 当Item被长按的时候是否可以被拖拽
*
* @return true
*/
@Override
public boolean isLongPressDragEnabled() {
return isCanDrag;
}

/**
* Item是否可以被滑动(H:左右滑动,V:上下滑动)
* isItemViewSwipeEnabled()返回值是否可以拖拽排序,true可以,false不可以
* @return true
*/
@Override
public boolean isItemViewSwipeEnabled() {
return isCanSwipe;
}

/**
* 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向
* 动作标识分:dragFlags和swipeFlags
* dragFlags:列表滚动方向的动作标识(如竖直列表就是上和下,水平列表就是左和右)
* wipeFlags:与列表滚动方向垂直的动作标识(如竖直列表就是左和右,水平列表就是上和下)
*
* 思路:如果你不想上下拖动,可以将 dragFlags = 0
* 如果你不想左右滑动,可以将 swipeFlags = 0
* 最终的动作标识(flags)必须要用makeMovementFlags()方法生成
*/
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof 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) 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;
}
//第一个参数是拖拽flag,第二个是滑动的flag
return makeMovementFlags(dragFlag, swipeFlag);
}
return 0;
}


/**
* 当Item被拖拽的时候被回调
*
* @param recyclerView recyclerView
* @param srcViewHolder 当前被拖拽的item的viewHolder
* @param targetViewHolder 当前被拖拽的item下方的另一个item的viewHolder
* @return 是否被移动
*/
@Override
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder srcViewHolder,
@NonNull RecyclerView.ViewHolder targetViewHolder) {
if (onItemTouchCallbackListener != null) {
int srcPosition = srcViewHolder.getAdapterPosition();
int targetPosition = targetViewHolder.getAdapterPosition();
return onItemTouchCallbackListener.onMove(srcPosition, targetPosition);
}
return false;
}


/**
* 当item侧滑出去时触发(竖直列表是侧滑,水平列表是竖滑)
*
* @param viewHolder viewHolder
* @param direction 滑动的方向
*/
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
if (onItemTouchCallbackListener != null) {
onItemTouchCallbackListener.onSwiped(viewHolder.getAdapterPosition());
}
}

/**
* 当item被拖拽或侧滑时触发
*
* @param viewHolder viewHolder
* @param actionState 当前item的状态
*/
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
//不管是拖拽或是侧滑,背景色都要变化
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
if (color==0){
viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext()
.getResources().getColor(android.R.color.darker_gray));
}else {
viewHolder.itemView.setBackgroundColor(color);
}
}
}


/**
* 当item的交互动画结束时触发
*
* @param recyclerView recyclerView
* @param viewHolder viewHolder
*/
@Override
public void clearView(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources()
.getColor(android.R.color.white));
viewHolder.itemView.setAlpha(1);
viewHolder.itemView.setScaleY(1);
}


@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
float value = 1 - Math.abs(dX) / viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(value);
viewHolder.itemView.setScaleY(value);
}
}


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);
}
}