1. 程式人生 > >Android之RecyclerView——用ItemDecoration裝飾你的Item

Android之RecyclerView——用ItemDecoration裝飾你的Item

參考資料

參考資料1

背景介紹

RecyclerView由於它強大的靈活性,已經可以代替掉傳統的ListViewGridView等列表控制元件了。但是也因為它的靈活性,一些東西就沒有固定,需要我們自己來實現,比如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(),並且每次新增的ItemDecorationitemOffset都將累加到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);
        }

這裡借張圖,侵刪!
設定不同outRect引數的效果圖

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,所以導致了繪製效果不可見。解決辦法是在計算bottomright時,直接指定我們所希望的間距,除非我們已經設定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礦泉水時(這不是廣告),發現上面裹桶口裹的一層保護塑料,裹的倒是十分結實!但是我找了半天沒發現有可以下手撕開的地方,也就是說要麼硬摳開,要麼藉助工具,總之就是不能讓你輕易就開啟。不知道生產這種桶裝水的人自己用不用,或者說自己換過水沒?
這事兒,你怎麼看?