局限于整體的設(shè)計(jì),對(duì)于 GridLayoutManager、StaggeredGridLayoutManager 會(huì)導(dǎo)致 Item 的寬度(例如:垂直布局)大小不一致,所以不建議再使用了。這里,推薦另外一個(gè)庫(kù):https://github.com/Fondesa/RecyclerViewDivider
關(guān)于RecyclerView的使用,不是本文介紹的重點(diǎn),還不清楚的同學(xué)可以參考這篇文章: Android RecyclerView 使用完全解析 體驗(yàn)藝術(shù)般的控件。
RecyclerView替代ListView勢(shì)在必行,唯一比較遺憾的是官方?jīng)]有內(nèi)置幾個(gè)好用的ItemDecoration,這使得很多人覺得使用起來(lái)比較麻煩。
有幸站在巨人的肩膀上,github上有大神實(shí)現(xiàn)了一個(gè):RecyclerView-FlexibleDivider,經(jīng)過一番使用,我發(fā)現(xiàn)對(duì)于GridLayoutManager(不支持SpanSizeLookup)和StaggeredGridLayoutManager的支持并不好。
RecyclerView-FlexibleDivider的實(shí)現(xiàn)思路和網(wǎng)上多數(shù)方案是一致的:
- 從左到右繪制橫向divider
- 從上至下繪制垂直divider
這種實(shí)現(xiàn)的方式弊端很明顯,就是遇到GridLayoutManager(使用了SpanSizeLookup)和StaggeredGridLayoutManager的時(shí)候,就沒法正常工作了,因?yàn)樗鼈儾皇恰耙?guī)則”的占據(jù)一個(gè)“單元”。所以,本文的實(shí)現(xiàn)思路是這樣的
- 橫向divider繪制在每個(gè)item的下方
- 垂直divider繪制在每個(gè)item的右側(cè)
在開始之前,我們有必要了解下ItemDecoration的幾個(gè)方法
- onDraw(Canvas c, ...):在繪制item views之前繪制decorations,一般我們都用這個(gè)方法
- onDrawOver(Canvas c, ...):與上面的方法相反,在繪制item views之后繪制decorations
- getItemOffsets(Rect outRect, ...):為outRect設(shè)置left,top和right,bottom,簡(jiǎn)單的理解就是設(shè)置item view的margin(marginLeft、marginTop、marginRight、marginBottom)
繪制decorations的方法就是使用onDraw方法中的參數(shù)Canvas,比如
- 繪制一條線:canvas.drawLine
- 繪制圖片:drawable.draw(canvas)
- 等等...
不管哪種繪制都需要的一個(gè)參數(shù),那就是繪制的范圍:Rect;另外針對(duì)橫向和垂直divider,getItemOffsets方法中的outRect的設(shè)置,應(yīng)該也是不一樣的。所以抽象出一個(gè)父類FlexibleDividerDecoration,繼承自RecyclerView.ItemDecoration。
FlexibleDividerDecoration
public abstract class FlexibleDividerDecoration extends RecyclerView.ItemDecoration {
protected abstract Rect getDividerBound(int position, RecyclerView parent, View child);
protected abstract void setItemOffsets(Rect outRect, int position, RecyclerView parent);
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int validChildCount = parent.getChildCount();
for (int i = 0; i < validChildCount; i++) {
View child = parent.getChildAt(i);
int childPosition = parent.getChildAdapterPosition(child);
// 當(dāng)前item是否需要divider(通常最后一個(gè)不需要)
if (!hasDivider(parent, childPosition)) {
continue;
}
// 繪制divider的區(qū)域,由子類具體實(shí)現(xiàn)
Rect bounds = getDividerBound(childPosition, parent, child);
switch (mDividerType) {
case DRAWABLE: // 圖片類型的divider
Drawable drawable = mDrawableProvider.drawableProvider(childPosition, parent);
drawable.setBounds(bounds);
drawable.draw(c);
break;
}
}
}
@Override
public void getItemOffsets(Rect rect, View v, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(v);
if (!hasDivider(parent, position)) {
return;
}
setItemOffsets(rect, position, parent);
}
/**
* Whether child has divider
*
* @param parent
* @param childPosition
* @return true if child has divider
*/
public boolean hasDivider(RecyclerView parent, int childPosition) {
if (this instanceof VerticalDividerItemDecoration) { // 垂直方向是否有divider
return hasVerticalDivider(parent, childPosition);
} else if (this instanceof HorizontalDividerItemDecoration) { // 水平方向是否有divider
return hasHorizontalDivider(parent, childPosition);
}
return false;
}
}
上面的代碼看注釋應(yīng)該比較好理解(注意:實(shí)際代碼會(huì)比文章中的復(fù)雜些許)。具體實(shí)現(xiàn)的子類有兩個(gè),分別是HorizontalDividerItemDecoration和VerticalDividerItemDecoration。
我們看下HorizontalDividerItemDecoration:
public class HorizontalDividerItemDecoration extends FlexibleDividerDecoration {
protected HorizontalDividerItemDecoration(Builder builder) {
super(builder);
}
@Override
protected Rect getDividerBound(int position, RecyclerView parent, View child) {
Rect bounds = new Rect(0, 0, 0, 0);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
bounds.left = child.getLeft();
bounds.right = child.getRight();
// divider大小
int dividerSize = getDividerSize(position, parent);
bounds.top = child.getBottom() + params.bottomMargin;
bounds.bottom = bounds.top + dividerSize;
return bounds;
}
@Override
protected void setItemOffsets(Rect outRect, int position, RecyclerView parent) {
outRect.set(0, 0, 0, getDividerSize(position, parent));
}
}
這里只貼出了關(guān)鍵代碼,因?yàn)閷?shí)際情況,我們可能還需要考慮更多的問題,比如:
- 水平divider的marginLeft和marginRight,還需要判斷item是否是最邊上的,因?yàn)橹虚g的divider是不需要margin的
- 垂直divider的marginTop和marginBottom,還需要判斷item是否是最邊上的,因?yàn)橹虚g的divider是不需要margin的
- 水平和垂直交叉位置的空白問題
- getReverseLayout為true和false時(shí),不同的處理
為了支持不同的LayoutManager,以上的判斷策略還不盡相同,因?yàn)槠拗疲唧w的代碼請(qǐng)查看文章最后的源碼,最后我們來(lái)看下最終實(shí)現(xiàn)的效果吧~


本文源碼地址:https://github.com/hiphonezhu/RecyclerView-FlexibleDivider(由于改動(dòng)較大,fork過來(lái)的代碼沒有pull給作者,如需使用請(qǐng)下載本文的源碼,而不是通過Gradle引入)