LowRecyclerView系列之:RecyclerView之新增分割線
關於Recyclerview新增分割線的部落格很多,但是很多部落格關於分割線都是講怎麼實現的,沒有講原理,而鴻洋大神的關於分割線的文章介紹的很詳細,但是感覺有些地方仍然不是太清楚。這篇文章以初學者的角度向大家詳細介紹了分割線新增的原理,詳細的解讀原始碼,力求讓大家能夠輕鬆理解dividers如何新增。同時解析LowRecyclerView的通用分割線的實現。
原始碼分析
使用過RecyclerView的小夥伴們對recyclerView.addItemDecoration()這個方法很熟悉,沒錯我們都是通過這個方法來新增分割線的。我們翻閱RecyclerView可以瞭解他,原始碼如下:
/**
* Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
* affect both measurement and drawing of individual item views.
*
* <p>Item decorations are ordered. Decorations placed earlier in the list will
* be run/queried/drawn first for their effects on item views. Padding added to views
* will be nested; a padding added by an earlier decoration will mean further
* item decorations in the list will be asked to draw/pad within the previous decoration's
* given area.</p>
*
* @param decor Decoration to add
*/
public void addItemDecoration(ItemDecoration decor) {
}
第一段註釋的大致意思是說addItemDecoration()這個方法主要是給RecyclerView新增一個ItemDecoration(中文譯作Item裝飾,我們常說的分割線就是用來裝飾我們的Item),Item decorations 他可以影響單個Item檢視的measurement(度量)和drawing(繪圖),仔細分析這句話的意思就是說我們的分割線是通過改變Item來繪製一定尺寸的分割線。
第二段主要是說:Item Decorations是有序的。在list將要被執行/查詢/繪製之前,Decoration會首先改變list的item檢視。Decoration將會以巢狀的形式填充到item檢視中去;在list被繪製的時候先去將Decoration的空間偏移出來。
通過上面兩段我們大致知道Decoration是通過在item裡面另外開闢的空間進行繪製的,那麼我們怎麼才能對分割線的空間大小進行控制呢,在addItemDecoration()方法中有一個ItemDecoration引數,我們接檢視他的原始碼,如下:
/**
* An ItemDecoration allows the application to add a special drawing and layout offset
* to specific item views from the adapter's data set. This can be useful for drawing dividers
* between items, highlights, visual grouping boundaries and more.
*
* <p>All ItemDecorations are drawn in the order they were added, before the item
* views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()}
* and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView,
* RecyclerView.State)}.</p>
*/
public static abstract class ItemDecoration
一個ItemDecoration允許一個應用從adapter的adta的集合中添一個特殊的drawing和佈局偏移到具體的item檢視,他能夠被用於給item,highlights,視覺化分組邊界等繪製dividers(分割線),翻譯的有點不通順,簡單的說ItemDecoration可以用於繪製item的dividers。
接下來我們來看一下ItemDecoration中的方法,如下:
可以看到共有6個方法其中三個過時的方法,我們不再看,剩下三個方法,我們先來看getItemOffsets()方法:
/**
* 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);
}
第一段的意思是:這個方法是用來檢索得到的Item的偏移量,outRect是用來指定Item檢視的偏移或者margin的畫素,這個方法預設實現是outRect將偏移量或者margin設定為0。
第二段的意思是說:如果想ItemDecoration不影響Item檢視的位置和大小,應該在outRect方法返回之前設定他的上、下、左、右四個引數的偏移量。
第三段的意思是說如果你想得到當前view在adapter裡的data集合的position,請用recyclerView的例項呼叫getChildAdapterPosition(View view)方法。
通過這三段註釋我們已經能夠清晰地瞭解它的使用了吧,哈哈,不得不說研究原始碼真的很有用,不說廢話繼續看另一個方法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);
}
這個方法將分割線繪製到recyclerView的畫布上,這個方法會將分割線在item繪製之前優先繪製出來,分割線會被繪製到item的下面。
我們再來看最後一個方法:
/**
* Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
* Any content drawn by this method will be drawn after the item views are drawn
* and will thus appear over 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 onDrawOver(Canvas c, RecyclerView parent, State state) {
onDrawOver(c, parent);
}
我們可以看到這個方法跟onDraw方法解釋大致相同,唯一的區別就是onDraw是優先於Item繪製,而onDrawOver是在item繪製之後進行繪製的,onDraw繪製的裝飾物是在item之下而onDrawOver是在item之上繪製的。可能對於對View研究很透徹的大牛來說,很容易理解,對於剛接觸這方面的小夥伴們來說就非常的困難,下面將詳細說名這兩個方法的區別。
首先來說再Item之前繪製,我們會優先繪製分割線,由於Item的高度和寬度是固定的而我們的分割線是在item的基礎上進行偏移的,我們可以在偏移的位置先將分割線繪製出來,然後再繪製Item,而onDrawOver是先繪製Item的,如果我們先繪製出了item那麼item的高度就固定了,我們在item的上面繪製分割線的時候,分割線就會在item之上進行繪製,繪製的分割線就會把我們的item給覆蓋,這樣帶來的體驗非常不好。
如何新增分割線
我們通過對ItemDecoration詳細的分析,大家應該能夠理解新增分割線的原理了吧,簡單的概述起來就是通過getItemOffsets()方法在讓Item進行偏移或者新增margin,然後呼叫onDraw在我偏移的地方繪製我們的裝飾物。
我們以GridView新增分割線為例,首先建立一個類去繼承ItemDecoration,在構造方法中傳入分割線的顏色,寬度和gridview的列數,如:
/**
* 自定義分割線(高和顏色)
* @param dividerHeight 分割線高度
* @param dividerColor 分割線顏色
* @param nunColumns RecyclerView的行數或列數
*/
public CommonRecyclerDivider(int dividerHeight, int dividerColor, int nunColumns) {
mDividerHeight = dividerHeight;
mNunColumns = nunColumns;
mAdapter = adapter;
// 初始化畫筆
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(dividerColor);
//填充
mPaint.setStyle(Paint.Style.FILL);
}
在該方法中我們先初始化畫筆的顏色,然後從寫getItemOffsets()方法,分別將item底部和右側添進行偏移,然後判斷最後一列右側不偏移,程式碼如下:
/**
* 設定分割線
* 先呼叫此方法(設定完後在執行onDraw方法)
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//獲取每個item檢視的posiotion
int itemPosition = parent.getChildAdapterPosition(view);
if(itemPosition%mNunColumns==mNunColumns-1){
outRect.set(0, 0, 0, mDividerHeight);
}else{
outRect.set(0, 0, mDividerHeight, mDividerHeight);
}
}
然後我們在onDraw方法裡進行分割線的繪製,程式碼如下:
/**
* 繪製分割線
* getItemOffsets方法呼叫後會呼叫此方法
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
//繪製底部
drawButtom(c, parent);
//繪製右側
drawRigth(c, parent);
}
/**
* 畫divider (底部)
* @param c
* @param parent
*/
private void drawButtom(Canvas c, RecyclerView parent) {
//獲取Item的數量
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
//獲取對應poisition對應的item
final View child = parent.getChildAt(i);
int left = child.getLeft();
int right = child.getRight() + mDividerHeight;
int top = child.getBottom();
int bottom = top + mDividerHeight;
c.drawRect(left, top, right, bottom, mPaint);
}
}
/**
* 畫divider(右邊)
*
* @param c
* @param parent
*/
private void drawRigth(Canvas c, RecyclerView parent) {
//獲取Item的數量
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
//獲取對應poisition對應的item
final View child = parent.getChildAt(i);
int top = child.getTop();
int left = child.getRight();
int bottom = child.getBottom();
int right = left + mDividerHeight;
c.drawRect(left, top, right, bottom, mPaint);
}
}
如果上面的程式碼你看不太懂就接和下面的這幅圖去理解:
通過這張圖我想你應該能夠理解分割線的繪製了。其他的不管是listview還是瀑布流繪製方法和gridView繪製方法一樣,但是由於瀑布流的高度是變化的,所以無法判斷哪個Item是最後一列,所以瀑布流新增分割線的時候最後一列的右側是繪製出來的。
封裝通用的分割線
關於通用的分割線原理基本上跟上面講述的原理基本相同,我們需要根據recyclerView的顯示型別進行判斷即可,小夥伴們可以參照上述方法進行封裝,也可以使用我的開源專案LowRecyclerView中的CommonRecyclerDivider,只需要簡單的修改即可使用。如果文章有錯誤,歡迎大家指正!!!