为RecyclerView添加拖动排序以及滑动删除功能

/ 0评 / 4

前言

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

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注