在很早很早之前(long long ago),ListView鼎盛的時代有一個屬性叫作divider。可是在RecycleView上面就是找不到他,那怎麼辦呢???直到後來有一天發現他變身了,變成了ItemDecoration。實在是扯不下去了,直接開始吧! 這篇博客醞釀了好長時間,但願不會讓各位看官失望。bash
瞭解ItemDecoration的原理,本身能夠添加分割線,每一個 ItemView 上疊加一個角標,自定義 RecyclerView 中的頭部或者是粘性頭部。ide
class TextItemDecoration extends RecyclerView.ItemDecoration {
//設置ItemView的內嵌偏移長度(inset)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
// 在子視圖上設置繪製範圍,並繪製內容
// 繪製圖層在ItemView如下,因此若是繪製區域與ItemView區域相重疊,會被遮擋
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
//一樣是繪製內容,但與onDraw()的區別是:繪製在圖層的最上層
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
}
複製代碼
//設置ItemView的內嵌偏移長度(inset)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
//只是添加下面這一行代碼
outRect.set(50, 50, 50, 50);
}
複製代碼
把這個·ItemDecoration·放在下面的RecyclerView上面,
代碼以下: recyclerView2.addItemDecoration(new TextItemDecoration());
運行效果,以下圖所示:源碼分析
下面的代碼都是在RecyclerView中,能夠在RecyclerView裏面找到源碼:優化
/測量全部的子view的寬和高,獲得這個子view的Rect。而後就能獲得這一塊真正的寬和高。
//注意還有padding的值。
public void measureChild(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() + widthUsed, lp.width,
canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
child.measure(widthSpec, heightSpec);
}
}
//獲得每一個子view相應的Rect,
//mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
//上面的代碼就是設置相應的四個值,就和咱們的TextItemDecoration類裏面的代碼對應起來了。
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
}
if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
// changed/invalid items should not be updated until they are rebound.
return lp.mDecorInsets;
}
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
//下面就是詳細的賦值
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}
複製代碼
咱們先來看看咱們自定義的TextItemDecoration類裏面的onDraw()代碼,以下所示:ui
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
複製代碼
很明顯上面傳遞了一個Canvas 參數對象,因此它擁有了繪製的能力。this
注意: 1.getItemOffsets 是針對每個 ItemView,而 onDraw 方法倒是針對 RecyclerView 自己,因此在 onDraw 方法中須要遍歷屏幕上可見的 ItemView,分別獲取它們的位置信息,而後分別的繪製對應的分割線。 2.Itemdecoration的onDraw()繪製會先於ItemView的onDraw()繪製,spa
第二點會出現以下的狀況:3d
出現上面的問題解決方案是getItemOffsets()與onDraw()一塊使用。說的我一愣一愣的,最主要的問題onDrow()的時候,是怎樣獲得相應的點。code
英雄莫怕,請看上面的代碼onDraw(Canvas c, RecyclerView parent, RecyclerView.State state):orm
看第二個參數:RecyclerView parent 這就是咱們的突破點。(這裏有一個疑問點???咱們第4節處理。)
int childCount = parent.getChildCount();
View child = parent.getChildAt(i);
上面的代碼就能解決咱們的問題。
複製代碼
上代碼實戰:要實現的效果以下:
做者:yzzCool 連接:https://www.jianshu.com/p/41ae13016243 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
代碼實現以下:
class TextItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
public TextItemDecoration() {
this.mPaint = new Paint();
mPaint.setColor(Color.YELLOW);
// 畫筆顏色設置爲黃色
}
//設置ItemView的內嵌偏移長度(inset)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(50, 50, 50, 50);
}
// 在子視圖上設置繪製範圍,並繪製內容
// 繪製圖層在ItemView如下,因此若是繪製區域與ItemView區域相重疊,會被遮擋
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// 獲取RecyclerView的Child view的個數
int childCount = parent.getChildCount();
// 遍歷每一個Item,分別獲取它們的位置信息,而後再繪製對應的分割線
for (int i = 0; i < childCount; i++) {
// 獲取每一個Item的位置
final View child = parent.getChildAt(i);
// 設置矩形(分割線)的寬度爲10px
final int mDivider = 10;
// 矩形左上頂點 = (ItemView的左邊界,ItemView的下邊界)
final int left = child.getLeft();
final int top = child.getBottom();
// 矩形右下頂點 = (ItemView的右邊界,矩形的下邊界)
final int right = child.getRight();
final int bottom = top + mDivider;
// 經過Canvas繪製矩形(分割線)
c.drawRect(left, top, right, bottom, mPaint);
}
}
//一樣是繪製內容,但與onDraw()的區別是:繪製在圖層的最上層
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
}
複製代碼
咱們先來看看咱們自定義的TextItemDecoration類裏面的onDrawOver()代碼,以下所示:
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
複製代碼
很明顯onDrawOver裏面的參數和onDraw裏面的參數如出一轍,那還要onDrawOver有什麼用呢???。請看下圖:
最主要的就是紫色區域,onDrawOver的使用方法和onDraw相似。如今咱們在每個條目的右上角加一個圖標。效果以下: 代碼實現以下:class TextItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
private Bitmap bitmap;
public TextItemDecoration() {
this.mPaint = new Paint();
mPaint.setColor(Color.YELLOW);
// 畫筆顏色設置爲黃色
bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.email);
}
//設置ItemView的內嵌偏移長度(inset)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(50, 50, 50, 50);
}
// 在子視圖上設置繪製範圍,並繪製內容
// 繪製圖層在ItemView如下,因此若是繪製區域與ItemView區域相重疊,會被遮擋
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// 獲取RecyclerView的Child view的個數
int childCount = parent.getChildCount();
// 遍歷每一個Item,分別獲取它們的位置信息,而後再繪製對應的分割線
for (int i = 0; i < childCount; i++) {
// 獲取每一個Item的位置
final View child = parent.getChildAt(i);
// 設置矩形(分割線)的寬度爲10px
final int mDivider = 10;
// 矩形左上頂點 = (ItemView的左邊界,ItemView的下邊界)
final int left = child.getLeft();
final int top = child.getBottom();
// 矩形右下頂點 = (ItemView的右邊界,矩形的下邊界)
final int right = child.getRight();
final int bottom = top + mDivider;
// 經過Canvas繪製矩形(分割線)
c.drawRect(left, top, right, bottom, mPaint);
}
}
//一樣是繪製內容,但與onDraw()的區別是:繪製在圖層的最上層
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
final int left = child.getRight() - bitmap.getWidth();
final int top = child.getTop();
c.drawBitmap(bitmap, left, top, mPaint);
}
}
}
複製代碼
具體的思路以下圖所示:
咱們的操做在OutRect裏面處理。下面咱們用假數據處理,頁面中只保留一個RecyclerView。咱們只分析TextItemDecoration裏面的代碼。代碼以下:
class TextItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
public TextItemDecoration() {
this.mPaint = new Paint();
// 畫筆顏色設置爲黃色
mPaint.setColor(Color.YELLOW);
}
//設置ItemView的內嵌偏移長度(inset)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position = parent.getChildAdapterPosition(view);
if (position % 5 == 0) {
outRect.set(0, 50, 0, 0);
}
}
// 在子視圖上設置繪製範圍,並繪製內容
// 繪製圖層在ItemView如下,因此若是繪製區域與ItemView區域相重疊,會被遮擋
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// 獲取RecyclerView的Child view的個數
int childCount = parent.getChildCount();
// 遍歷每一個Item,分別獲取它們的位置信息,而後再繪製對應的分割線
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
int index = parent.getChildAdapterPosition(child);
if (index % 5 == 0) {
int left = 0;
int top = child.getTop() - 50;
int right = child.getRight();
int bottom = child.getTop();
c.drawRect(left, top, right, bottom, mPaint);
}
}
}
//一樣是繪製內容,但與onDraw()的區別是:繪製在圖層的最上層
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
}
複製代碼
效果圖以下:
這距離咱們想要的效果越來躍進了。能不能在黃條在最上面的時候停留呢??還有就是推上去??不出所料全部的操做都是在這裏面了。 下面咱們直接上代碼,思考留給我親愛的讀者:@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
// 獲取RecyclerView的Child view的個數
int childCount = parent.getChildCount();
// 遍歷每一個Item,分別獲取它們的位置信息,而後再繪製對應的分割線
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
int index = parent.getChildAdapterPosition(child);
if (index % 5 == 0) {
int item = (index) / 5;
if (i < 5) {
if (i == 1 && child.getTop() < 100) {
int left = 0;
int top = child.getTop() - 100;
int right = child.getRight();
int bottom = child.getTop() - 50;
c.drawRect(left, top, right, bottom, mPaint);
c.drawText("這是條目" + item + "視圖i是+" + i + "頂部的高低" + child.getTop() + "index++" + index, left, top + 50, textPaint);
} else {
int left = 0;
int top = 0;
int right = child.getRight();
int bottom = 50;
c.drawRect(left, top, right, bottom, mPaint);
if (i == 0) {
c.drawText("這是條目" + (item + 1) + "視圖i是+" + i + "頂部的高低" + child.getTop() + "index++" + index, left, top + 50, textPaint);
} else {
c.drawText("這是條目" + (item) + "視圖i是+" + i + "頂部的高低" + child.getTop() + "index++" + index, left, top + 50, textPaint);
}
}
}
if (i != 0) {
int left = 0;
int top = child.getTop() - 50;
int right = child.getRight();
int bottom = child.getTop();
c.drawRect(left, top, right, bottom, mPaint);
c.drawText("這是條目" + item + "視圖i是+" + i + "頂部的高低" + child.getTop() + "index++" + index, left, top + 50, textPaint);
}
}
}
}
複製代碼
咱們的效果圖以下:
我把要打印的都給小夥伴們打印出來了。具體的優化看本身的需求優化就能夠了。
本篇文章介紹了RecyclerView.ItemDecoration的使用,還有它的原理。其實仍是挺簡單的。我相信簡單的自定義小夥伴應該都會了。