前言
最新MIUI9出来啦,不得不说交互动效做的真心不赖,本篇介绍的就是如何为列表实现拖动排序功能,也就是仿照MIUI的信息助手的设置里面的功能,效果图如下,长按可以拖动排序也可以滑动删除,由于使用的RecycleView,想做成支付宝那种网格类型的拖动排序也是几句代码的事啦。

具体实现
首先设置好的RecycleView,然后实例化一个ItemTouchHelper,将ItemTouchHelper与RecyclerView关联起来就完成啦。
ItemTouchHelper helper = new ItemTouchHelper(getItemTouchHelperCallback()); helper.attachToRecyclerView(mRecyclerView);
ItemTouchHelper的构造方法中需要一个ItemTouchHelper.Callback,ItemTouchHelper会在拖拽的时候回调Callback中相应的方法,所以具体的拖动逻辑我们在Callback中实现即可。
ItemTouchHelper.Callback必须实现如下几个方法
public class MyItemTouchHelperCallback extends ItemTouchHelper.Callback {
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return 0;
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder targetViewHolder) {
return false;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
}
下面我就来介绍下每个方法的具体作用
getMovementFlags:返回值用来标识RecycleView的item可以拖动的方向以及可以滑动的方向,由于使用的一个int类型的每一位代表不同的方向,所以需要使用makeMovementFlags方法生成最终结果。
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlag;
int swipeFlags;
//如果是表格布局,则可以上下左右的拖动,但是不能滑动
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
dragFlag = ItemTouchHelper.UP |
ItemTouchHelper.DOWN |
ItemTouchHelper.LEFT |
ItemTouchHelper.RIGHT;
swipeFlags = 0;
}
//如果是线性布局,那么只能上下拖动,只能左右滑动
else {
dragFlag = ItemTouchHelper.UP |
ItemTouchHelper.DOWN;
swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
}
//通过makeMovementFlags生成最终结果
return makeMovementFlags(dragFlag, swipeFlags);
}
当在getMovementFlags构建好flags并返回以后,我们的RecycleView就可以拖动啦,但是效果并不像上面的gif图片一样。
onMove:当item被长按进入拖动状态以后会被的回调,返回值代表是否交换了位置,其实没啥用。一般返回true。
如果我们不进行其他的任何处理,就只能看到一个item被拖动,没有上图中item交换的动画效果,其实ItemTouchHelper并不能帮我们交换数据,需要我们自己交换数据源然后使用Adapter的notifyItemMoved方法去交换数据。
onSwiped:当item滑动的时候的回调。
同样的,如果我们不进行任何其他处理,只能看到一个item被滑动然后消失,但是位置还在,ItemTouchHelper并不能帮我们交换数据,需要我们自己删除数据并调用Adapter的notifyItemRemoved方法。
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
//被拖动的item位置
int fromPosition = viewHolder.getLayoutPosition();
//他的目标位置
int targetPosition = target.getLayoutPosition();
//为了降低耦合,使用接口让Adapter去实现交换功能
mItemPositionListener.onItemSwap(fromPosition, targetPosition);
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
//为了降低耦合,使用接口让Adapter去实现交换功能
mItemPositionListener.onItemMoved(viewHolder.getLayoutPosition());
}
可以看到,由于真实的数据交换应该是给Adapter去实现的,只有Adapter才拥有数据,所以为了降低耦合,抽取出一个接口给Adapter去实现。
//接口定义
public interface OnItemPositionListener {
//交换
void onItemSwap(int from, int target);
//滑动
void onItemMoved(int position);
}
//接口实现
private List<String> mDatas = new ArrayList<>();
@Override
public void onItemSwap(int from, int target) {
Collections.swap(mDatas, from, target);
notifyItemMoved(from, target);
}
@Override
public void onItemMoved(int position) {
mDatas.remove(position);
notifyItemRemoved(position);
}
一些小优化
如果我们想正在被拖动的item背景比较深,那么我们还需要进行其他的额外处理。
覆写ItemTouchHelper.Callback中的onSelectedChanged与clearView方法,onSelectedChanged与clearView分别对应拖动/停止拖动,我们在这里设置下背景颜色即可。
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
//当开始拖拽的时候
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
viewHolder.itemView.setBackgroundColor(Color.LTGRAY);
}
}
//当手指松开的时候
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
viewHolder.itemView.setBackgroundColor(Color.TRANSPARENT);
super.clearView(recyclerView, viewHolder);
}
如果我们想只有部分的item能被拖动或者就像miui的信息助手里面一样点击右边的图标即可拖动,那么我们可以自己触发拖动逻辑。
首先覆写ItemTouchHelper.Callback的isLongPressDragEnabled方法,让其返回false,表示我们不需要默认的长按开始拖动功能。然后在我们需要开始拖动的地方调用ItemTouchHelper.startDrag(ViewHolder viewHolder)方法即可开始拖动。如下图,只有在点击右侧图标的时候可以被拖动。

//禁止长按滚动交换,需要滚动的时候使用{@link ItemTouchHelper#startDrag(ViewHolder)}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
//当手指按下的时候才开始拖动
protected class MyHolder extends RecyclerView.ViewHolder implements View.OnTouchListener {
public ImageView drag;
public MyHolder(View itemView) {
super(itemView);
drag = itemView.findViewById(R.id.item_drag);
drag.setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//当按下的时候才开始拖动
if (event.getAction() == MotionEvent.ACTION_DOWN) {
helper.startDrag(this);
}
return false;
}
}
再多说一句:ItemTouchHelper的源码注释写的非常非常的详细,如果觉得本篇博客写的不太清除可以直接去查看源码注释。
参考链接:
本文Demo地址:Github