前言
-
RecyclerView
在Android
開發(fā)中非常常用,如果能結(jié)合ItemDecoration
類使用,那么將大大提高RecyclerView
的表現(xiàn)效果 - 本文全面解析了
ItemDecoration
類,包括ItemDecoration
類簡(jiǎn)介晕窑、使用方法 & 實(shí)例講解粘优,最終結(jié)合 自定義View實(shí)現(xiàn) 時(shí)間軸UI開發(fā),希望你們會(huì)喜歡照宝。
ItemDecoration
類屬于RecyclerView
的高級(jí)用法- 閱讀本文前請(qǐng)先學(xué)習(xí)
RecyclerView
的使用:Android開發(fā):ListView蛇受、AdapterView、RecyclerView全面解析
目錄
1. ItemDecoration類 簡(jiǎn)介
1.1 定義
RecyclerView
類的靜態(tài)內(nèi)部類
1.2 作用
向 RecyclerView
中的 ItemView
添加裝飾
即繪制更多內(nèi)容厕鹃,豐富
ItemView
的UI
效果
2. 具體使用
ItemDecoration
類中僅有3個(gè)方法兢仰,具體如下:
public class TestDividerItemDecoration extends RecyclerView.ItemDecoration {
// 方法1:getItemOffsets()
// 作用:設(shè)置ItemView的內(nèi)嵌偏移長度(inset)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
...
}
// 方法2:onDraw()
// 作用:在子視圖上設(shè)置繪制范圍,并繪制內(nèi)容
// 類似平時(shí)自定義View時(shí)寫onDraw()一樣
// 繪制圖層在ItemView以下剂碴,所以如果繪制區(qū)域與ItemView區(qū)域相重疊把将,會(huì)被遮擋
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
...
}
// 方法3:onDrawOver()
// 作用:同樣是繪制內(nèi)容,但與onDraw()的區(qū)別是:繪制在圖層的最上層
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
...
}
下面忆矛,我將詳細(xì)介紹這3個(gè)方法察蹲。
2.1 getItemOffsets()
2.1.1 作用
設(shè)置ItemView的內(nèi)嵌偏移長度(inset)
- 如圖,其實(shí)
RecyclerView
中的 ItemView 外面會(huì)包裹著一個(gè)矩形(outRect
) -
內(nèi)嵌偏移長度 是指:該矩形(
outRect
)與ItemView
的間隔
- 內(nèi)嵌偏移長度分為4個(gè)方向:上催训、下洽议、左、右漫拭,并由
outRect
中的top亚兄、left、right采驻、bottom
參數(shù) 控制
top审胚、left、right礼旅、bottom
參數(shù)默認(rèn) = 0膳叨,即矩形和Item重疊,所以看起來矩形就消失了
2.1.2 具體使用
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// 參數(shù)說明:
// 1. outRect:全為 0 的 Rect(包括著Item)
// 2. view:RecyclerView 中的 視圖Item
// 3. parent:RecyclerView 本身
// 4. state:狀態(tài)
outRect.set(50, 0, 0,50);
// 4個(gè)參數(shù)分別對(duì)應(yīng)左(Left)各淀、上(Top)懒鉴、右(Right)、下(Bottom)
// 上述語句代表:左&下偏移長度=50px碎浇,右 & 上 偏移長度 = 0
...
}
2.1.3 源碼分析
-
RecyclerView
本質(zhì)上是一個(gè)自定義ViewGroup
临谱,子視圖child
= 每個(gè)ItemView
- 其通過
LayoutManager
測(cè)量并布局ItemView
public void measureChild(View child, int widthUsed, int heightUsed) {
// 參數(shù)說明:
// 1. child:要測(cè)量的子view(ItemView)
// 2. widthUsed: 一個(gè)ItemView的所有ItemDecoration占用的寬度(px)
// 3. heightUsed:一個(gè)ItemView的所有ItemDecoration占用的高度(px)
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
// 累加當(dāng)前ItemDecoration 4個(gè)屬性值->>分析1
widthUsed += insets.left + insets.right;
// 計(jì)算每個(gè)ItemView的所有ItemDecoration的寬度
heightUsed += insets.top + insets.bottom;
// 計(jì)算每個(gè)ItemView的所有ItemDecoration的高度
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
canScrollHorizontally());
// 測(cè)量child view(ItemView)的寬度
// 第三個(gè)參數(shù)設(shè)置 child view 的 padding,即ItemView的Padding
// 而該參數(shù)把 insets 的值算進(jìn)去奴璃,所以insets 值影響了每個(gè) ItemView 的 padding值
// 高度同上
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
child.measure(widthSpec, heightSpec);
}
}
// 分析完畢悉默,請(qǐng)?zhí)?<-- 分析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() 中設(shè)置的值
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
// 將getItemOffsets() 中設(shè)置的值添加到insets 變量中
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
// 最終返回
return insets;
}
// insets介紹
// 1. 作用:
// a. 把每個(gè)ItemView的所有 ItemDecoration 的 getItemOffsets 中設(shè)置的值累加起來,(每個(gè)ItemView可添加多個(gè)ItemDecoration)
// 即把每個(gè)ItemDecoration的left, top, right, bottom 4個(gè)屬性分別累加
// b. 記錄上述結(jié)果
// c. inset就像padding和margin一樣苟穆,會(huì)影響view的尺寸和位置
// 2. 使用場(chǎng)景:設(shè)置View的邊界大小抄课,使得其大小>View的背景大小
// 如 按鈕圖標(biāo)(View的背景)較小唱星,但是我們希望按鈕有較大的點(diǎn)擊熱區(qū)(View的邊界大小)
// 返回到分析1進(jìn)來的原處
總結(jié)
- 結(jié)論:
outRect
4個(gè)屬性值影響著ItemView
的Padding值 - 具體過程:在
RecyclerView
進(jìn)行子View
寬高測(cè)量時(shí)(measureChild()
)跟磨,會(huì)將getItemOffsets()
里設(shè)置的outRect
4個(gè)屬性值(Top间聊、Bottom、Left抵拘、Right
)通過insert
值累加 哎榴,并最終添加到子View
的Padding
屬性中
2.2 onDraw()
2.2.1 作用
通過 Canvas
對(duì)象繪制內(nèi)容
2.2.2 具體使用
- 使用方法類似自定義View時(shí)的
onDraw()
請(qǐng)看我寫的自定義View文章:自定義View Draw過程- 最易懂的自定義View原理系列(4)
@Override
public void onDraw(Canvas c, RecyclerView parent,
RecyclerView.State state) {
....
// 使用類似自定義View時(shí)的 onDraw()
}
2.2.3 特別注意
注意點(diǎn)1:Itemdecoration
的onDraw()
繪制會(huì)先于ItemView
的onDraw()
繪制,所以如果在Itemdecoration
的onDraw()
中繪制的內(nèi)容在ItemView
邊界內(nèi)僵蛛,就會(huì)被ItemView
遮擋住尚蝌。如下圖:
此現(xiàn)象稱為
onDraw()
的OverDraw
現(xiàn)象
解決方案:配合前面的 getItemOffsets()
一起使用在outRect
矩形 與 ItemView
的間隔區(qū)域 繪制內(nèi)容
即:通過
getItemOffsets()
設(shè)置與Item
的間隔區(qū)域,從而獲得與ItemView
不重疊的繪制區(qū)域
注意點(diǎn)2: getItemOffsets()
針對(duì)是每一個(gè) ItemView
的充尉,而 onDraw()
針對(duì) RecyclerView
本身
解決方案:在 使用onDraw()
繪制時(shí)飘言,需要先遍歷RecyclerView
的所有ItemView
分別獲取它們的位置信息,然后再繪制內(nèi)容
- 此處遍歷的
RecyclerView
的ItemView
(即Child view
)驼侠,并不是Adapter
設(shè)置的每一個(gè)item
姿鸿,而是可見的item
- 因?yàn)橹挥锌梢姷?code>Item 才是
RecyclerView
的Child view
@Override
public void onDraw(Canvas c, RecyclerView parent,
RecyclerView.State state) {
// RecyclerView 的左邊界加上 paddingLeft距離 后的坐標(biāo)位置
final int left = parent.getPaddingLeft();
// RecyclerView 的右邊界減去 paddingRight 后的坐標(biāo)位置
final int right = parent.getWidth() - parent.getPaddingRight();
// 即左右邊界就是 RecyclerView 的 ItemView區(qū)域
// 獲取RecyclerView的Child view的個(gè)數(shù)
final int childCount = parent.getChildCount();
// 設(shè)置布局參數(shù)
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
// 遍歷每個(gè)RecyclerView的Child view
// 分別獲取它們的位置信息,然后再繪制內(nèi)容
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
int index = parent.getChildAdapterPosition(view);
// 第一個(gè)Item不需要繪制
if ( index == 0 ) {
continue;
}
// ItemView的下邊界:ItemView 的 bottom坐標(biāo) + 距離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);
}
}
}
2.2.4 應(yīng)用場(chǎng)景
在豐富 ItemView
的顯示效果泪电,即在ItemView
的基礎(chǔ)上繪制內(nèi)容
如分割線等等
2.2.5 實(shí)例講解
- 實(shí)例說明:在
ItemView
設(shè)計(jì)一個(gè)高度為10 px
的紅色分割線 - 思路
- 通過
getItemOffsets()
設(shè)置與Item
的下間隔區(qū)域 =10 px
- 通過
設(shè)置好
onDraw()
可繪制的區(qū)域
- 通過
onDraw()
繪制一個(gè)高度 =10px
的矩形(填充顏色=紅色)
- 具體實(shí)現(xiàn)
步驟1:自定義ItemDecoration類
ItemDecoration.java
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
// 在構(gòu)造函數(shù)里進(jìn)行繪制的初始化般妙,如畫筆屬性設(shè)置等
public DividerItemDecoration() {
mPaint = new Paint();
mPaint.setColor(Color.RED);
// 畫筆顏色設(shè)置為紅色
}
// 重寫getItemOffsets()方法
// 作用:設(shè)置矩形OutRect 與 Item 的間隔區(qū)域
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int itemPosition = parent.getChildAdapterPosition(view);
// 獲得每個(gè)Item的位置
// 第1個(gè)Item不繪制分割線
if (itemPosition != 0) {
outRect.set(0, 0, 0, 10);
// 設(shè)置間隔區(qū)域?yàn)?0px,即onDraw()可繪制的區(qū)域?yàn)?0px
}
}
// 重寫onDraw()
// 作用:在間隔區(qū)域里繪制一個(gè)矩形,即分割線
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// 獲取RecyclerView的Child view的個(gè)數(shù)
int childCount = parent.getChildCount();
// 遍歷每個(gè)Item相速,分別獲取它們的位置信息,然后再繪制對(duì)應(yīng)的分割線
for ( int i = 0; i < childCount; i++ ) {
// 獲取每個(gè)Item的位置
final View child = parent.getChildAt(i);
int index = parent.getChildAdapterPosition(child);
// 第1個(gè)Item不需要繪制
if ( index == 0 ) {
continue;
}
// 獲取布局參數(shù)
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
// 設(shè)置矩形(分割線)的寬度為10px
final int mDivider = 10;
// 根據(jù)子視圖的位置 & 間隔區(qū)域鲜锚,設(shè)置矩形(分割線)的2個(gè)頂點(diǎn)坐標(biāo)(左上 & 右下)
// 矩形左上頂點(diǎn) = (ItemView的左邊界,ItemView的下邊界)
// ItemView的左邊界 = RecyclerView 的左邊界 + paddingLeft距離 后的位置
final int left = parent.getPaddingLeft();
// ItemView的下邊界:ItemView 的 bottom坐標(biāo) + 距離RecyclerView底部距離 +translationY
final int top = child.getBottom() + params.bottomMargin +
Math.round(ViewCompat.getTranslationY(child));
// 矩形右下頂點(diǎn) = (ItemView的右邊界,矩形的下邊界)
// ItemView的右邊界 = RecyclerView 的右邊界減去 paddingRight 后的坐標(biāo)位置
final int right = parent.getWidth() - parent.getPaddingRight();
// 繪制分割線的下邊界 = ItemView的下邊界+分割線的高度
final int bottom = top + mDivider;
// 通過Canvas繪制矩形(分割線)
c.drawRect(left,top,right,bottom,mPaint);
}
}
}
步驟2:在設(shè)置RecyclerView時(shí)添加該分割線即可
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);
2.2.6 結(jié)果展示
2.2.7 源碼地址
Carson_Ho的Github地址:RecyclerView_ItemDecoration
2.3 onDrawOver()
2.3.1 作用
- 與
onDraw()
類似突诬,都是繪制內(nèi)容 - 但與
onDraw()
的區(qū)別是:Itemdecoration
的onDrawOver()
繪制 是后于ItemView
的onDraw()
繪制
- 即不需要考慮繪制內(nèi)容被
ItemView
遮擋的問題,反而ItemView
會(huì)被onDrawOver()
繪制的內(nèi)容遮擋- 繪制時(shí)機(jī)比較:
Itemdecoration.onDraw()
>ItemView.onDraw()
>Itemdecoration.onDrawOver()
2.3.2 具體使用
- 使用方法類似自定義View時(shí)的
onDraw()
請(qǐng)看我寫的自定義View文章:自定義View Draw過程- 最易懂的自定義View原理系列(4)
@Override
public void onDrawOver(Canvas c, RecyclerView parent,
RecyclerView.State state) {
....
// 使用類似自定義View時(shí)的 onDraw()
}
2.3.3 應(yīng)用場(chǎng)景
在 RecyclerView
/ 特定的 ItemView
上繪制內(nèi)容芜繁,如蒙層旺隙、重疊內(nèi)容等等
2.3.4 實(shí)例講解
- 實(shí)例說明:在
RecyclerView
上每個(gè)ItemView
上疊加一個(gè)角標(biāo)
- 具體代碼實(shí)現(xiàn)
** 步驟1:自定義 ItemDecoration
類**
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
private Bitmap mIcon;
// 在構(gòu)造函數(shù)里進(jìn)行繪制的初始化,如畫筆屬性設(shè)置等
public DividerItemDecoration(Context context) {
mPaint = new Paint();
mPaint.setColor(Color.RED);
// 畫筆顏色設(shè)置為紅色
// 獲取圖片資源
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的總數(shù)
int childCount = parent.getChildCount();
// 遍歷Item
for ( int i = 0; i < childCount; i++ ) {
// 獲取每個(gè)Item的位置
View view = parent.getChildAt(i);
int index = parent.getChildAdapterPosition(view);
// 設(shè)置繪制內(nèi)容的坐標(biāo)(ItemView的左邊界,ItemView的上邊界)
// ItemView的左邊界 = RecyclerView 的左邊界 = paddingLeft距離 后的位置
final int left = parent.getWidth()/2;
// ItemView的上邊界
float top = view.getTop();
// 第1個(gè)ItemView不繪制
if ( index == 0 ) {
continue;
}
// 通過Canvas繪制角標(biāo)
c.drawBitmap(mIcon,left,top,mPaint);
}
}
}
** 步驟2:在設(shè)置RecyclerView時(shí)添加即可 **
Rv = (RecyclerView) findViewById(R.id.my_recycler_view);
//使用線性布局
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
Rv.setLayoutManager(layoutManager);
Rv.setHasFixedSize(true);
//用自定義分割線類設(shè)置分割線
Rv.addItemDecoration(new DividerItemDecoration());
//為ListView綁定適配器
myAdapter = new MyAdapter(this,listItem);
Rv.setAdapter(myAdapter);
myAdapter.setOnItemClickListener(this);
2.3.5 結(jié)果展示
2.3.6 源碼地址
Carson_Ho的Github地址:RecyclerView_ItemDecoration
3. 使用總結(jié)
我用一張圖總結(jié)RecyclerView ItemDecoration
類的使用
4. 結(jié)合自定義View的實(shí)踐應(yīng)用:時(shí)間軸
-
Android
開發(fā)中骏令,時(shí)間軸的UI
需求非常常見蔬捷,如下圖:
示意圖 本次實(shí)例將結(jié)合 自定義
View
&RecyclerView
的知識(shí),手把手教你實(shí)現(xiàn)該常見 & 實(shí)用的自定義View
:時(shí)間軸
具體請(qǐng)看文章:Android 自定義View實(shí)戰(zhàn)系列 :時(shí)間軸
- 下一篇文章我將繼續(xù)結(jié)合 自定義
View
&RecyclerView.ItemDecoration
類進(jìn)行一些有趣的自定義View
實(shí)例講解榔袋,感興趣的同學(xué)可以繼續(xù)關(guān)注本人運(yùn)營的Wechat Public Account
: - 我想給你們介紹一個(gè)與眾不同的Android微信公眾號(hào)(福利回贈(zèng))
- 我想邀請(qǐng)您和我一起寫Android(福利回贈(zèng))
請(qǐng)點(diǎn)贊周拐!因?yàn)槟愕墓膭?lì)是我寫作的最大動(dòng)力!
相關(guān)文章閱讀
Android開發(fā):最全面凰兑、最易懂的Android屏幕適配解決方案
Android事件分發(fā)機(jī)制詳解:史上最全面妥粟、最易懂
Android開發(fā):史上最全的Android消息推送解決方案
Android開發(fā):最全面、最易懂的Webview詳解
Android開發(fā):JSON簡(jiǎn)介及最全面解析方法!
Android四大組件:Service服務(wù)史上最全面解析
Android四大組件:BroadcastReceiver史上最全面解析
歡迎關(guān)注Carson_Ho的簡(jiǎn)書吏够!
不定期分享關(guān)于安卓開發(fā)的干貨勾给,追求短滩报、平、快播急,但卻不缺深度脓钾。