前些天利用周末時間搗鼓了一下Recyclerview,其實雖然現在RecyclerView出來了這么久,之所以得到我們的偏愛是因為它的高度解耦和高度可自定義非常靈活。我相信很多人在使用RecyclerView的時候曾經也為分割線頭疼過,有人可能使用很簡單的方法就是在每一個item里面加一個view設置背景最后在adapter里面處理最后分割線。但是久而久之你會發(fā)現你在項目中使用RecyclerView的頻率非常高,不停的重寫這樣的代碼難道你不覺得累么?說實話我就是覺得繁瑣和重復無用代碼所以想造輪子,當初真是很傻很天真啊!好啦,該進入正題了
首先在gradle里面添加依賴如下:
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testCompile 'junit:junit:4.12'
compile 'com.android.support:design:25.0.0'
compile 'com.android.support:recyclerview-v7:25.0.0'
compile 'com.android.support:appcompat-v7:25.0.0'
}
然后在綁定一些簡單的數據如圖
對于RecyclerView的layoutManager你了解多少?水平、垂直、網格都少不了它,去看看源碼你就會發(fā)現新大陸的
if (isVertical) {//水平布局
isVertical = false;
addItemDecoration = new DividerItemDecoration(MainActivity.this, LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this, LinearLayoutManager.HORIZONTAL, false));
recyclerView.addItemDecoration(addItemDecoration);
} else {//豎直布局
isVertical = true;
addItemDecoration = new DividerItemDecoration(MainActivity.this, LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
recyclerView.addItemDecoration(addItemDecoration);
}
if (isGrid) {//流式布局
isGrid = false;
recyclerView.setAdapter(new MyStaggedRecyclerAdapter(infoBeans));
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,LinearLayoutManager.VERTICAL));
} else {//網格布局
isGrid = true;
dividerGridViewItemDecoration = new DividerGridViewItemDecoration(MainActivity.this);
recyclerView.setLayoutManager(new GridLayoutManager(MainActivity.this,3));
recyclerView.setAdapter(new MyRecyclerAdapter(infoBeans));
recyclerView.addItemDecoration(dividerGridViewItemDecoration);
}
}
這些LayoutManager都是提供不需要自己手動寫,下面進入今天主題分割線了。首先,我們可以思考RecyclerView它到底是怎么弄出的分割線呢?
我們進入ItemDecoration就能發(fā)現它是RecyclerView的一個抽象內部類還有onDraw方法
/**
* Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
* Any content drawn by this method will be drawn before the item views are drawn,
* and will thus appear underneath the views.
*
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView
*/
public void onDraw(Canvas c, RecyclerView parent, State state) {
onDraw(c, parent);
}
/**
* @deprecated
* Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)}
*/
@Deprecated
public void onDraw(Canvas c, RecyclerView parent) {
}
/**
* @deprecated
* Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
*/
@Deprecated
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
outRect.set(0, 0, 0, 0);
}
/**
* Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
* the number of pixels that the item view should be inset by, similar to padding or margin.
* The default implementation sets the bounds of outRect to 0 and returns.
*
* <p>
* If this ItemDecoration does not affect the positioning of item views, it should set
* all four fields of <code>outRect</code> (left, top, right, bottom) to zero
* before returning.
*
* <p>
* If you need to access Adapter for additional data, you can call
* {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
* View.
*
* @param outRect Rect to receive the output.
* @param view The child view to decorate
* @param parent RecyclerView this ItemDecoration is decorating
* @param state The current state of RecyclerView.
*/
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
沒錯通過RecyclerView代碼可以看出來分割線的繪制就是通過這兩個方法確定繪制區(qū)域和執(zhí)行繪制的,下面我們來自定義一個DividerItemDecoration繼承ItemDecoration重寫onDraw和getItemOffsets方法
@Override
public void onDraw(Canvas c, RecyclerView parent, State state) {
//RecyclerView會調用該方法繪制分割線
if(mOrientation == LinearLayoutManager.VERTICAL){//豎直
drawVertical(c,parent);
}else{//水平
drawHorizontal(c,parent);
}
super.onDraw(c, parent, state);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
State state) {
// 調用此方法先獲取條目之間的寬度并設置outRect矩形區(qū)域
if(mOrientation == LinearLayoutManager.VERTICAL){
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
}else{
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0 );
}
}
private void drawHorizontal(Canvas c, RecyclerView parent) {// 畫水平線
int top = parent.getPaddingTop();
int bottom = parent.getHeight() - parent.getPaddingBottom();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount ; i++) {
View child = parent.getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
int left = child.getRight() + params.rightMargin + Math.round(ViewCompat.getTranslationX(child));
int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top , right, bottom);
mDivider.draw(c);
}
}
private void drawVertical(Canvas c, RecyclerView parent) {// 畫水豎直
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount ; i++) {
View child = parent.getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin + Math.round(ViewCompat.getTranslationY(child));
int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top , right, bottom);
mDivider.draw(c);
}
}
如圖我們會發(fā)現一個問題,最后一項分割線還是繪制了,那么需要我們自己處理一下

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
State state) {
// 調用此方法先獲取條目之間的寬度并設置outRect矩形區(qū)域
// 最后一項不需要分割線所以可以設置偏移量都為0
if (parent.getChildViewHolder(view).getAdapterPosition() == parent.getAdapter().getItemCount() - 1) {
outRect.set(0, 0, 0, 0);
return;
}
if(mOrientation == LinearLayoutManager.VERTICAL){
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
}else{
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0 );
}
}
在getItemOffsets方法中加入判斷是不是最后一項即可
那對于gridLayout的話稍微麻煩一點,底部和右邊都需要我們自己計算上代碼
@Override
@Deprecated
public void getItemOffsets(Rect outRect, int itemPosition,
RecyclerView parent) {
// 四個方向的偏移值
int right = mDivider.getIntrinsicWidth();
int bottom = mDivider.getIntrinsicHeight();
if (isLastRow(itemPosition,parent)) {//最后一行
bottom = 0;
}
if (isLastColum(itemPosition,parent)) {//最后一列
right = 0;
}
outRect.set(0, 0, right, bottom);
}
先計算四個方向的偏移量,判斷最后一行和最后一列做處理
//最后一行
public boolean isLastRow(int itemPosition, RecyclerView parent) {
int spanCount = getSpanCount(parent);
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
int childCount = parent.getAdapter().getItemCount();
int lastRow = childCount % spanCount;
//最后一行的數量小于spanCount
if (lastRow == 0 || lastRow < spanCount) {
return true;
}
}
return false;
}
//最后一列
public boolean isLastColum(int itemPosition, RecyclerView parent) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
int spanCount = getSpanCount(parent);
if ((itemPosition + 1) % spanCount == 0) {
return true;
}
}
return false;
}
private int getSpanCount(RecyclerView parent){
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if(layoutManager instanceof GridLayoutManager){
GridLayoutManager lm = (GridLayoutManager)layoutManager;
int spanCount = lm.getSpanCount();
return spanCount;
}
return 0;
}
處理之后效果如下

最后一個是流式布局很簡單了,這里就不說了,有興趣可以自己玩玩,之后其實我也發(fā)現一個問題,就是當你的item數很少的時候即使你的recyclerview是全屏,最后一行也會繪制分割線,有解決方案的可以和我分享一下哦?。?!
項目鏈接 https://github.com/389987790/RecyclerViewItemDecoration