局限于整體的設計,對于 GridLayoutManager、StaggeredGridLayoutManager 會導致 Item 的寬度(例如:垂直布局)大小不一致,所以不建議再使用了。這里,推薦另外一個庫:https://github.com/Fondesa/RecyclerViewDivider
關于RecyclerView的使用,不是本文介紹的重點,還不清楚的同學可以參考這篇文章: Android RecyclerView 使用完全解析 體驗藝術般的控件。
RecyclerView替代ListView勢在必行,唯一比較遺憾的是官方沒有內置幾個好用的ItemDecoration,這使得很多人覺得使用起來比較麻煩。
有幸站在巨人的肩膀上,github上有大神實現了一個:RecyclerView-FlexibleDivider,經過一番使用,我發現對于GridLayoutManager(不支持SpanSizeLookup)和StaggeredGridLayoutManager的支持并不好。
RecyclerView-FlexibleDivider的實現思路和網上多數方案是一致的:
- 從左到右繪制橫向divider
- 從上至下繪制垂直divider
這種實現的方式弊端很明顯,就是遇到GridLayoutManager(使用了SpanSizeLookup)和StaggeredGridLayoutManager的時候,就沒法正常工作了,因為它們不是“規則”的占據一個“單元”。所以,本文的實現思路是這樣的
- 橫向divider繪制在每個item的下方
- 垂直divider繪制在每個item的右側
在開始之前,我們有必要了解下ItemDecoration的幾個方法
- onDraw(Canvas c, ...):在繪制item views之前繪制decorations,一般我們都用這個方法
- onDrawOver(Canvas c, ...):與上面的方法相反,在繪制item views之后繪制decorations
- getItemOffsets(Rect outRect, ...):為outRect設置left,top和right,bottom,簡單的理解就是設置item view的margin(marginLeft、marginTop、marginRight、marginBottom)
繪制decorations的方法就是使用onDraw方法中的參數Canvas,比如
- 繪制一條線:canvas.drawLine
- 繪制圖片:drawable.draw(canvas)
- 等等...
不管哪種繪制都需要的一個參數,那就是繪制的范圍:Rect;另外針對橫向和垂直divider,getItemOffsets方法中的outRect的設置,應該也是不一樣的。所以抽象出一個父類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);
// 當前item是否需要divider(通常最后一個不需要)
if (!hasDivider(parent, childPosition)) {
continue;
}
// 繪制divider的區域,由子類具體實現
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;
}
}
上面的代碼看注釋應該比較好理解(注意:實際代碼會比文章中的復雜些許)。具體實現的子類有兩個,分別是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));
}
}
這里只貼出了關鍵代碼,因為實際情況,我們可能還需要考慮更多的問題,比如:
- 水平divider的marginLeft和marginRight,還需要判斷item是否是最邊上的,因為中間的divider是不需要margin的
- 垂直divider的marginTop和marginBottom,還需要判斷item是否是最邊上的,因為中間的divider是不需要margin的
- 水平和垂直交叉位置的空白問題
- getReverseLayout為true和false時,不同的處理
為了支持不同的LayoutManager,以上的判斷策略還不盡相同,因為篇幅限制,具體的代碼請查看文章最后的源碼,最后我們來看下最終實現的效果吧~
本文源碼地址:https://github.com/hiphonezhu/RecyclerView-FlexibleDivider(由于改動較大,fork過來的代碼沒有pull給作者,如需使用請下載本文的源碼,而不是通過Gradle引入)