Android自定義RecyclerView分割線,打造無邊緣分割線
前言:
現在的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);
}
}
}
}