毫無疑問,RecyclerView 是 Android 中最重要的系統元件之一,它的出現就是為了高效代替 ListView 和 GridView。
今天,我們來講講 RecyclerView 中的靜態內部類 ItemDecoration。顧名思義 ItemDecoration 就是 Item 的裝飾,我們可以在 Item 的上下左右新增自定義的裝飾,從而豐富 Item 的 UI 效果。
我們都知道 RecyclerView 裡面就是一個又一個的 Item,但是其實這些 Item 外面包裹著一個矩形,只是我們再使用 RecyclerView 的時候 Left、Right、Top 和 Bottom 預設都是 0,所以我們看不到這些矩形,具體如下圖所示:
我們都知道,使用 RecyclerView 時 ,我們不能像 ListView 那樣通過 setDivider() 的方式來設定分割線,但是系統已經為我們提供了一個 DividerItemDecoration 類來設定分割線,這個類就是繼承 RecyclerView.ItemDecoration,我們來看下原始碼:
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
public static final int VERTICAL = LinearLayout.VERTICAL;
private static final String TAG = "DividerItem";
private static final int[] ATTRS = new int[]{ android.R.attr.listDivider };
... ...
... ...
不設定分割線,效果如下所示:
DividerItemDecoration 的使用非常簡單,只需新增下面程式碼即可:
DividerItemDecoration decoration = new DividerItemDecoration(this,DividerItemDecoration.VERTICAL);
recyclerView.addItemDecoration(decoration);
具體效果如下所示:
一般情況下以上 RecyclerView 的基本用法便可以實現絕大多數需求,但是某些場景下卻遠遠不夠,特別是需要實現比較複雜的 UI 效果的時候,所以這時候就需要利用 ItemDecoration,接下來我們就學習一下 ItemDecoration 的具體使用。
首先,我們來看一下 ItemDecoration 的原始碼,這裡的原始碼已經把註釋和三個已經被棄用的方法去掉了,具體如下所示:
public abstract static class ItemDecoration {
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
onDraw(c, parent);
}
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
onDrawOver(c, parent);
}
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent);
}
}
從原始碼中我們可以看出 ItemDecoration 類中就只有三個方法,分別是 onDraw()、onDrawOver() 和 getItemOffsets(),具體作用如下:
getItemOffsets() 的作用就是設定 Item 的內嵌偏移長度,從上面我們已經知道,RecyclerView 的 Item 外面是包裹著一個矩形的,這個方法就是用來設定矩形與 Item 之間的間隔的。
具體使用:
public class RectItemDecoration extends RecyclerView.ItemDecoration {
// 引數說明:
// 1. outRect:全為 0 的 Rect(包括著Item)
// 2. view:RecyclerView 中的 檢視 Item
// 3. parent:RecyclerView 本身
// 4. state:狀態
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// 4個引數分別對應左(Left)、上(Top)、右(Right)、下(Bottom)
// 上述語句代表:左、右、上和下偏移長度=100px
outRect.set(100, 100, 100,100);
}
}
//用自定義分割線類設定分割線
recyclerView.addItemDecoration(new RectItemDecoration());
效果如下所示:(上下左右都設定了 100px 的偏移量)
onDraw() 的作用是通過 Canvas 物件繪製內容,需要注意的是 onDraw() 繪製會先於 Item 的繪製,所以如果在 onDraw() 中繪製的內容在 Item 邊界內,就會被 Item 遮擋住,所以 onDraw() 一般會和 getItemOffsets() 結合一起使用,即在矩形與 Item 的間隔區域內繪製內容。
範例1:在左側間隔區域繪製一個空心圓,並在下側間隔區域繪製一個 20px 的紅色分割線。
我們先來看效果:
具體程式碼如下所示:
public class CircleRectDecoration extends RecyclerView.ItemDecoration {
private Paint colorPaint;
private Paint dividerPaint;
// 初始化
public RectItemDecoration() {
colorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
colorPaint.setColor(Color.BLUE);
colorPaint.setStyle(Paint.Style.STROKE);
colorPaint.setStrokeWidth(5);
dividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
dividerPaint.setColor(Color.RED);
dividerPaint.setStyle(Paint.Style.FILL);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.set(100, 0, 0, 20);
}
@Override
public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(canvas, parent, state);
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
// 需要遍歷每個 Item 進行繪製
for (int i = 0; i < parent.getChildCount(); i++) {
View childView = parent.getChildAt(i);
int leftItemWidth = layoutManager.getLeftDecorationWidth(childView);
int bottomItemHeight = layoutManager.getBottomDecorationHeight(childView);
int left = leftItemWidth / 2;
canvas.drawCircle(left, childView.getTop() + childView.getHeight() / 2, 20, colorPaint);
// getItemOffsets() 中的設定的是 bottom = 20px;所以在 drawRect 時,top 為 childView.getBottom, bottom 為top + bottomDecorationHeight
canvas.drawRect(new Rect(leftItemWidth, childView.getBottom(),
childView.getWidth() + leftItemWidth,
childView.getBottom() + bottomItemHeight), dividerPaint);
}
}
getItemOffsets() 是針對每個 item 都會執行一次,也就是說每個 item 的 outRect 可以設定為不同值,但是 onDraw() 是針對 ItemDecoration 的,不是針對 item 的,只執行一次。所以我們在 onDraw() 中繪製的時候,需要遍歷每個 item 進行繪製。
onDrawOver() 的作用與 onDraw() 類似,都是通過 Canvas 物件繪製內容,但與onDraw() 的區別是:onDrawOver() 繪製是後於 onDraw() 的,所以繪製內容是不會被 Item 所遮擋的,反而 Item 會被 onDrawOver() 繪製的內容所遮擋。
範例2:在範例1的基礎上,繪製一個角標到 Item 上。
我們先來看效果:
具體程式碼如下所示:
@Override
public void onDrawOver(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(canvas, parent, state);
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
int index = parent.getChildAdapterPosition(view);
float top = view.getTop();
if (index < 3) {
canvas.drawBitmap(mIcon, 120, top, flagPaint);
}
}
}
自定義 ItemDecoration 通常要根據需要,複寫它的 3 個方法。
ItemDecoration 的作用遠不止以上這麼一點,我這裡只是簡單的介紹了下 ItemDecoration 中三個方法的使用,從而使得 RecyclerView 中的 Item 的 UI 更加豐富。