RecyclerView高级进阶(七)上下拖动列表条目改变条目顺序与滑动删除条目

今天我们讲点儿干货,我们是否有这些需求:(1)允许用户改变一个列表中各条目的顺序,例如用户想把自己喜欢的列表项拖动置顶或按喜好排序,正好RecyclerView可以轻松实现拖动条目改变顺序这个功能;(2)用户也想删除一个条目,如滑动某一个列表项删除条目,类似于QQ列表滑动删除。这些功能在RecyclerView控件上都可以轻松实现,依照惯例我们先来看一下运行效果:

其中切换按钮,是将垂直列表切换成网格布局样式,同样可以拖动网格列表项。

开讲啦!

一、几个关键点

1. ItemTounchHelper.Callback

我们要实现的功能是拖动条目排序与滑动条目删除,这2个操作都离不开触条目的触摸事件。说到条目触摸这里有一个类:

 android.support.v7.widget.helper.ItemTouchHelper.callback

这个类是一个抽象类,我们可以通过实现它的抽象函数,来实现列表项条目 发生触摸事件时的回调函数。也就是说当ITEM触摸

事件发生时,系统会自动回调ItemTounchHelper.Callback里的一些函数。

2.RecyclerView.Adapter的两个函数:

(1)notifyItemMoved(fromPosition, toPosition);

  这个是最终实现两个ITEM交换顺序的函数,它是adapter里的函数,因此当我们在ItemTouchHelper里监听到上下拖动事件时,应调用adapter里的notifyItemMoved(fromPosition,toPosition)函数。

(2)notifyItemRemoved(position);

同理,当我们在emTouchHelper里监听到swipe滑动items事件时调用notifyItemRemoved(position)函数来删除我们正在滑动的条目postion。

3. 如何将RecyclerView,  ItemTounchHelper.Callback、RecyclerView.Adapter这几个东西串起来.

这里我们只是简单描述一下,详细的实现我们会紧跟在后边讲解。

ItemTouchHelper.Callback callback = new MyItemTouchHelperCallback(adapter);
itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(recylerview);
正如上述代码所示,将adapter作为参数传递给ItemTouchHelper.Callback callback这样在监听到拖动
和滑动删除事件时,就可以直接调用adapter里的notifyItemMoved(fromPostion,toPosition)与
notifyItemRemoved(position)函数了。当然在这里我们不会直接传递adapter对象,而是让adapter对象实现

一个接口再传递给ItemTouchHelperCallback, 然后在ItemTouchHelperCallback里调用接口的函数(实现部分在adapter类里),这样就相当于把处理事件的任务转交给了adapter,从而在adapter里调用notifyItemMoved与notifyItemRemoved,以及执行数据交换等工作。这样就将adapter与ItemTouchHelperCallback关联了起来,这时还没有和我们的主角RecyclerView关联起来。下边的两句代码就是将recyclerView于ItemTouchHelper关联了起来。

itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(recylerview);
OK,是时候贴出我们的MainActivity代码了(完整项目下载在最后)
MainActivity.JAVA
package com.anyikang.volunteer.sos.recyclerview;

import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

import java.util.ArrayList;

public class MainActivity extends Activity implements StartDragListener {

	private RecyclerView recylerview;  //RecyclerView控件实例对象
   //RecyclerView要显示的 列表数据,在此为一组字符串。
    private MyRecyclerAdapter adapter; //同ListView一样,需要一个适配器来 将list数据 装载到 RecyclerView列表控件。
	//private MyStaggedRecyclerAdapter adapter;  //列表显示风格  为瀑布流 界面样式 的 适配器。
	boolean isGrid = false;
	Button btConvert;
	DividerGridViewItemDecoration mGridViewItemDecoration;
	MyListItemDecoration myListItemDecoration;
	private RecyclerView.ItemDecoration myItemDecoration;
	private ItemTouchHelper itemTouchHelper;

	private ArrayList<Integer> list= new ArrayList();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		initData();//列表数据
		initView();
	}

	private void initData() {
		list.add(R.drawable.fone);
		list.add(R.drawable.ftwo);
		list.add(R.drawable.fthree);
		list.add(R.drawable.ffour);
		list.add(R.drawable.ffive);
		list.add(R.drawable.fsix);
		list.add(R.drawable.fseven);
		list.add(R.drawable.feight);
		list.add(R.drawable.fnine);
		list.add(R.drawable.ften);
		list.add(R.drawable.feleven);
		list.add(R.drawable.ftwelve);
		list.add(R.drawable.thirteen);
	}
	/**
	 * 初始化视图
	 */
	public void initView()
	{

		//分割线装饰
		mGridViewItemDecoration = new DividerGridViewItemDecoration(MainActivity.this); //网格分割线
		myListItemDecoration = new MyListItemDecoration(MainActivity.this,LinearLayout.VERTICAL);//垂直列表分割线

		//recylerview设置adapter与装饰分割线
		recylerview = (RecyclerView) findViewById(R.id.recylerview);
		adapter = new MyRecyclerAdapter(list,this);
		recylerview.setLayoutManager(new LinearLayoutManager(this));
		myItemDecoration = new  MyListItemDecoration(this, LinearLayout.VERTICAL);
		recylerview.addItemDecoration(myItemDecoration);
		recylerview.setItemAnimator(new DefaultItemAnimator());
		recylerview.setAdapter(adapter);

		//将ItemTouchHelper与adapter 和 recylerview关联起来
		ItemTouchHelper.Callback callback = new MyItemTouchHelperCallback(adapter);
		itemTouchHelper = new ItemTouchHelper(callback);
		itemTouchHelper.attachToRecyclerView(recylerview);


		//垂直列表与网格风格互相切换
		btConvert = findViewById(R.id.btConvert);
		btConvert.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View view) {

				if(myItemDecoration!=null)
					recylerview.removeItemDecoration(myItemDecoration);
				if(!isGrid){
			              recylerview.setLayoutManager(new GridLayoutManager(MainActivity.this, 2));
					//recylerview.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false));//默认垂直
					myItemDecoration = new DividerGridViewItemDecoration(MainActivity.this);
					recylerview.addItemDecoration(myItemDecoration);
				}else{
					recylerview.setLayoutManager(new LinearLayoutManager(MainActivity.this,LinearLayout.VERTICAL,false));//默认垂直
					myItemDecoration = new MyListItemDecoration(MainActivity.this, LinearLayoutManager.VERTICAL);
					recylerview.addItemDecoration(myItemDecoration);
				}
				isGrid = !isGrid;
			}
		});
		return;

	}

	@Override
	public void onStartDrag(RecyclerView.ViewHolder viewHolder) {
		itemTouchHelper.startDrag(viewHolder);
	}
}

其中onStartDrag函数可能大家比较陌生,这个一会儿就有用了。这个函数将在adapter里调用,调用的时机决定了在什么情况下

itemToucherHelper才开始捕捉拖动事件,举个例子,当我们触摸adapter里的图片时,调用MainAcitivy里的startDrag函数,让整个ITEM就开始被监听是否拖动。

MainActivity实现了 implements StartDragListener,并在new MyRecyclerAdapter的时候将这个接口实例传递给了adapter,以供adapter在触摸图片

的时候回调MainActivity里的onStartDrag函数。调用了这个函数之后就意味着与itemTouchHelper相关联的callback就开始捕捉事件了,以及配合adapter执行交换与删除ITEM.

二、实现这几个关键点

1. ItemTounchHelper.Callback

package com.anyikang.volunteer.sos.recyclerview;

import android.graphics.Canvas;
import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.support.v7.widget.helper.ItemTouchHelper.Callback;

public class MyItemTouchHelperCallback extends Callback {
	private ItemTouchMoveListener moveListener;

	public MyItemTouchHelperCallback(ItemTouchMoveListener moveListener) {
		this.moveListener = moveListener;
	}

	//最先调用,判断ITEM触摸拖动方向,如上下左右拖动;滑动方向SWIPE 左右滑动
	@Override
	public int getMovementFlags(RecyclerView recyclerView, ViewHolder holder) {

		int dragFlags = ItemTouchHelper.UP|ItemTouchHelper.DOWN;
		//int dragFlags = ItemTouchHelper.UP|ItemTouchHelper.DOWN|ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT;
		int swipeFlags = ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT;
		int flags = makeMovementFlags(dragFlags, swipeFlags);

		return flags;
	}

	/**
	 * 是否长按拖动
	 * @return true长按拖动
	 */
	@Override
	public boolean isLongPressDragEnabled() {
		return true;
	}

	/**
	 * 拖动时ITEM时,调用adapter里的onItemMove函数实现ITEM交换位置与交换数据
	 * @param recyclerView: 列表控件
	 * @param srcHolder      源ITEM,
	 * @param targetHolder   目标ITEM,即拖动条目时要和这个ITEM交换位置
	 * @return
	 */
	@Override
	public boolean onMove(RecyclerView recyclerView, ViewHolder srcHolder, ViewHolder targetHolder) {
		if(srcHolder.getItemViewType()!=targetHolder.getItemViewType()){
			return false;
		}
		boolean result = moveListener.onItemMove(srcHolder.getAdapterPosition(), targetHolder.getAdapterPosition());
		return result;
	}

	/**
	 * swipe侧滑时调用adapter里的onItemRemove函数实现ITEM删除
	 * @param holder
	 * @param arg1
	 */
	@Override
	public void onSwiped(ViewHolder holder, int arg1) {
		moveListener.onItemRemove(holder.getAdapterPosition());
	}

	/**
	 * 拖动时ITEM背景色
	 * @param viewHolder
	 * @param actionState
	 */
	@Override
	public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
		//判断选中状态
		if(actionState!=ItemTouchHelper.ACTION_STATE_IDLE){
			viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getColor(R.color.colorPrimary));
		}
		super.onSelectedChanged(viewHolder, actionState);
	}

	/**
	 * 交换数据后停止拖动,背景色恢复为白色
	 * @param recyclerView
	 * @param viewHolder
	 */
	@Override
	public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
		viewHolder.itemView.setBackgroundColor(Color.WHITE);
		super.clearView(recyclerView, viewHolder);
	}

	/**
	 * 重新绘制ITEM的大小等。例如在这里滑动删除时,让ITEM逐渐缩小,并逐渐变成透明而消失
	 * @param c
	 * @param recyclerView
	 * @param viewHolder
	 * @param dX
	 * @param dY
	 * @param actionState
	 * @param isCurrentlyActive
	 */
	@Override
	public void onChildDraw(Canvas c, RecyclerView recyclerView,
			ViewHolder viewHolder, float dX, float dY, int actionState,
			boolean isCurrentlyActive) {

		if(actionState==ItemTouchHelper.ACTION_STATE_SWIPE){
			//透明度动画
			float alpha = 1-Math.abs(dX)/viewHolder.itemView.getWidth();
			viewHolder.itemView.setAlpha(alpha);//1~0
			viewHolder.itemView.setScaleX(alpha);//1~0
			viewHolder.itemView.setScaleY(alpha);//1~0
		}
		
		
		super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState,
				isCurrentlyActive);
	}
	

}

以上代码已经有注视了,我们还是简单讲解一下

   (1). 构造函数:

public MyItemTouchHelperCallback(ItemTouchMoveListener moveListener) {
   this.moveListener = moveListener;
}
传递进来一个ItemTouchMoveListener接口的实例对象(在adapter里实现),用以在拖动和侧滑时调用adapter里
里的交换函数与删除ITEM的函数。
(2). getMovementFlags函数: 
getMovementFlags函数决定了要监听什么样的事件,它的返回值是一个Flag,在这里返回了 可以监听上下拖动与左右
侧滑 触摸事件的FLAG。核心函数是:makeMovementFlags(dragFlags,swipeFlags),在这里dragFlags为上下
,swipeFlags为左右方向。
(3). onMove与onSwiped

   onMove函数,监听到了拖动ITEM事件,则就去调用adapter里的notifyItemMoved函数去移动条目位置。

 onSwiped,监听到了swipe侧滑事件,则就去调用adapter里的notifyItemRemoved函数去删除条目。

  (4). 绘制条目相关的函数:

onSelectedChanged:  选定item并拖动条目时修改条目的背景色为蓝色
clearView:       拖动停止时,恢复条目的背景色为白色,是onSelectedChanged的结束处理。
onChildDraw:      拖动的过程中,根据拖动的位移量dx,dy来计算条目放大或缩小的比例,以及透明度的比例

2.RecyclerView.Adapter

MyRecyclerAdapter.java
package com.anyikang.volunteer.sos.recyclerview;

import android.support.v7.widget.RecyclerView;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import java.util.ArrayList;
import java.util.Collections;

public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.MyViewHolder> implements ItemTouchMoveListener {

	private ArrayList<Integer>list;

	private StartDragListener dragListener;
	private OnItemClickListener mOnItemClickListener;



	public MyRecyclerAdapter(ArrayList<Integer> list,StartDragListener dragListener) {
		this.list = list;
		this.dragListener = dragListener;
	}


	class MyViewHolder extends RecyclerView.ViewHolder{
		ImageView imv;

		public MyViewHolder(View view) {
			super(view);
			imv = (ImageView)view.findViewById(R.id.imv);
		}
		
	}
	@Override
	public int getItemCount() {
		return list.size();
	}

	@Override
	public void onBindViewHolder(final MyViewHolder holder, final int position) {
		holder.imv.setImageResource(list.get(position));

		holder.imv.setOnTouchListener(new View.OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {

				if(event.getAction() == MotionEvent.ACTION_DOWN)
				{
					dragListener.onStartDrag(holder);
				}

				return false;
			}
		});
	}


	@Override
	public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1) {
		MyViewHolder holder = new MyViewHolder(View.inflate(viewGroup.getContext(), R.layout.list_item2, null));
		return holder;
	}


	public interface OnItemClickListener{
		void onItemClick(View view, int position);
	}

	public void setOnItemClickListener(OnItemClickListener listener){
		this.mOnItemClickListener = listener;
	}


	//ITEM交换位置,先交换数据,再调用notifyItemMoved(fromPosition, toPosition);刷新
	@Override
	public boolean onItemMove(int fromPosition, int toPosition) {


		Collections.swap(list, fromPosition, toPosition);
		notifyItemMoved(fromPosition, toPosition);
		return true;
	}

	//ITEM删除,先删除数据,然后再notifyItemRemoved刷新
	@Override
	public boolean onItemRemove(int position) {
		list.remove(position);
		notifyItemRemoved(position);
		return true;
	}
}

接口

ItemTouchMoveListener .java
package com.anyikang.volunteer.sos.recyclerview;

public interface ItemTouchMoveListener {

	/**
	 * 当拖拽的时候回调,交换fromPosition与toPosition

	 */
	boolean onItemMove(int fromPosition, int toPosition);

	/**
	 * 当条目被移除是回调,删除position条目
	 */
	boolean onItemRemove(int position);
}

从上述代码可以看出adapter先实现了

ItemTouchMoveListener 接口抽象函数
onItemMove与onItemMove,供1中的ItemTouchHelper.callback里调用,来实现交换条目与删除条目的效果。
还有一个值得注意的地方,我们在之前提过,就是onStartDrag函数的调用,表示在从什么时机开始ItemTouchHelper

才真正开始监听列表item上的拖到或侧滑swipe事件,在这里有以下代码:

holder.imv.setOnTouchListener(new View.OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {

				if(event.getAction() == MotionEvent.ACTION_DOWN)
				{
					dragListener.onStartDrag(holder);
				}

				return false;
			}
		});

它表示当手指按下图片的时候,ItemTouchHelper就可以开始监听ITEM的触摸事件了。因为在imageview.onTouch函数的

Down事件里调用了dragListener.onStartDrag。

3. 如何将RecyclerView,  ItemTounchHelper.Callback、RecyclerView.Adapter这几个东西串起来.

这个我们在 “(一)”中已经提到了,核心代码就在MainActivity.java里,在此不再赘述,请大家直接下载完整项目代码进行研究。

三、总结

RecyclerView: 要玩转的列表控件

ItemTounchHelper.Callback:  监听列表条目的触摸事件,包括拖动与侧滑

ItemTounchHelper:         startDrag来启动监听,或者说叫醒监听器ItemTounchHelper.Callback  开始监听

RecyclerView.Adapter: 主要管理列表数据,核心的2个函数notifyItemRemoved,notifyItemMoved.

java技巧:

类与类之间的调用,使用了interface来实现类之间的交互,传递数据等,就是在类1实现一个接口和它的抽象函数,将实现的接口实例传递给类2,然后在类2中的恰当时机调用接口中的函数,从而将代码执行的阵地转移到了类1与类1中的成员变量一起完成最终的处理.  即实现了一个“回调”。

源码下载: https://download.csdn.net/download/gaoxiaoweiandy/10590022

猜你喜欢

转载自blog.csdn.net/gaoxiaoweiandy/article/details/80671744