今天我们讲点儿干货,我们是否有这些需求:(1)允许用户改变一个列表中各条目的顺序,例如用户想把自己喜欢的列表项拖动置顶或按喜好排序,正好RecyclerView可以轻松实现拖动条目改变顺序这个功能;(2)用户也想删除一个条目,如滑动某一个列表项删除条目,类似于QQ列表滑动删除。这些功能在RecyclerView控件上都可以轻松实现,依照惯例我们先来看一下运行效果:
其中切换按钮,是将垂直列表切换成网格布局样式,同样可以拖动网格列表项。
开讲啦!
我们要实现的功能是拖动条目排序与滑动条目删除,这2个操作都离不开触条目的触摸事件。说到条目触摸这里有一个类:
android.support.v7.widget.helper.ItemTouchHelper.callback
这个类是一个抽象类,我们可以通过实现它的抽象函数,来实现列表项条目 发生触摸事件时的回调函数。也就是说当ITEM触摸
事件发生时,系统会自动回调ItemTounchHelper.Callback里的一些函数。
(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 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.
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来计算条目放大或缩小的比例,以及透明度的比例
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 implements ItemTouchMoveListener {
private ArrayListlist;
private StartDragListener dragListener;
private OnItemClickListener mOnItemClickListener;
public MyRecyclerAdapter(ArrayList 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