前言: RecyclerView相对于ListView实现拖拽和滑动的效果要容易很多,今天写一个小程序,在上一篇文章 RecyclerView+CardView使用总结(带小例子) 基础上实现RecyclerView条目的上下拖拽和滑动删除,效果图如下:
第一步:设置拖动和滑动的回掉,让recyclerView和回调处理关联起来 主要代码如下:
mRecyclerView = (RecyclerView) mView.findViewById(R.id.hot_fragment_rcv);
/*1,设置管理器*/
mRecyclerView.setLayoutManager(new LinearLayoutManager(this.getContext()));
/*2,设置适配器*/
initListData();
mAdapter = new HotFgListStrAdapter(mDatas);
mRecyclerView.setAdapter(mAdapter);
/**设置拖动和滑动的回调*/
ItemTouchHelper.Callback callback = new RecycleItemTouchHelper(mAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);
/*3,添加item的添加和移除动画, 这里我们使用默认的*/
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
/*4,添加分割线,自定义分割线*/
mRecyclerView.addItemDecoration(new HotFgItemDecoration());
第二步,实现拖动和滑动的方式,怎么拖动和滑动,向哪个方向拖动和滑动
ItemTouchHelper.Callback是谷歌提供的强大的工具类,处理RecyclerView拖动和滑动的实现,并且可以实现我们自己定义的动画和定制的效果。RecycleItemTouchHelper是我们继承ItemTouchHelper.Callback实现的。对于实现那几个函数,函数的作用,我在代码中做了注释,代码如下:
RecycleItemTouchHelper.java
public class RecycleItemTouchHelper extends ItemTouchHelper.Callback {
private final ItemTouchHelperCallback helperCallback;
public RecycleItemTouchHelper(ItemTouchHelperCallback helperCallback) {
this.helperCallback = helperCallback;
}
/**
* 该方法返回一个整数,用来指定拖拽和滑动在哪个方向是可以的。
* @param recyclerView
* @param viewHolder
* @return
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
/**
* makeMovementFlags(int dargFlags, int swipeFlags)
* 该方法第一个参数指定拖动,第二个参数自定滑动。参数选择有六个如下:
* ItemTouchHelper.UP 滑动拖拽方向向上
* ItemTouchHelper.DOWN 向下
* ItemTouchHelper.LEFT 向左
* ItemTouchHelper.RIGHT 向右
* ItemTouchHelper.START 依赖布局方向的水平开始方法(右向左)
* ItemTouchHelper.END 依赖布局方向的水平结束方向(左向右)
*/
//本次设置结果为 支持上下拖拽,向右滑动
int flag = makeMovementFlags(ItemTouchHelper.UP|ItemTouchHelper.DOWN, ItemTouchHelper.END);
return flag;
}
/**
* 该方法是拖拽的回掉
* @param recyclerView
* @param viewHolder 表示拖动的item
* @param target 表示拖动的目标位置的item
* @return 如果item切换的位置返回true, 否则返回false
*/
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
helperCallback.onMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
/**
* 该方法为item滑动的回掉
* @param viewHolder 表示滑动的item
* @param direction 表示滑动的方向
*/
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
helperCallback.onItemDelete(viewHolder.getAdapterPosition());
}
//*********上面的三个方法是必须实现的*********下面三个方法可选择实现
/**
* item是否支持长按拖动,true支持,false不支持
* @return
*/
@Override
public boolean isLongPressDragEnabled() {
return super.isLongPressDragEnabled();
}
/**
* item是否支持滑动,true支持,false不支持
* @return
*/
@Override
public boolean isItemViewSwipeEnabled() {
return super.isItemViewSwipeEnabled();
}
/**
* 移动过程中绘制item
* @param c
* @param recyclerView
* @param viewHolder
* @param dX 表示x轴移动距离
* @param dY 表示y轴移动距离
* @param actionState 当前item的状态
* @param isCurrentlyActive 如果当前用户在操作则为true,否则为false
*/
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
方法说明,我在注释中已经解释,这里就不再解释了,注意: 如果实现拖动或者滑动必须将上面的是否支持拖动isLongPressDragEnabled()和是否支持滑动isItemViewSwipeEnabled()的方法返回true,否则onMove()或者onSwiped()方法不会执行。默认情况下isLongPressDragEnabled()和isItemViewSwipeEnabled()返回true;
ItemTouchHelperCallback是个接口,包含处理拖动和滑动后的方法,代码如下:ItemTouchHelperCallback.java
public interface ItemTouchHelperCallback {
/**
* 移动item
* @param fromPosition 起始位置
* @param toPosition 结束位置
*/
void onMove(int fromPosition, int toPosition);
/**
* 删除item
* @param position
*/
void onItemDelete(int position);
}
第三步:对拖动和滑动后的事件进行响应处理
对RecyclerView进行拖动后,条目的位置变化了,滑动后,条目移除了,这些变化最终的展示效果,必须通知Adapter来实现,所以Adapter实现接口ItemTouchHelperCallback,具体代码如下:HotFgListStrAdapter.java
public class HotFgListStrAdapter extends RecyclerView.Adapter<HotFgListStrAdapter.TextViewHolder> implements ItemTouchHelperCallback {
private List<String> mDatas;
private LayoutInflater mInflater;
public HotFgListStrAdapter(List<String> mDatas) {
this.mDatas = mDatas;
}
@Override
public TextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
mInflater = LayoutInflater.from(parent.getContext());
return new TextViewHolder(mInflater.inflate(R.layout.item_hot_fg_list2, parent, false));
}
@Override
public void onBindViewHolder(TextViewHolder holder, final int position) {
holder.mTvTitle.setText(mDatas.get(position));
holder.mTvTitle2.setText(mDatas.get(position));
}
@Override
public int getItemCount() {
return mDatas.size();
}
@Override
public void onMove(int fromPosition, int toPosition) {
Collections.swap(mDatas, fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onItemDelete(int position) {
mDatas.remove(position);
notifyItemRemoved(position);
}
/**
* 文字item的holder
*/
class TextViewHolder extends RecyclerView.ViewHolder{
private TextView mTvTitle, mTvTitle2;
public TextViewHolder(View itemView) {
super(itemView);
mTvTitle = (TextView) itemView.findViewById(R.id.hot_fg_item_tv);
mTvTitle2 = (TextView) itemView.findViewById(R.id.hot_fg_item_tv2);
}
}
}
notifyItemMoved(…)、notifyItemRemoved(…)、notifyItemChanged(…)等等针对一条或者连续多条数据进行更新,这个很方便,不用因为一条数据改变,而调用notifyDataSetChanged()来通知多所有数据刷新。建议使用这个方法。代码到这里其实已经实现文章开头的效果了,但是滑动删除一条条目的体验不好,如果我们滑动后显示一个删除按钮,体验会更好一些。
第四步:功能升级,优化用户体验
现在我们在滑动过程中显示一个删除图标,删除过程中再增加一个动画。这个效果不是很麻烦,前面我们写过一个方法onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
该方法是移动过程中绘制item的回调,当actionState == ItemTouchHelper.ACTION_STATE_SWIPE时,即为滑动时候绘制背景和删除图片,具体代码如下:
/**
* 移动过程中绘制item
* @param c
* @param recyclerView
* @param viewHolder
* @param dX 表示x轴移动距离
* @param dY 表示y轴移动距离
* @param actionState 当前item的状态
* @param isCurrentlyActive 如果当前用户在操作则为true,否则为false
*/
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
//滑动时自己实现背景及图片
if (actionState==ItemTouchHelper.ACTION_STATE_SWIPE){
//dX大于0时向右滑动,小于0向左滑动
View itemView=viewHolder.itemView;//获取滑动的view
Resources resources= recyclerView.getResources();
Bitmap bitmap= BitmapFactory.decodeResource(resources, R.drawable.address_delete);//获取删除指示的背景图片
int padding =40;//图片绘制的padding
int maxDrawWidth=2*padding+bitmap.getWidth();//最大的绘制宽度
Paint paint=new Paint();
paint.setColor(resources.getColor(R.color.themeColor));
int x=Math.round(Math.abs(dX));
int drawWidth=Math.min(x,maxDrawWidth);//实际的绘制宽度,取实时滑动距离x和最大绘制距离maxDrawWidth最小值
int itemTop=itemView.getBottom()-itemView.getHeight();//绘制的top位置
//向右滑动
if(dX>0){
//根据滑动实时绘制一个背景
c.drawRect(itemView.getLeft(),itemTop,drawWidth,itemView.getBottom(),paint);
//在背景上面绘制图片
if (x>padding){//滑动距离大于padding时开始绘制图片
//指定图片绘制的位置
Rect rect=new Rect();//画图的位置
rect.left=itemView.getLeft()+padding;
rect.top=itemTop+(itemView.getBottom()-itemTop-bitmap.getHeight())/2;//图片居中
int maxRight=rect.left+bitmap.getWidth();
rect.right=Math.min(x,maxRight);
rect.bottom=rect.top+bitmap.getHeight();
//指定图片的绘制区域
Rect rect1=null;
if (x<maxRight){
rect1=new Rect();//不能再外面初始化,否则dx大于画图区域时,删除图片不显示
rect1.left=0;
rect1.top = 0;
rect1.bottom=bitmap.getHeight();
rect1.right=x-padding;
}
c.drawBitmap(bitmap,rect1,rect,paint);
}
//滑动透明度动画
float alpha = 1.0f - Math.abs(dX) / (float) itemView.getWidth();
itemView.setAlpha(alpha);
//绘制时需调用平移动画,否则滑动看不到反馈
itemView.setTranslationX(dX);
}else {
//如果在getMovementFlags指定了向左滑动(ItemTouchHelper。START)时则绘制工作可参考向右的滑动绘制,也可直接使用下面语句交友系统自己处理
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}else {
//拖动时有系统自己完成
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
这一步做完后,运行效果如下:
文章到这里就结束了,文章有不对的地方,欢迎指正^_^。这里推荐github上的一个开源项目,继承ListView实现的滑动条目进行处理操作。