一、概述
通過ItemDecoration
,可以給RecyclerView
或者RecyclerView
中的每個Item
新增額外的裝飾效果,最常用的就是用來為Item
之間新增分割線,今天,我們就來一起學習有關的知識:
API
DividerItemDecoration
解析- 自定義
ItemDecoration
二、API
介紹
當我們實現自己的ItemDecoration
時,需要繼承於ItemDecoration
,並根據需要實現以下三個方法:
2.1 public void onDraw(Canvas c, RecyclerView parent, State state)
canvas
:RecyclerView
的canvas
parent
:RecyclerView
例項State
:RecyclerView
當前的狀態,值包括START/LAYOUT/ANIMATION
。
所有在這個方法中的繪製操作,將會在itemViews
被繪製之前執行,因此,它會顯示在itemView
之下。
2.2 public void onDrawOver(Canvas c, RecyclerView parent, State state)
和2.1
方法類似,區別在於它繪製在itemViews
之上。
2.3 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
通過outRect
,可以設定item
之間的間隔,間隔區域的大小就是outRect
所指定的範圍,view
就是對應位置的itemView
,其它的引數解釋和上面相同。
三、DividerItemDecoration
解析
3.1 使用方法
上面我們解釋了需要重寫的方法以及其中引數的含義,下面,我們通過官方自帶的DividerItemDecoration
,來進一步加深對這些方法的認識。
DividerItemDecoration
是為LinearLayoutManager
提供的分割線,在建立它的時候,需要指定ORIENTATION
,這個方向應當和LinearLayoutManager
的方向相同。
private void init() {
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content);
mTitles = new ArrayList<>();
for (int i = 0; i < 20; i++) {
mTitles.add(String.valueOf(i));
}
BaseAdapter baseAdapter = new BaseAdapter(mTitles);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
recyclerView.setAdapter(baseAdapter);
}
複製程式碼
最終展示的效果為:
3.2 原始碼解析
3.2.1 繪製
DividerItemDecoration
重寫了基類當中的onDraw
方法,也就是說這個分割線是在itemView
之前繪製的:
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (parent.getLayoutManager() == null) {
return;
}
if (mOrientation == VERTICAL) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
複製程式碼
我們先看縱向排列的RecyclerView
分割線:
@SuppressLint("NewApi")
private void drawVertical(Canvas canvas, RecyclerView parent) {
//首先儲存畫布
canvas.save();
final int left;
final int right;
//確定左右邊界的範圍,如果RecyclerView不允許子View繪製在Padding內,那麼這個範圍為去掉Padding後的範圍
if (parent.getClipToPadding()) {
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
canvas.clipRect(left, parent.getPaddingTop(), right,
parent.getHeight() - parent.getPaddingBottom());
} else {
left = 0;
right = parent.getWidth();
}
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
//獲得itemView的範圍,這個範圍包括了margin和offset,它們被儲存在mBounds當中
parent.getDecoratedBoundsWithMargins(child, mBounds);
//需要考慮translationY和translationY
final int bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child));
//由於是垂直排列的,因此上邊界等於下邊界減去分割線的高度.
final int top = bottom - mDivider.getIntrinsicHeight();
//設定divider和範圍
mDivider.setBounds(left, top, right, bottom);
//繪製.
mDivider.draw(canvas);
}
//回覆畫布.
canvas.restore();
}
複製程式碼
整個過程分為三步:
- 確定子
View
在RecyclerView
中的繪製範圍 - 確定每個子
View
的範圍 - 確定
mDivider
的繪製範圍
下圖就是最終計算的結果:
橫向排列的RecyclerView
列表和上面的原理是相同的,區別就在於計算mDivider.setBounds
的計算:
//....
parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds);
final int right = mBounds.right + Math.round(ViewCompat.getTranslationX(child));
final int left = right - mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
//..
複製程式碼
3.2.2 邊界處理
從上面的分析可以知道,如果將divider
直接繪製在itemView
的範圍內,那麼由於我們是先繪製divider
,再繪製itemView
的內容的,那麼它就會被覆蓋,因此,通過重寫getItemOffsets
,通過其中的outRect
來指定留出的空隙:
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
if (mOrientation == VERTICAL) {
//如果是縱向排列,那麼要在itemView的下方留出一個下邊界
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
//如果是橫向排列,那麼要在itemView的右方留出一個右邊界
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
複製程式碼
四、自定義ItemDecoration
下面,我們參考上面的寫法,寫一個簡單的GridLayoutManager
的分割線:
public class GridDividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[] { android.R.attr.listDivider };
private Drawable mDivider;
private final Rect mBounds = new Rect();
public GridDividerItemDecoration(Context context) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
}
public void setDrawable(@NonNull Drawable drawable) {
mDivider = drawable;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
drawDivider(c, parent);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
}
private void drawDivider(Canvas canvas, RecyclerView parent) {
canvas.save();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = parent.getChildAt(i);
parent.getDecoratedBoundsWithMargins(view, mBounds);
mDivider.setBounds(mBounds.right - mDivider.getIntrinsicWidth(), mBounds.top, mBounds.right, mBounds.bottom);
mDivider.draw(canvas);
mDivider.setBounds(mBounds.left, mBounds.bottom - mDivider.getIntrinsicHeight(), mBounds.right , mBounds.bottom);
mDivider.draw(canvas);
}
canvas.restore();
}
}
複製程式碼
最終的效果為:
這裡我們為了演示方便,沒有考慮最後一列或者最後一行沒有分割線的情況,這篇文章寫的比較好:Android RecyclerView 使用完全解析 體驗藝術般的控制元件。五、總結
ItemDecoration
的使用並不難,大多數情況下就只需要重寫onDraw
和onDrawOver
中的一個;如果需要在Item
之間新增間隔,那麼要重寫getItemOffsets
並理解outRect
的含義,假如不需要新增間隔,那麼不需要重寫該方法。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/