前言
今年換了新工作 , 剛開(kāi)始熟悉項(xiàng)目 ,今天終于交付了一個(gè)版本 , 整個(gè)人忙得暈頭轉(zhuǎn)向 , 每次接手新項(xiàng)目 ,都需要一段時(shí)間去熟悉項(xiàng)目 , 有時(shí)候會(huì)因?yàn)椴皇煜で叭螌?xiě)的代碼 , 可能會(huì)掉進(jìn)不明不白的坑里 , 至此,我覺(jué)得想要最快熟悉前任寫(xiě)代碼 ,當(dāng)前又沒(méi)有任務(wù) ,可以小范圍重構(gòu) , 并進(jìn)行測(cè)試 , 可以減少因?yàn)椴皇煜ろ?xiàng)目的一些細(xì)節(jié) ,而踩到不明不白的坑 。
ItemDecoration
使用過(guò)RecyclerView的朋友肯定有過(guò)這樣的經(jīng)歷 , 以前在使用ListView的時(shí)候,在Item之間的分割線(xiàn)上 , 使用ListView的divider屬性就可以搞定,但在RecyclerView中卻再也沒(méi)見(jiàn)到這個(gè)屬性,那么他到哪里去了呢 ? RecyclerView給我們提供了這樣的方法addItemDecoration(ItemDecoration decor) 傳遞進(jìn)去的ItemDecoration對(duì)象就是今天的主角,專(zhuān)門(mén)用于處理Item之間的間隔和拓展 。
從RecyclerView源碼中可以看到,ItemDecoration是其子類(lèi)并且還是一個(gè)抽象類(lèi),里面有幾個(gè)抽象方法,需要我們繼承ItemDecoration,并進(jìn)行重寫(xiě)的 。
/**
* 獲得條目偏移量 , 回調(diào)此方法來(lái)確定每個(gè)Item的偏移量
* @param outRect 矩形區(qū)域 , 每一個(gè)Item都是一個(gè)矩形區(qū)域,rect表示矩形區(qū)域的四個(gè)方位
* @param view 每個(gè)條目的View對(duì)象
* @param parent
* @param state
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
}
/**
* 間隔線(xiàn)繪制
* @param c
* @param parent
* @param state
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
}
我們從源碼中可以看到 , ItemDecoration里面,全是RecyclerView在內(nèi)部會(huì)回調(diào)的方法,并提供onDraw方法 ,這樣給了我們極大的自定義性,可以畫(huà)出任意圖案的Item 。
自定義簡(jiǎn)單的ItemDecoration
移動(dòng)設(shè)備開(kāi)發(fā),大部分時(shí)間都是在可以List列表類(lèi)打交道,因?yàn)槭殖衷O(shè)備的顯示空間有限,可以只能通過(guò)上下滑動(dòng)來(lái)展示更多的內(nèi)容。因?yàn)?code>RecyclerView沒(méi)有給我們提供常規(guī)的divider,那么我們就指定一個(gè)簡(jiǎn)單的ItemDecoration,了解它自定義的原理 。只有了解了其原理 , 以后想要什么樣的都有 。
ItemDecoration給我們提供了兩個(gè)方法,onDraw方法自不必多說(shuō) , 大家都知道 。getItemOffsets這個(gè)方法估計(jì)就比較少見(jiàn)了, 這個(gè)方法是設(shè)置我們每個(gè)Item的偏移量,也就是我們需要Item給出多大的繪制空間 , 這個(gè)方法給了Rect對(duì)象,用來(lái)設(shè)置巨型的偏移量,也就是每個(gè)Item多出多燒來(lái)進(jìn)行繪制你給的圖案 。
getItemOffsets 圖解

由圖上可以看出 , 每個(gè)Item都是一個(gè)rect , 而我們要繪制的就是rect的四個(gè)邊, 上圖是以畫(huà)類(lèi)似ListView的divider為例子 。
畫(huà)水平線(xiàn)
畫(huà)水平線(xiàn) , 想要畫(huà)水平線(xiàn),就要先弄清楚 , 要畫(huà)rect的哪個(gè)邊,就List的來(lái)說(shuō),是rect的下邊也就是bottom 。在getItemOffsets設(shè)置rect的下邊的偏移量 。
設(shè)置偏移量
outRect.set(0,0,0,drawable.getIntrinsicHeight()); // 將drawable繪制在Item的底部
這樣就在rect的底部 , 偏移了一定的高度,可以繪制并顯示我們的圖案 。
繪制
/**
* 畫(huà)水平線(xiàn)
* @param c 畫(huà)布
* @param parent
*/
private void drawHorizontalLine(Canvas c, RecyclerView parent) {
int left = parent.getPaddingLeft(); // 從recyclerView容器內(nèi)邊距算起 , 左邊起點(diǎn)坐標(biāo)
int right = parent.getWidth() - parent.getPaddingRight(); // recyclerView寬度減去paddingRight計(jì)算右邊坐標(biāo)
// 以上 , Item decoration的寬度坐標(biāo)確定 , 即rect的寬度
int childCount = parent.getChildCount();
for (int i = 0; i <childCount ; i++) {
View childView = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) childView.getLayoutParams();
int top = childView.getBottom() + layoutParams.bottomMargin + Math.round(ViewCompat.getTranslationY(childView));
int bottom = top + drawable.getIntrinsicHeight();
// 每一個(gè)Item都需要繪制一遍
drawable.setBounds(left,top,right,bottom);
drawable.draw(c);
}
}
因?yàn)槔L制的是Drawable所以也需要指定drawable的rect , 指定rect的方法為drawable.setBounds(left,top,right,bottom);,這樣來(lái)確定drawable繪制的圖形大小寬高 。
畫(huà)水平線(xiàn),所有rect的左邊是從RecyclerView的左內(nèi)邊距算起(內(nèi)邊距不參與繪制) , 右邊就是RecyclerView的整個(gè)寬度減去右內(nèi)邊距,這樣計(jì)算得出Item的寬度 。因?yàn)槭荓istView,每條線(xiàn)都在不同的Item下,所有rect的上邊就在每個(gè)Item的bottom下面,所有drawable的上邊就要從每個(gè)Item的下邊算起,即 childView.getBottom() + layoutParams.bottomMargin + Math.round(ViewCompat.getTranslationY(childView)); 而drawable rect的下邊則是每個(gè)Item的bottom 加上 drawable的高度 , 這樣drawable rect的四個(gè)邊就計(jì)算完成了 , 我們水平線(xiàn)也就畫(huà)出來(lái)了 。
完整源碼
public class SimpleItemDecoration extends RecyclerView.ItemDecoration {
private int orientation;
private Drawable drawable;
public SimpleItemDecoration(@NonNull int orientation, @NonNull Drawable drawable) {
this.orientation = orientation;
this.drawable = drawable;
}
/**
* 設(shè)置RecyclerView的布局管理器
* @param orientation
*/
public void setOrientation(int orientation) {
if (LinearLayoutManager.HORIZONTAL != orientation && LinearLayoutManager.VERTICAL != orientation)
new IllegalArgumentException("RecyclerView不支持此布局管理器");
this.orientation = orientation;
}
/**
* 獲得條目偏移量 , 回調(diào)此方法來(lái)確定每個(gè)Item的偏移量
* @param outRect 矩形區(qū)域 , 每一個(gè)Item都是一個(gè)矩形區(qū)域,rect表示矩形區(qū)域的四個(gè)方位
* @param view 每個(gè)條目的View對(duì)象
* @param parent
* @param state
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (LinearLayoutManager.VERTICAL == orientation) {
outRect.set(0,0,0,drawable.getIntrinsicHeight()); // 將drawable繪制在Item的底部
}else if (LinearLayoutManager.HORIZONTAL == orientation) {
outRect.set(0,0,drawable.getIntrinsicWidth(),0); // 將drawable繪制在Item的右邊
}
}
/**
* 間隔線(xiàn)繪制
* @param c
* @param parent
* @param state
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (LinearLayoutManager.VERTICAL == orientation) {
drawHorizontalLine(c,parent); //畫(huà)水平線(xiàn)
}else if (LinearLayoutManager.HORIZONTAL == orientation) {
drawVerticalLine(c,parent); // 畫(huà)垂直線(xiàn)
}
}
/**
* 畫(huà)水平線(xiàn)
* @param c 畫(huà)布
* @param parent
*/
private void drawHorizontalLine(Canvas c, RecyclerView parent) {
int left = parent.getPaddingLeft(); // 從recyclerView容器內(nèi)邊距算起 , 左邊起點(diǎn)坐標(biāo)
int right = parent.getWidth() - parent.getPaddingRight(); // recyclerView寬度減去paddingRight計(jì)算右邊坐標(biāo)
// 以上 , Item decoration的寬度坐標(biāo)確定 , 即rect的長(zhǎng)度
int childCount = parent.getChildCount();
for (int i = 0; i <childCount ; i++) {
View childView = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) childView.getLayoutParams();
int top = childView.getBottom() + layoutParams.bottomMargin + Math.round(ViewCompat.getTranslationY(childView));
int bottom = top + drawable.getIntrinsicHeight();
// 每一個(gè)Item都需要繪制一遍
drawable.setBounds(left,top,right,bottom);
drawable.draw(c);
}
}
/**
* 畫(huà)垂直線(xiàn)
* @param canvas
* @param parent
*/
private void drawVerticalLine(Canvas canvas,RecyclerView parent) {
int top = parent.getPaddingTop();
int bottom = parent.getHeight() - parent.getPaddingBottom();
int childCount = parent.getChildCount();
for (int i = 0; i <childCount ; i++) {
View childView = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) childView.getLayoutParams();
int left = childView.getRight() + layoutParams.rightMargin + Math.round(ViewCompat.getTranslationX(childView));
int right = left + drawable.getIntrinsicWidth();
drawable.setBounds(left,top,right,bottom);
drawable.draw(canvas);
}
}
}
結(jié)語(yǔ)
RecyclerView很強(qiáng)大 , 但是需要自定義的很多 , 這也就造成了RecyclerView可以寫(xiě)出千變?nèi)f化的UI出來(lái),如此松耦合的庫(kù),需要我們不斷的去學(xué)習(xí) 。
很久沒(méi)寫(xiě)文章了 , 今年忙碌并且充實(shí)著,也即將迎來(lái)人生中的第一輛車(chē),想想渾身都是動(dòng)力 。