如果你现在还在用listview,gridview,那么我只能说,太out了。recyclerview现在使用的很普遍了已经,它不仅实现了他们俩的功能,还很方便的实现了横向列表,瀑布流列表等等!不过这些功能相信大部分人都知道,今天记录的是关于通过RecyclerView的ItemDecoration实现的一些进阶功能,从易到难包括如下:
1. ItemDecoration实现padding
2. ItemDecoration实现下划线
3. ItemDecoration实现酷炫吸顶效果
4. ItemDecoration实现item的拖拽,平移等操作
(穿插还通过OnItemTouchListener封装了列表的点击和长点击事件)
demo下载:https://github.com/loveAndroidAndroid/android-study
下面讲解有限,有需要的看demo地址
ItemDecoration是RecyclerView内部的一个抽象类,要实现这个抽象类自然需要实现内部的抽象方法,除了deprecated的方法只有下面三个方法:
//可以实现类似padding的效果
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
//可以实现类似绘制背景的效果,内容在上面
public void onDraw(Canvas c, RecyclerView parent, State state)
//可以绘制在内容的上面,覆盖内容
public void onDrawOver(Canvas c, RecyclerView parent, State state)
声明下:ItemDecoration的使用必须在setAdapter前,通过recyclerView.addItemDecoration()方法设置
public class PaddingDecoration extends RecyclerView.ItemDecoration{
private int padding;
public PaddingDecoration(Context context) {
//即你要设置的分割线的宽度 --这里设为10dp
padding = context.getResources().getDimensionPixelSize(R.dimen.padding);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
//outRect就是你那个item条目的矩形
outRect.left = padding; //相当于 设置 left padding
outRect.top = padding; //相当于 设置 top padding
outRect.right = padding; //相当于 设置 right padding
outRect.bottom = padding; //相当于 设置 bottom padding
}
}
public class DeviderDecoration extends RecyclerView.ItemDecoration {
private int deviderHeight;
private Paint dividerPaint;
public DeviderDecoration(Context context) {
//设置画笔
dividerPaint = new Paint();
//设置分割线颜色
dividerPaint.setColor(context.getResources().getColor(R.color.colorAccent));
//设置分割线宽度
deviderHeight = context.getResources().getDimensionPixelSize(R.dimen.divider_height);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
//改变宽度
outRect.bottom = deviderHeight;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
//得到列表所有的条目
int childCount = parent.getChildCount();
//得到条目的宽和高
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < childCount - 1; i++) {
View view = parent.getChildAt(i);
//计算每一个条目的顶点和底部 float值
float top = view.getBottom();
float bottom = view.getBottom() + deviderHeight;
//重新绘制
c.drawRect(left, top, right, bottom, dividerPaint);
}
}
}
public class SectionDecoration extends RecyclerView.ItemDecoration {
private List dataList;
private DecorationCallback callback;
private TextPaint textPaint;
private Paint paint;
private int topGap;
private int alignBottom;
private Paint.FontMetrics fontMetrics;
public SectionDecoration(List dataList, Context context, DecorationCallback decorationCallback) {
Resources res = context.getResources();
this.dataList = dataList;
this.callback = decorationCallback;
//设置悬浮栏的画笔---paint
paint = new Paint();
paint.setColor(res.getColor(R.color.colorGray));
//设置悬浮栏中文本的画笔
textPaint = new TextPaint();
textPaint.setAntiAlias(true);
textPaint.setTextSize(DensityUtil.dip2px(context, 14));
textPaint.setColor(Color.DKGRAY);
textPaint.setTextAlign(Paint.Align.LEFT);
fontMetrics = new Paint.FontMetrics();
//决定悬浮栏的高度等
topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);
//决定文本的显示位置等
alignBottom = res.getDimensionPixelSize(R.dimen.sectioned_alignBottom);
}
//图1:代表了getItemOffsets(),可以实现类似padding的效果
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int pos = parent.getChildAdapterPosition(view);
String groupId = callback.getGroupId(pos);
if (groupId.equals("-1")) return;
//只有是同一组的第一个才显示悬浮栏
if (pos == 0 || isFirstInGroup(pos)) {
outRect.top = topGap;
if (dataList.get(pos) == "") {
outRect.top = 0;
}
} else {
outRect.top = 0;
}
}
//图2:代表了onDraw(),可以实现类似绘制背景的效果,内容在上面
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
String groupId = callback.getGroupId(position);
if (groupId.equals("-1")) return;
String textLine = callback.getGroupFirstLine(position).toUpperCase();
if (textLine == "") {
float top = view.getTop();
float bottom = view.getTop();
c.drawRect(left, top, right, bottom, paint);
return;
} else {
if (position == 0 || isFirstInGroup(position)) {
float top = view.getTop() - topGap;
float bottom = view.getTop();
//绘制悬浮栏
c.drawRect(left, top, right, bottom, paint);
//绘制文本
c.drawText(textLine, left, bottom, textPaint);
}
}
}
}
//图3:代表了onDrawOver(),可以绘制在内容的上面,覆盖内容
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = state.getItemCount();
int childCount = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
float lineHeight = textPaint.getTextSize() + fontMetrics.descent;
String preGroupId = "";
String groupId = "-1";
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
preGroupId = groupId;
groupId = callback.getGroupId(position);
if (groupId.equals("-1") || groupId.equals(preGroupId)) continue;
String textLine = callback.getGroupFirstLine(position).toUpperCase();
if (TextUtils.isEmpty(textLine)) continue;
int viewBottom = view.getBottom();
float textY = Math.max(topGap, view.getTop());
//下一个和当前不一样移动当前
if (position + 1 < itemCount) {
String nextGroupId = callback.getGroupId(position + 1);
//组内最后一个view进入了header
if (nextGroupId != groupId && viewBottom < textY) {
textY = viewBottom;
}
}
//textY - topGap决定了悬浮栏绘制的高度和位置
c.drawRect(left, textY - topGap, right, textY, paint);
//left+2*alignBottom 决定了文本往左偏移的多少(加-->向左移)
//textY-alignBottom 决定了文本往右偏移的多少 (减-->向上移)
// c.drawText(textLine, left + 2 * alignBottom, textY - alignBottom, textPaint);
c.drawText(textLine, left, textY - alignBottom, textPaint);
}
}
/**
* 判断是不是组中的第一个位置
*
* @param pos
* @return
*/
private boolean isFirstInGroup(int pos) {
if (pos == 0) {
return true;
} else {
// 因为是根据 字符串内容的相同与否 来判断是不是同意组的,所以此处的标记id 要是String类型
// 如果你只是做联系人列表,悬浮框里显示的只是一个字母,则标记id直接用 int 类型就行了
String prevGroupId = callback.getGroupId(pos - 1);
String groupId = callback.getGroupId(pos);
//判断前一个字符串 与 当前字符串 是否相同
if (prevGroupId.equals(groupId)) {
return false;
} else {
return true;
}
}
}
//定义一个借口方便外界的调用
public interface DecorationCallback {
String getGroupId(int position);
String getGroupFirstLine(int position);
}
}
textRecycler.addItemDecoration(new SectionDecoration(list, this, new SectionDecoration.DecorationCallback() {
@Override
public String getGroupId(int position) {
if(NameBean.get(position)!=null) {
return NameBean.get(position);
}
return "-1";
}
@Override
public String getGroupFirstLine(int position) {
if(NameBean.get(position)!=null) {
return NameBean.get(position);
}
return "";
}
}));
(穿插还通过OnItemTouchListener封装了列表的点击和长点击事件)
该功能主要通过addOnItemTouchListener() 来实现。
textRecycler.addOnItemTouchListener(new OnRecyclerItemClickListener(textRecycler) {
@Override
public void onItemClick(RecyclerView.ViewHolder viewHolder) {
ToastUtils.showToast(viewHolder.getAdapterPosition()+1+"");
}
@Override
public void onLongClick(RecyclerView.ViewHolder viewHolder) {
ToastUtils.showToast(viewHolder.getAdapterPosition()+1+"");
//当 item 被长按且不是第一个时,开始拖曳这个 item
if (viewHolder.getLayoutPosition() != 0) {
itemTouchHelper.startDrag(viewHolder);
}
}
});
其中 OnRecyclerItemClickListener 是自定义的一个触摸监听器,代码如下:
public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
//手势探测器
private GestureDetectorCompat mGestureDetectorCompat;
private RecyclerView mRecyclerView;
public OnRecyclerItemClickListener(RecyclerView mRecyclerView) {
this.mRecyclerView = mRecyclerView;
mGestureDetectorCompat = new GestureDetectorCompat(mRecyclerView.getContext(),
new ItemTouchHelperGestureListener(mRecyclerView,this));
}
//第一个是拦截触摸事件的,第二个是处理触摸事件的,第三个是处理触摸冲突的。第三个这里我们用不到
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetectorCompat.onTouchEvent(e);
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetectorCompat.onTouchEvent(e);
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
//提供单机 长按的方法
public abstract void onItemClick(RecyclerView.ViewHolder viewHolder);
public abstract void onLongClick(RecyclerView.ViewHolder viewHolder);
}
GestureDetectorCompat 中传入了一个 ItemTouchHelperGestureListener,代码如下:
public class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
private RecyclerView mRecyclerView;
private OnRecyclerItemClickListener onRecyclerItemClickListener;
public ItemTouchHelperGestureListener(RecyclerView mRecyclerView, OnRecyclerItemClickListener onRecyclerItemClickListener) {
this.mRecyclerView = mRecyclerView;
this.onRecyclerItemClickListener = onRecyclerItemClickListener;
}
//一次单独的轻触抬起手指操作,就是普通的点击事件
@Override
public boolean onSingleTapUp(MotionEvent e) {
//这个ChildHelper类,它会协助获取RecyclerView中的childVIew。 可点击看源码
View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
if (childViewUnder != null) {
RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
onRecyclerItemClickListener.onItemClick(childViewHolder);
}
return true;
}
//长按屏幕超过一定时长,就会触发,就是长按事件
@Override
public void onLongPress(MotionEvent e) {
View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
if (childViewUnder != null) {
RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
onRecyclerItemClickListener.onLongClick(childViewHolder);
}
}
其实通过一个手势探测器 GestureDetectorCompat 来探测屏幕事件,然后通过手势监听器 SimpleOnGestureListener 来识别手势事件的种类,然后调用我们设置的对应的回调方法。
通过findChildViewUnder()可以知道我们点击的是哪个item,可看源码
public View findChildViewUnder(float x, float y) {
final int count = mChildHelper.getChildCount();
for (int i = count - 1; i >= 0; i--) {
final View child = mChildHelper.getChildAt(i);
final float translationX = ViewCompat.getTranslationX(child);
final float translationY = ViewCompat.getTranslationY(child);
if (x >= child.getLeft() + translationX &&
x <= child.getRight() + translationX &&
y >= child.getTop() + translationY &&
y <= child.getBottom() + translationY) {
return child;
}
}
return null;
}
同时我们调用 RecyclerView 的另一个方法 getChildViewHolder(),可以获得该 item 的 ViewHolder,最后再回调我们定义的虚方法 onItemClick() 就ok了,这样我们就可以在外部实现该方法来获得 item 的点击事件了。
ItemTouchHelper 一个帮助开发人员处理拖拽和滑动删除的实现类,它能够让你非常容易实现侧滑删除、拖拽的功能。
/**
* Created by wen on 2017/8/8.
* ItemTouchHelper 一个帮助开发人员处理拖拽和滑动删除的实现类,它能够让你非常容易实现侧滑删除、拖拽的功能。
*/
public class MyItemTouchHelper extends ItemTouchHelper.Callback {
RecyclerAdapter adapter;
public MyItemTouchHelper(RecyclerAdapter adapter) {
this.adapter = adapter;
}
//通过返回值来设置是否处理某次拖曳或者滑动事件
//dragFlags 是拖拽标志,
//swipeFlags 是滑动标志,
//swipeFlags 都设置为0,暂时不考虑滑动相关操作。
//getMovementFlags() 用于设置是否处理拖拽事件和滑动事件,以及拖拽和滑动操作的方向,有以下两种情况:
//如果是列表类型的 RecyclerView,拖拽只有 UP、DOWN 两个方向
//如果是网格类型的则有 UP、DOWN、LEFT、RIGHT 四个方向
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
} else {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
//注意:和拖曳的区别就是在这里
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
}
//当长按并进入拖曳状态时,拖曳的过程中不断的回调此方法
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
//拖动的 item 的下标
int fromPosition = viewHolder.getAdapterPosition();
//目标 item 的下标,目标 item 就是当拖曳过程中,不断和拖动的 item 做位置交换的条目。
int toPosition = target.getAdapterPosition();
//对应某些需求,某一个item不能拖拽
if (toPosition == 0) {
return false;
}
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
//通过你传入的adapter得到你的数据 并进行交换
Collections.swap(((RecyclerAdapter) adapter).getDataList(), i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(((RecyclerAdapter) adapter).getDataList(), i, i - 1);
}
}
adapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
//滑动删除的回调
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int adapterPosition = viewHolder.getAdapterPosition();
adapter.notifyItemRemoved(adapterPosition);
((RecyclerAdapter)adapter).getDataList().remove(adapterPosition);
}
//当长按 item 刚开始拖曳的时候调用
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
//给被拖曳的 item 设置一个深颜色背景
viewHolder.itemView.setBackgroundColor(Color.LTGRAY);
}
super.onSelectedChanged(viewHolder, actionState);
}
//当完成拖曳手指松开的时候调用
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
//给已经完成拖曳的 item 恢复开始的背景。
// 这里我们设置的颜色尽量和你 item 在 xml 中设置的颜色保持一致
viewHolder.itemView.setBackgroundColor(Color.WHITE);
}
//返回 false 让它控制所有的 item 都不能拖曳。
@Override
public boolean isLongPressDragEnabled() {
return false;
}
}
itemTouchHelper = new ItemTouchHelper(new MyItemTouchHelper(adapter));
itemTouchHelper.attachToRecyclerView(textRecycler);
textRecycler.addOnItemTouchListener(new OnRecyclerItemClickListener(textRecycler) {
@Override
public void onItemClick(RecyclerView.ViewHolder viewHolder) {
ToastUtils.showToast(viewHolder.getAdapterPosition()+1+"");
}
@Override
public void onLongClick(RecyclerView.ViewHolder viewHolder) {
ToastUtils.showToast(viewHolder.getAdapterPosition()+1+"");
//当 item 被长按且不是第一个时,开始拖曳这个 item(这里只是一个特殊需求)
if (viewHolder.getLayoutPosition() != 0) {
itemTouchHelper.startDrag(viewHolder);
}
}
});
本文记录了recycleview的ItemDecoration的进阶使用!在此声明还有一个好玩的东西ItemAnimator可以实现列表的动画,留作提醒,下次研究!有什么写的不好,也请提出来讨论吧。