前言
最新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