RecyclerView
在 Android
開發中很是經常使用,若是能結合ItemDecoration
類使用,那麼將大大提升RecyclerView
的表現效果ItemDecoration
類,包括ItemDecoration
類簡介、使用方法 & 實例講解,但願大家會喜歡。
ItemDecoration
類屬於RecyclerView
的高級用法,閱讀本文前請先學習RecyclerView
的使用:Android開發:ListView、AdapterView、RecyclerView全面解析java
RecyclerView
類的靜態內部類git
###1.2 做用 向 RecyclerView
中的 ItemView
添加裝飾github
即繪製更多內容,豐富
ItemView
的UI
效果bash
ItemDecoration
類中僅有3個方法,具體以下:微信
public class TestDividerItemDecoration extends RecyclerView.ItemDecoration {
// 方法1:getItemOffsets()
// 做用:設置ItemView的內嵌偏移長度(inset)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
...
}
// 方法2:onDraw()
// 做用:在子視圖上設置繪製範圍,並繪製內容
// 相似平時自定義View時寫onDraw()同樣
// 繪製圖層在ItemView如下,因此若是繪製區域與ItemView區域相重疊,會被遮擋
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
...
}
// 方法3:onDrawOver()
// 做用:一樣是繪製內容,但與onDraw()的區別是:繪製在圖層的最上層
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
...
}
複製代碼
下面,我將詳細介紹這3個方法。ide
設置ItemView的內嵌偏移長度(inset)函數
RecyclerView
中的 ItemView 外面會包裹着一個矩形(outRect
)outRect
)與 ItemView
的間隔outRect
中的 top、left、right、bottom
參數 控制
top、left、right、bottom
參數默認 = 0,即矩形和Item重疊,因此看起來矩形就消失了源碼分析
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// 參數說明:
// 1. outRect:全爲 0 的 Rect(包括着Item)
// 2. view:RecyclerView 中的 視圖Item
// 3. parent:RecyclerView 自己
// 4. state:狀態
outRect.set(50, 0, 0,50);
// 4個參數分別對應左(Left)、上(Top)、右(Right)、下(Bottom)
// 上述語句表明:左&下偏移長度=50px,右 & 上 偏移長度 = 0
...
}
複製代碼
RecyclerView
本質上是一個自定義ViewGroup
,子視圖child
= 每一個ItemView
LayoutManager
測量並佈局 ItemView
public void measureChild(View child, int widthUsed, int heightUsed) {
// 參數說明:
// 1. child:要測量的子view(ItemView)
// 2. widthUsed: 一個ItemView的全部ItemDecoration佔用的寬度(px)
// 3. heightUsed:一個ItemView的全部ItemDecoration佔用的高度(px)
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
// 累加當前ItemDecoration 4個屬性值->>分析1
widthUsed += insets.left + insets.right;
// 計算每一個ItemView的全部ItemDecoration的寬度
heightUsed += insets.top + insets.bottom;
// 計算每一個ItemView的全部ItemDecoration的高度
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
canScrollHorizontally());
// 測量child view(ItemView)的寬度
// 第三個參數設置 child view 的 padding,即ItemView的Padding
// 而該參數把 insets 的值算進去,因此insets 值影響了每一個 ItemView 的 padding值
// 高度同上
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
child.measure(widthSpec, heightSpec);
}
}
// 分析完畢,請跳出
<-- 分析1:getItemDecorInsetsForChild()-->
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
insets.set(0, 0, 0, 0);
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
// 獲取getItemOffsets() 中設置的值
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
// 將getItemOffsets() 中設置的值添加到insets 變量中
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
// 最終返回
return insets;
}
// insets介紹
// 1. 做用:
// a. 把每一個ItemView的全部 ItemDecoration 的 getItemOffsets 中設置的值累加起來,(每一個ItemView可添加多個ItemDecoration)
// 即把每一個ItemDecoration的left, top, right, bottom 4個屬性分別累加
// b. 記錄上述結果
// c. inset就像padding和margin同樣,會影響view的尺寸和位置
// 2. 使用場景:設置View的邊界大小,使得其大小>View的背景大小
// 如 按鈕圖標(View的背景)較小,可是咱們但願按鈕有較大的點擊熱區(View的邊界大小)
// 返回到分析1進來的原處
複製代碼
outRect
4個屬性值影響着ItemView
的Padding值RecyclerView
進行子View
寬高測量時(measureChild()
),會將getItemOffsets()
裏設置的 outRect
4個屬性值(Top、Bottom、Left、Right
)經過insert
值累加 ,並最終添加到子View
的 Padding
屬性中經過 Canvas
對象繪製內容佈局
onDraw()
請看我寫的自定義View文章:自定義View Draw過程- 最易懂的自定義View原理系列(4)post
@Override
public void onDraw(Canvas c, RecyclerView parent,
RecyclerView.State state) {
....
// 使用相似自定義View時的 onDraw()
}
複製代碼
**注意點1:Itemdecoration
的onDraw()
繪製會先於ItemView
的onDraw()
繪製,因此若是在Itemdecoration
的onDraw()
中繪製的內容在ItemView
邊界內,就會被ItemView
遮擋住。**以下圖:
此現象稱爲
onDraw()
的OverDraw
現象
解決方案:配合前面的 getItemOffsets()
一塊兒使用在outRect
矩形 與 ItemView
的間隔區域 繪製內容
即:經過
getItemOffsets()
設置與Item
的間隔區域,從而得到與ItemView
不重疊的繪製區域
注意點2: getItemOffsets()
針對是每個 ItemView
的,而 onDraw()
針對 RecyclerView
自己
解決方案:在 使用onDraw()
繪製時,須要先遍歷RecyclerView
的全部ItemView
分別獲取它們的位置信息,而後再繪製內容
- 此處遍歷的
RecyclerView
的ItemView
(即Child view
),並非Adapter
設置的每個item
,而是可見的item
- 由於只有可見的
Item
纔是RecyclerView
的Child view
@Override
public void onDraw(Canvas c, RecyclerView parent,
RecyclerView.State state) {
// RecyclerView 的左邊界加上 paddingLeft距離 後的座標位置
final int left = parent.getPaddingLeft();
// RecyclerView 的右邊界減去 paddingRight 後的座標位置
final int right = parent.getWidth() - parent.getPaddingRight();
// 即左右邊界就是 RecyclerView 的 ItemView區域
// 獲取RecyclerView的Child view的個數
final int childCount = parent.getChildCount();
// 設置佈局參數
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
// 遍歷每一個RecyclerView的Child view
// 分別獲取它們的位置信息,而後再繪製內容
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
int index = parent.getChildAdapterPosition(view);
// 第一個Item不須要繪製
if ( index == 0 ) {
continue;
}
// ItemView的下邊界:ItemView 的 bottom座標 + 距離RecyclerView底部距離 +translationY
final int top = child.getBottom() + params.bottomMargin +
Math.round(ViewCompat.getTranslationY(child));
// 繪製分割線的下邊界 = ItemView的下邊界+分割線的高度
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
複製代碼
在豐富 ItemView
的顯示效果,即在ItemView
的基礎上繪製內容
如分割線等等
ItemView
設計一個高度爲 10 px
的紅色分割線getItemOffsets()
設置與 Item
的下間隔區域 = 10 px
設置好
onDraw()
可繪製的區域
onDraw()
繪製一個高度 = 10px
的矩形(填充顏色=紅色)步驟1:自定義ItemDecoration類
ItemDecoration.java
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
// 在構造函數裏進行繪製的初始化,如畫筆屬性設置等
public DividerItemDecoration() {
mPaint = new Paint();
mPaint.setColor(Color.RED);
// 畫筆顏色設置爲紅色
}
// 重寫getItemOffsets()方法
// 做用:設置矩形OutRect 與 Item 的間隔區域
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int itemPosition = parent.getChildAdapterPosition(view);
// 得到每一個Item的位置
// 第1個Item不繪製分割線
if (itemPosition != 0) {
outRect.set(0, 0, 0, 10);
// 設置間隔區域爲10px,即onDraw()可繪製的區域爲10px
}
}
// 重寫onDraw()
// 做用:在間隔區域裏繪製一個矩形,即分割線
@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);
int index = parent.getChildAdapterPosition(child);
// 第1個Item不須要繪製
if ( index == 0 ) {
continue;
}
// 獲取佈局參數
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
// 設置矩形(分割線)的寬度爲10px
final int mDivider = 10;
// 根據子視圖的位置 & 間隔區域,設置矩形(分割線)的2個頂點座標(左上 & 右下)
// 矩形左上頂點 = (ItemView的左邊界,ItemView的下邊界)
// ItemView的左邊界 = RecyclerView 的左邊界 + paddingLeft距離 後的位置
final int left = parent.getPaddingLeft();
// ItemView的下邊界:ItemView 的 bottom座標 + 距離RecyclerView底部距離 +translationY
final int top = child.getBottom() + params.bottomMargin +
Math.round(ViewCompat.getTranslationY(child));
// 矩形右下頂點 = (ItemView的右邊界,矩形的下邊界)
// ItemView的右邊界 = RecyclerView 的右邊界減去 paddingRight 後的座標位置
final int right = parent.getWidth() - parent.getPaddingRight();
// 繪製分割線的下邊界 = ItemView的下邊界+分割線的高度
final int bottom = top + mDivider;
// 經過Canvas繪製矩形(分割線)
c.drawRect(left,top,right,bottom,mPaint);
}
}
}
複製代碼
步驟2:在設置RecyclerView時添加該分割線便可
Rv = (RecyclerView) findViewById(R.id.my_recycler_view);
//使用線性佈局
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
Rv.setLayoutManager(layoutManager);
Rv.setHasFixedSize(true);
// 經過自定義分割線類 添加分割線
Rv.addItemDecoration(new DividerItemDecoration());
//爲ListView綁定適配器
myAdapter = new MyAdapter(this,listItem);
Rv.setAdapter(myAdapter);
myAdapter.setOnItemClickListener(this);
複製代碼
Carson_Ho的Github地址:RecyclerView_ItemDecoration
onDraw()
相似,都是繪製內容onDraw()
的區別是:Itemdecoration
的onDrawOver()
繪製 是後於 ItemView
的onDraw()
繪製
- 即不須要考慮繪製內容被
ItemView
遮擋的問題,反而ItemView
會被onDrawOver()
繪製的內容遮擋- 繪製時機比較:
Itemdecoration.onDraw()
>ItemView.onDraw()
>Itemdecoration.onDrawOver()
onDraw()
請看我寫的自定義View文章:自定義View Draw過程- 最易懂的自定義View原理系列(4)
@Override
public void onDrawOver(Canvas c, RecyclerView parent,
RecyclerView.State state) {
....
// 使用相似自定義View時的 onDraw()
}
複製代碼
在 RecyclerView
/ 特定的 ItemView
上繪製內容,如蒙層、重疊內容等等
RecyclerView
上每一個 ItemView
上疊加一個角標** 步驟1:自定義 ItemDecoration
類**
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
private Bitmap mIcon;
// 在構造函數裏進行繪製的初始化,如畫筆屬性設置等
public DividerItemDecoration(Context context) {
mPaint = new Paint();
mPaint.setColor(Color.RED);
// 畫筆顏色設置爲紅色
// 獲取圖片資源
mIcon = BitmapFactory.decodeResource(context.getResources(), R.mipmap.logo);
}
// 重寫onDrawOver()
// 將角度繪製到ItemView上
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
// 獲取Item的總數
int childCount = parent.getChildCount();
// 遍歷Item
for ( int i = 0; i < childCount; i++ ) {
// 獲取每一個Item的位置
View view = parent.getChildAt(i);
int index = parent.getChildAdapterPosition(view);
// 設置繪製內容的座標(ItemView的左邊界,ItemView的上邊界)
// ItemView的左邊界 = RecyclerView 的左邊界 = paddingLeft距離 後的位置
final int left = parent.getWidth()/2;
// ItemView的上邊界
float top = view.getTop();
// 第1個ItemView不繪製
if ( index == 0 ) {
continue;
}
// 經過Canvas繪製角標
c.drawBitmap(mIcon,left,top,mPaint);
}
}
}
複製代碼
** 步驟2:在設置RecyclerView時添加便可 **
Rv = (RecyclerView) findViewById(R.id.my_recycler_view);
//使用線性佈局
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
Rv.setLayoutManager(layoutManager);
Rv.setHasFixedSize(true);
//用自定義分割線類設置分割線
Rv.addItemDecoration(new DividerItemDecoration());
//爲ListView綁定適配器
myAdapter = new MyAdapter(this,listItem);
Rv.setAdapter(myAdapter);
myAdapter.setOnItemClickListener(this);
複製代碼
Carson_Ho的Github地址:RecyclerView_ItemDecoration
有一個很是使用的自定義View是基於RecyclerView ItemDecoration類的,具體請看文章Android 自定義View實戰系列 :時間軸
我用一張圖總結RecyclerView ItemDecoration
類的使用
下一篇文章我將對講解Android
的相關知識,有興趣能夠繼續關注Carson_Ho的安卓開發筆記