转载请注明出处:https://www.jianshu.com/p/05680f83a471
# ItemTouchHelper 简介:
这是一个RecyclerView的工具,提供了drag & swipe 的功能,可以帮助我们处理RecyclerView中的Item的拖拽和滑动事件。
一、创建ItemTouchHelper:
//创建helper对象,callback监听recyclerView item 的各种状态
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
//关联recyclerView,一个helper对象只能对应一个recyclerView
itemTouchHelper.attachToRecyclerView(recyclerView);
二、创建callback的两种方式:
- ItemTouchHelper.Callback :
new Callback() {
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
// 有6个值来控制方向
//控制拖拽的方向(一般是上下左右)
int dragFlags= ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
//控制快速滑动的方向(一般是左右)
int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
//还有两个flag 分别是 ItemTouchHelper.START 和 ItemTouchHelper.END ,原文的解释是:
//Horizontal direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout direction. Used for swipe & drag control.
// 横向方向,取决于 RecyclerView 的方向,与 LinearLayoutManager 的 layoutReverse 有关(暂时没有验证)
return makeMovementFlags(dragFlags, swipeFlags);//计算movement flag值
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
// 拖拽时,每移动一个位置就会调用一次。
// 在此改变 dataList 被移动 item 的位置,并且刷新adapter
if (recyclerView == null) return false;
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (adapter == null) return false;
if (dataList != null) {
int from = viewHolder.getAdapterPosition();
endPosition = target.getAdapterPosition();//在这里我一直在刷新最后移动到的位置,以便接下来做其他操作
Collections.swap(dataList, from, endPosition);//数据交换位置
// 使用notifyItemMoved可以表现得更平滑,问题是 from ~ endPosition 间的item position 不会更新,并引发一系列角标混乱的问题,
//这个问题可以在后面的 onSelectedChanged()方法中解决。
// 在此做notifyItemMoved操作就足够了,notifyDataSetChanged() 和 notifyItemRangeChanged() 会打断 drag 操作。
adapter.notifyItemMoved(from, endPosition);
//其他操作
if (onMovedListener != null) {
onMovedListener.onMoved(dataList, from, endPosition);
}
}
return true; // true 可以拖拽,false 不能。
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
//滑动处理
}
}
要想能够拖拽还得将 isLongPressDragEnabled() 这个方法返回值置为 true,同理,滑动是 isItemViewSwipeEnabled()。这两个回调默认返回 true,如果不小心重写返回了false,就改过来吧。
- ItemTouchHelper.SimpleCallback :
new SimpleCallback(int dragFlags,int swipeFlags) {
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
}
ItemTouchHelper.SimpleCallback 继承自 ItemTouchHelper.Callback,仅仅是对两个触摸事件 flag 值设置的封装 , 提供了 set、get 方法,其他无异。
public SimpleCallback(int dragDirs, int swipeDirs) {
mDefaultSwipeDirs = swipeDirs;
mDefaultDragDirs = dragDirs;
}
//不需要再写 getMovementFlags() 方法
@Override
public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
return makeMovementFlags(getDragDirs(recyclerView, viewHolder),
getSwipeDirs(recyclerView, viewHolder));
}
三、 处理拖拽后position错乱问题:
解决问题的根本就是在拖拽操作结束后,刷新 adapter。
- 不能在 onMove 回调中直接刷新,会打断 drag 操作。
- 在 onSelectedChanged() 或 clearView() 回调刷新。
- 建议使用 notifyItemRangeChanged(int start,int end) 方法刷新,可以减少不必要的刷新。
- onSelectedChanged() , 只会回调两次:手势事件产生 和 手势抬起并且动画结束。
int startPosition;
int endPosition;
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (viewHolder != null && actionState != ACTION_STATE_IDLE) {
// 非闲置状态下,记录下起始 position
startPosition = viewHolder.getAdapterPosition();
}
if(actionState == ACTION_STATE_IDLE){
// 当手势抬起时刷新,endPosition 是在 onMove() 回调中记录下来的
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
if (adapter != null) {
adapter.notifyItemRangeChanged(Math.min(startPosition, endPosition), Math.abs(startPosition - endPosition) + 1);
}
}
}
在这里如果同时有 drag 和 swipe 两种情况,判断一下是 drag 还是 swipe 的结束好一些。
- clearView() , 只会触发一次:当手势抬起并且动画结束后回调,刷新操作放在这里也可以。
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
}
- Tips:clearViewon() 比 SelectedChanged() 后调用,所以在手势抬起后,actionState 变为 ACTION_STATE_IDLE,然后 viewHolder 会在 clearView 中被置为null。
写在最后
有什么问题欢迎留言