RecyclerView
源碼分析系列文章已經告一個段落了,從今天開始,我將續源碼分析系列的文章,補充RecyclerView
其餘內容。這個系列的文章沒有固定性,多是源碼分析,也有多是踩坑經驗,還有多是一些自定義操做。bash
ItemDecoration
做爲RecyclerView
4大相關組成部分之一,其重要性就不用我來介紹。同時,相信你們都比較熟悉它,自定義分割線就是用它來實現的。可是,你們有沒有想過,ItemDecoration
是怎麼實現分割線的?它內部的原理又是什麼呢?這就是本文須要解決的問題。本文打算從以下幾個方面來介紹ItemDecoration
:ide
- 自定義
ItemDecoration
。ItemDecoration
原理解析。
在閱讀本文以前,建議已經對RecyclerView
的三大流程有必定的瞭解,有興趣的同窗能夠參考個人文章:RecyclerView 源碼分析(一) - RecyclerView的三大流程。源碼分析
在介紹本文的內容以前,我先來對ItemDecoration
作一個小小的概述,咱們在自定義ItemDecoration
時,都知道重寫其中的兩個方法:onDraw
和getItemOffsets
兩個。這其中,咱們使用onDraw
方法用來繪製分割線,getItemOffsets
方法設置ItemView
的間距。佈局
這裏,我結合onDrawOver
方法來解釋一下onDraw
方法的真正做用。首先,到如今爲止,有可能還認爲onDraw
方法是用來分割線。我在這裏統一解釋一下:this
方法名 | 做用 |
---|---|
onDraw | ItemView 繪製完成以後,會調用此方法繪製。也就是說,onDraw 方法繪製的內容在ItemView 的上面。從這裏,咱們能夠看出來,onDraw 方法的做用不只僅是繪製分割線。 |
onDrawOver | onDraw 繪製完成以後,會調用此方法進行繪製。也就是說,onDraw 方法繪製的內容在onDraw 方法繪製的內容上面。 |
getItemOffsets | 設置每一個ItemView 上下左右的間距。 |
而分割線是怎麼繪製出來的呢?由於getItemOffsets
給每一個ItemView
設置間距,而onDraw
剛好在這個間距裏面繪製內容,從而就造成了分割線。實際上,onDraw
繪製的範圍不只僅是ItemView
的間距,而是能夠在整個RecyclerView
內部繪製;同時onDraw
方法不是繪製分割線的惟一方法,其實onDrawOver
方法也是能夠繪製分割線。只不過是,咱們一般這樣來理解,onDraw
用來會分割線部分,而onDrawOver
方法用來繪製最上層的內容,好比說陰影部分。spa
既然手把手教你們認識ItemDecoration
,自定義一個ItemDecoration
天然不能少。咱們先來看一下效果: 3d
ItemView
有一個黃色的分割線,其次我在每一個
ItemView
上面繪製了一個半透明的陰影。
而後,咱們再來看看實現:code
public class CustomItemDecoration extends RecyclerView.ItemDecoration {
private final Drawable mDividerDrawable;
private final int mDivider;
private final Drawable mShadowDrawable;
public CustomItemDecoration(Context context) {
mDividerDrawable = context.getResources().getDrawable(R.drawable.divider);
mShadowDrawable = context.getResources().getDrawable(R.drawable.shadow);
mDivider = mDividerDrawable.getIntrinsicHeight();
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
// 繪製分割線
final int left = parent.getPaddingLeft();
final int right = parent.getMeasuredWidth() - parent.getPaddingRight();
final int count = parent.getChildCount();
for (int i = 0; i < count; i++) {
View child = parent.getChildAt(i);
final RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int top = child.getBottom() + layoutParams.bottomMargin;
final int bottom = top + mDivider;
mDividerDrawable.setBounds(left, top, right, bottom);
mDividerDrawable.draw(c);
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
// 繪製陰影
final int left = parent.getPaddingLeft();
final int right = parent.getMeasuredWidth() - parent.getPaddingRight();
final int count = parent.getChildCount();
for (int i = 0; i < count; i++) {
View child = parent.getChildAt(i);
final int top = child.getTop();
final int bottom = child.getBottom();
mShadowDrawable.setBounds(left, top, right, bottom);
mShadowDrawable.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// 設置間距
int layoutPosition = parent.getChildViewHolder(view).getAdapterPosition();
if (layoutPosition == parent.getAdapter().getItemCount() - 1) {
outRect.set(0, 0, 0, 0);
} else {
outRect.set(0, 0, 0, mDivider);
}
}
}
複製代碼
整個ItemDecoration
的實現是很是簡單,從上面的代碼中,咱們能夠獲得:cdn
- 經過
onDraw
方法來繪製分割線,這其中,計算了left、top、right和bottom,而這個範圍剛好就是ItenView
的間距範圍。- 經過
onDrawOver
方法來繪製陰影,繪製的範圍剛好就在ItemView
的範圍。- 經過
getItemOffsets
方法來設置ItemView
的間距。
自定義ItemDecoration
就是這麼的簡單,這裏就很少餘的介紹了,接下來咱們來看一下ItemDecoration
是怎麼實現分割線的。對象
咱們要向瞭解ItemDecoration
的原理,其實從下面兩個方面來了解就OK了:
ItemDecoration
是怎麼實現ItemView
間距的。ItemDecoration
是進行繪製的。
咱們想要知道上面兩個問題的答案,就必須從RecyclerView
的源碼入手。咱們先來看看間距部分。
間距的實現,咱們主要從兩個方面去尋找答案:1.ItemView
的測量;2.ItemView
的佈局。
首先咱們來看一下ItemView
的測量,實現過程在LayoutManager
的measureChildWithMargins
方法:
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight()
+ lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom()
+ lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
child.measure(widthSpec, heightSpec);
}
}
複製代碼
從上面的代碼中,咱們能夠看到,調用了RecyclerView
的getItemDecorInsetsForChild
方法來獲取全部ItemDecoration
設置的間距之和。這裏我不對getItemDecorInsetsForChild
方法展開,由於很是的簡單,有興趣的同窗能夠看一下,會重點分析這個方法。得到間距之和以後,保存在一個Rect
對象裏面,而後再測量ItemView
時,將這部分的間距之和計算在padding以內,因此在測量ItemView
,就考慮過ItemDecoration
設置的間距。
而後,咱們在來看看佈局的過程,代碼主要體如今LayoutManager
的layoutDecoratedWithMargins
方法裏面:
public void layoutDecoratedWithMargins(View child, int left, int top, int right,
int bottom) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = lp.mDecorInsets;
child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
right - insets.right - lp.rightMargin,
bottom - insets.bottom - lp.bottomMargin);
}
複製代碼
從上面的代碼中,咱們發現,在佈局ItemView
時,考慮到ItemDecoration
設置的間距。因此到這裏,咱們已經知道ItemDecoration
是怎麼實現ItemView
的間距的,那是由於在測量時和佈局時,都會考慮到ItemDecoarion
設置的間距。
ItemDecoration
的繪製過程主要體如今onDraw
方法和onDrawOver
方法裏面。而這兩個方法都是在RecyclerView
的draw過程被回調的,咱們來看看代碼:
@Override
public void draw(Canvas c) {
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
// ······
}
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
複製代碼
熟悉View
的三大流程的同窗應該都知道,draw
方法調用super.draw(c)
會執行到onDraw
方法。而在onDraw
方法裏面,咱們發現先是執行super.onDraw(c)
,這個方法主要是繪製ItemView
,而後就是調用ItemDecoration
的onDraw
方法,最後在draw
方法裏面調用ItemDecoration
的onDrawOver
方法。從這裏,咱們就知道,爲何ItemView
的繪製在最下面,onDraw
繪製的內容在中間,而onDrawOver
繪製的內容在最上面,由於他們方法執行有順序。
總的來講,ItemDecoration
是很是的簡單,在這裏,我對此作一個簡單的總結。
- 咱們使用
ItemDecoration
,只須要關注它的三個方法便可,分別是:getItemOffsets
、onDraw
和onDrawOver
方法。其中getItemOffsets
方法主要是給每一個ItemView
設置間距,onDraw
方法和onDrawOver
方法都是用來繪製的,其中onDraw
先繪製,其次纔是onDrawOver
方法。不過兩個方法繪製的內容都是在ItemView
的上面。ItemDecoration
之因此可以實現間距,是由於在測量ItemView
和繪製ItemView
時,都考慮到ItemDecoration
的存在。