前言:
现在的RecyclerView几乎已经完全取代ListView和GridView了,已经几年没使用ListView和GridView了,想当年还需要自己在getView方法中复用convertView。而现在的RecyclerView一出生就被设计成convertView复用的,尽管你不想复用(才怪)。RecyclerView功能如此强大的同时就会面临许多需求,如给RecyclerView的item添加分割线,给RecyclerView添加下拉刷新,上拉加载更多的功能等等。这篇文章就将为RecyclerView添加中间分割线做个简单的介绍,其实给RecyclerView添加分割线并没有你想象的那么难,只要你肯动手,你就会拥有属于你自己的分割线,想怎么画就怎么画。
实现效果图:
为RecyclerView添加分割线需通过RecyclerView的addItemDecoration方法,addItemDecoration方法需接受一个ItemDecoration类型的对象。为此,我们首先需要自定义一个类继承于ItemDecoration并实现getItemOffsets和SpaceItemDecoration两个方法,步骤如下。
重写getItemOffsets方法,给每个Item设置合适的偏移量(left,top,right,bottom);重写onDraw方法,给每个item绘制分隔线(矩形)。1. 重写getItemOffsets
这个方法在RecyclerView渲染item的时候通过getItemOffsets方法的Rect类型的参数outRect的left,top,right,bottom四个属性为item设置相应的偏移量,如:
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); // 获取位置 int position = parent.getChildAdapterPosition(view); view.setTag(position); RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); if(layoutManager instanceof GridLayoutManager) { GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager; GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup(); mSpanCount = gridLayoutManager.getSpanCount(); mMaxSpanGroupIndex = spanSizeLookup.getSpanGroupIndex(parent.getAdapter().getItemCount() - 1, mSpanCount); int spanSize = spanSizeLookup.getSpanSize(position); int spanIndex = spanSizeLookup.getSpanIndex(position, mSpanCount); int spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, mSpanCount); Log.d(TAG, "getItemOffsets: spanIndex:" + spanIndex); if (spanSize <mSpanCount && spanIndex != 0) { // 左边需要偏移 outRect.left = mSpace; } if(spanGroupIndex != 0) { outRect.top = mSpace; } }else if(layoutManager instanceof LinearLayoutManager){ LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; if(position != 0) { if (linearLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL) { outRect.left = mSpace; } else { outRect.top = mSpace; } } } }在上面getItemOffsets方法中首先通过RecyclerView的getChildAdapterPosition方法获得item在Adapter中的位置并设置给它的tag属性保存下来,然后判断布局管理器的类型。如果布局管理器是GridLayoutManager类型,那么就需要判断当前Item不是位于该行的第一个(spanIndex != 0)并且没有占据一行(spanSize < mSpanCount)的时候就需要给Item设置left偏移量为我们的Space,如果当Item所在的行不是第一行(spanGroupIndex != 0)的时候需要给Item设置top偏移。当布局管理器为LinearLayoutManager的时候那就更简单了,先判断当前Item的position是不是第一个,如果不是第一个那么再判断布局管理器是横向还是纵向的,如果是横向,那么设置Item的left偏移量为Space,反之设置Item的top偏移量为Space。
2. 重写onDraw,绘制分割线
由于布局管理器的不同,绘制分割线的方法也不同,所以首先需要先判断一下布局管理器的类型
** * 绘制分割线 * @param c * @param parent * @param state */ @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); if(layoutManager instanceof GridLayoutManager) { drawSpace(c, parent); }else if(layoutManager instanceof LinearLayoutManager){ LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; if(linearLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL){ // 画竖直分割线 drawVertical(c,parent); }else{ // 画横向分割线 drawHorizontal(c,parent); } } } 如果布局管理器为GridLayoutManager方法,那么我们就通过drawSpace方法完成分割线的绘制,如下: /** * 绘制分割线 * @param canvas * @param parent */ private void drawSpace(Canvas canvas, RecyclerView parent) { GridLayoutManager gridLayoutManager = (GridLayoutManager) parent.getLayoutManager(); int spanCount = gridLayoutManager.getSpanCount(); GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup(); int childCount = parent.getChildCount(); int top,bottom,left,right; for(int i=0;i<childCount;i++){ // 绘制思路,以绘制bottom和left为主,top和right不绘制,需要判断出当前的item是否位于边缘,位于边缘的item不绘制bottom和left,你懂得 View child = parent.getChildAt(i); int position = (int) child.getTag(); RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); int spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, spanCount); int spanSize = spanSizeLookup.getSpanSize(position); int spanIndex = spanSizeLookup.getSpanIndex(position, spanCount); // 画bottom分割线,如果没到达底部,绘制bottom if(spanGroupIndex < mMaxSpanGroupIndex) { top = child.getBottom() + layoutParams.bottomMargin; bottom = top + mSpace; left = child.getLeft() - layoutParams.leftMargin; // 不需要外边缘 right = child.getRight() + layoutParams.rightMargin + mSpace; canvas.drawRect(left, top, right, bottom, mPaint); } // 画left分割线 if (spanSize != mSpanCount && spanIndex!=0) { // 左边需要分割线,开始绘制 top = child.getTop() - layoutParams.topMargin; bottom = child.getBottom() + layoutParams.bottomMargin; left = child.getLeft() - layoutParams.leftMargin - mSpace; right = left + mSpace; canvas.drawRect(left, top, right, bottom, mPaint); } } }这里你需要理解的是绘制思路,我们先判断当前Item所在的行是否位于最后一行,如果不是最后一行,那么需要绘制底部分割线(横向)。需要注意的是计算分割线的right需加上Space,不然竖直分割线和水平分割线交界处就会出现空白。当当前Item所占的SpanSize不是整行,并且当前Item的所在的行标不为0的时候(spanSize != mSpanCount && spanIndex!=0)需要绘制竖直分割线。
如果布局管理器是LinearLayoutManager,那么我们需要判断布局方法是纵向还是横向,如果是纵向,那么我们需要绘制横线(底部),如果是横向,那么我们需要绘制竖线(右侧)。 private void drawHorizontal(Canvas c, RecyclerView parent) { // 画竖直分割线 LinearLayoutManager linearLayoutManager = (LinearLayoutManager) parent.getLayoutManager(); int top,bottom,left,right; for(int i=0;i<parent.getChildCount();i++){ View child = parent.getChildAt(i); int position = (int) child.getTag(); // 判断是否位于边缘 if(position == linearLayoutManager.getItemCount()-1) continue; RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); top = child.getBottom()+layoutParams.bottomMargin; bottom = top + mSpace; left = child.getLeft() - layoutParams.leftMargin; right = child.getRight() + layoutParams.rightMargin; c.drawRect(left,top,right,bottom,mPaint); } } private void drawVertical(Canvas c, RecyclerView parent) { // 画竖直分割线 LinearLayoutManager linearLayoutManager = (LinearLayoutManager) parent.getLayoutManager(); int top,bottom,left,right; for(int i=0;i<parent.getChildCount();i++){ View child = parent.getChildAt(i); int position = (int) child.getTag(); // 判断是否位于边缘 if(position == 0) continue; RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); top = child.getTop()-layoutParams.topMargin; bottom = child.getBottom()+layoutParams.bottomMargin; left = child.getLeft() - layoutParams.leftMargin - mSpace; right = left + mSpace; c.drawRect(left,top,right,bottom,mPaint); } }到这里为止,一个item内部无边缘的分割线就绘制完了,整个绘制过程中需要理解的是我们绘制的思路以Item left偏移和top偏移以画底部分割线和画右侧分割线并判断Item是否位于边缘而决定要不要绘制分割线达到无边缘效果。如果你对于上面的偏移量的计算不是很清楚的话你不妨动手将上面的代码敲一遍并改变left,top,right,bottom看看效果,只有这样你才能理解这几个偏移量的含义。点此,github直通车,也查看如下完整代码:
package com.lt.demo; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; /** * Created by lt on 2018/4/9. */ public class SpaceItemDecoration extends RecyclerView.ItemDecoration{ private static final java.lang.String TAG = "SpaceItemDecoration"; private int mSpanCount; private int mSpace; private Paint mPaint; private int mMaxSpanGroupIndex; /** * 获取Item的偏移量 * @param outRect * @param view * @param parent * @param state */ @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); // 获取位置 int position = parent.getChildAdapterPosition(view); view.setTag(position); RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); if(layoutManager instanceof GridLayoutManager) { GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager; GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup(); mSpanCount = gridLayoutManager.getSpanCount(); mMaxSpanGroupIndex = spanSizeLookup.getSpanGroupIndex(parent.getAdapter().getItemCount() - 1, mSpanCount); int spanSize = spanSizeLookup.getSpanSize(position); int spanIndex = spanSizeLookup.getSpanIndex(position, mSpanCount); int spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, mSpanCount); Log.d(TAG, "getItemOffsets: spanIndex:" + spanIndex); if (spanSize <mSpanCount && spanIndex != 0) { // 左边需要偏移 outRect.left = mSpace; } if(spanGroupIndex != 0) { outRect.top = mSpace; } }else if(layoutManager instanceof LinearLayoutManager){ LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; if(position != 0) { if (linearLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL) { outRect.left = mSpace; } else { outRect.top = mSpace; } } } } public SpaceItemDecoration(int space, int spaceColor) { this.mSpace = space; mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(spaceColor); mPaint.setStyle(Paint.Style.FILL); } /** * 绘制分割线 * @param c * @param parent * @param state */ @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); if(layoutManager instanceof GridLayoutManager) { drawSpace(c, parent); }else if(layoutManager instanceof LinearLayoutManager){ LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; if(linearLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL){ // 画竖直分割线 drawVertical(c,parent); }else{ // 画横向分割线 drawHorizontal(c,parent); } } } private void drawHorizontal(Canvas c, RecyclerView parent) { // 画竖直分割线 LinearLayoutManager linearLayoutManager = (LinearLayoutManager) parent.getLayoutManager(); int top,bottom,left,right; for(int i=0;i<parent.getChildCount();i++){ View child = parent.getChildAt(i); int position = (int) child.getTag(); // 判断是否位于边缘 if(position == linearLayoutManager.getItemCount()-1) continue; RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); top = child.getBottom()+layoutParams.bottomMargin; bottom = top + mSpace; left = child.getLeft() - layoutParams.leftMargin; right = child.getRight() + layoutParams.rightMargin; c.drawRect(left,top,right,bottom,mPaint); } } private void drawVertical(Canvas c, RecyclerView parent) { // 画竖直分割线 LinearLayoutManager linearLayoutManager = (LinearLayoutManager) parent.getLayoutManager(); int top,bottom,left,right; for(int i=0;i<parent.getChildCount();i++){ View child = parent.getChildAt(i); int position = (int) child.getTag(); // 判断是否位于边缘 if(position == 0) continue; RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); top = child.getTop()-layoutParams.topMargin; bottom = child.getBottom()+layoutParams.bottomMargin; left = child.getLeft() - layoutParams.leftMargin - mSpace; right = left + mSpace; c.drawRect(left,top,right,bottom,mPaint); } } /** * 绘制分割线 * @param canvas * @param parent */ private void drawSpace(Canvas canvas, RecyclerView parent) { GridLayoutManager gridLayoutManager = (GridLayoutManager) parent.getLayoutManager(); int spanCount = gridLayoutManager.getSpanCount(); GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup(); int childCount = parent.getChildCount(); int top,bottom,left,right; for(int i=0;i<childCount;i++){ // 绘制思路,以绘制bottom和left为主,top和right不绘制,需要判断出当前的item是否位于边缘,位于边缘的item不绘制bottom和left,你懂得 View child = parent.getChildAt(i); int position = (int) child.getTag(); RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); int spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, spanCount); int spanSize = spanSizeLookup.getSpanSize(position); int spanIndex = spanSizeLookup.getSpanIndex(position, spanCount); // 画bottom分割线,如果没到达底部,绘制bottom if(spanGroupIndex < mMaxSpanGroupIndex) { top = child.getBottom() + layoutParams.bottomMargin; bottom = top + mSpace; left = child.getLeft() - layoutParams.leftMargin; // 不需要外边缘 right = child.getRight() + layoutParams.rightMargin + mSpace; canvas.drawRect(left, top, right, bottom, mPaint); } // 画left分割线 if (spanSize != mSpanCount && spanIndex!=0) { // 左边需要分割线,开始绘制 top = child.getTop() - layoutParams.topMargin; bottom = child.getBottom() + layoutParams.bottomMargin; left = child.getLeft() - layoutParams.leftMargin - mSpace; right = left + mSpace; canvas.drawRect(left, top, right, bottom, mPaint); } } } }