Android之RecyclerView——用ItemDecoration裝飾你的Item
參考資料
背景介紹
RecyclerView由於它強大的靈活性,已經可以代替掉傳統的ListView和GridView等列表控制元件了。但是也因為它的靈活性,一些東西就沒有固定,需要我們自己來實現,比如RecyclerView就沒有提供預設的分割線,當我們需要一條分割線時,我們需要自己通過繼承RecyclerView.ItemDecoration來實現。當然,它不僅僅可以實現分割線,還能創造很多不一樣的效果,這取決於我們的想象力在這個限制範圍內能到什麼程度了。在另一篇RecyclerView——LayoutManager中,我們有必要先了解一點本篇的內容。接下來我們來好好聊聊ItemDecoration
認識ReccylerView.ItemDecoration
為RecyclverView新增ItemDecoration
我們可以通過呼叫:
mRecyclerView.addItemDecoration(mItemDecoration);
來為我們的RecyclerView新增一個自定義的ItemDecoration裝飾它。
這裡值得注意的是我們採用的是addXXX方法,這意味著我們可以為RcyclerView新增多個ItemDecoration。開啟該方法的原始碼我們可以看到:
//這是我們通常呼叫的程式碼
public void addItemDecoration (ItemDecoration decor) {
//這裡又呼叫了另一個過載的方法
addItemDecoration(decor, -1);
}
/**
* Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
* affect both measurement and drawing of individual item views.
*
* <p>Item decorations are ordered. Decorations placed earlier in the list will
* be run/queried/drawn first for their effects on item views. Padding added to views
* will be nested; a padding added by an earlier decoration will mean further
* item decorations in the list will be asked to draw/pad within the previous decoration's
* given area.</p>
*
* @param decor Decoration to add
* @param index Position in the decoration chain to insert this decoration at. If this value
* is negative the decoration will be added at the end.
* 通過文件我們可以知道,index引數控制這我們add的ItemDecoration新增到mItemDecorations陣列中的位置。如果小於0,我們的ItemDecoration將被新增到最後。
*/
public void addItemDecoration(ItemDecoration decor, int index) {
if (mLayout != null) {
mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or"
+ " layout");
}
if (mItemDecorations.isEmpty()) {
setWillNotDraw(false);
//注意,當我們首次新增ItemDecoration時,mItemDecorations是一個空的陣列,
//但這事我們就需要讓View開啟自我繪製,否則RecyclerView的onDraw()方法將有可能不被執行。
//我們在寫自定義ViewGroup時也需要注意這個問題。
}
if (index < 0) {
//這個邏輯保證了小於0,新增到末尾
mItemDecorations.add(decor);
} else {
//新增到指定位置
mItemDecorations.add(index, decor);
}
//這個方法最終把RecyclerView的Item的LayoutParams的mInsertDirty屬性設定為true,
//這樣在measure時,才能夠把所有的ItemDecoration中的itemOffset新增到Item的佈局引數上。
markItemDecorInsetsDirty();
requestLayout();
}
所以,我們可以多次呼叫addItemDecoration()
,並且每次新增的ItemDecoration的itemOffset都將累加到Item的佈局引數上。
自定義ItemDecoration
對於自定義ItemDecoration我們通常僅僅關心以下3個方法就行。
getItemOffsets()
這個方法有2個過載方法:
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
outRect.set(0, 0, 0, 0);
//注意,重寫時super要慎重使用,否則設定的Rect會被重置。
//通過上面的講解,我們知道,在這裡設定的outRect的引數最終都會被累加到Item的佈局引數上。
//如果值設為負數,那麼Item會發生重疊。
}
//這個方法主要能夠方便的獲取到View和當前RecyclerView的狀態,
//但它最終呼叫的仍然是上一個方法,所以重寫這個方法需要注意super,最好就不要要。
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
這裡借張圖,侵刪!
onDraw()
該方法同樣有兩個過載方法:
public void onDraw(Canvas c, RecyclerView parent){
//在這個方法中利用Canvas繪製點什麼,
//它最終會繪製在Item的下一層,如果它超出Item,那麼超出部分將可見。
//注意,計算Item的top時,記得加上Translation,否則不能準確計算
}
//該方法最終還是呼叫上一個方法,所以要注意super,最好就不要。這個方法能夠獲取到RecyclerView狀態。
public void onDraw(Canvas c, RecyclerView parent, State state) {
onDraw(c, parent);
}
借張圖,侵刪!
這裡有個坑,在我使用mDrawable.canvas()
來繪製分割線的時候,由於mDrawable.getIntrinscXXX()
獲得的值為-1,所以導致了繪製效果不可見。解決辦法是在計算bottom或right時,直接指定我們所希望的間距,除非我們已經設定mDrawable的尺寸。
還有一點需要注意:最終getItemOffset()方法中設定的偏移距離會疊加到Item的佈局尺寸引數上,而分割線的繪製應該在Item底部(去尾),或者說Item頂部(去頭),繪製區域
onDrawOver()
與onDraw()
方法類似,不同的是它將被覆蓋在Item上面。它同樣有兩個過載方法:
public void onDrawOver(Canvas c, RecyclerView parent, State state) {
onDrawOver(c, parent);
}
@Deprecated
public void onDrawOver(Canvas c, RecyclerView parent) {
}
總結
通過自定義ItemDecoration
我們可以實現需要吊炸天的效果。先遵守規則,然後天馬行空。
探討
今天在換燕x礦泉水時(這不是廣告),發現上面裹桶口裹的一層保護塑料,裹的倒是十分結實!但是我找了半天沒發現有可以下手撕開的地方,也就是說要麼硬摳開,要麼藉助工具,總之就是不能讓你輕易就開啟。不知道生產這種桶裝水的人自己用不用,或者說自己換過水沒?
這事兒,你怎麼看?