RecyclerView.ItemDecoration的詳解使用(RecyclerView系列二)

前言

在很早很早之前(long long ago),ListView鼎盛的時代有一個屬性叫作divider。可是在RecycleView上面就是找不到他,那怎麼辦呢???直到後來有一天發現他變身了,變成了ItemDecoration。實在是扯不下去了,直接開始吧! 這篇博客醞釀了好長時間,但願不會讓各位看官失望。bash

任務

瞭解ItemDecoration的原理,本身能夠添加分割線,每一個 ItemView 上疊加一個角標,自定義 RecyclerView 中的頭部或者是粘性頭部。ide

分析和實戰

1.具體的使用

如今開始處理:寫TextItemDecoration類讓他繼承RecyclerView.ItemDecoration,主要的代碼以下所示:

class TextItemDecoration extends RecyclerView.ItemDecoration {

        //設置ItemView的內嵌偏移長度(inset)
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
        }

        // 在子視圖上設置繪製範圍,並繪製內容
        // 繪製圖層在ItemView如下,因此若是繪製區域與ItemView區域相重疊,會被遮擋
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDraw(c, parent, state);
        }

        //一樣是繪製內容,但與onDraw()的區別是:繪製在圖層的最上層
        @Override
        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
        }
    }
複製代碼

2.分析方法getItemOffsets()

getItemOffsets()總的歸納
咱們全部的分析這個圖就能夠歸納了。如今咱們開始分析這個方法,在Android Studio中看super.getItemOffsets(outRect, view, parent, state);這個方法,最終咱們在RecyclerView看到outRect.set(0, 0, 0, 0);這一行代碼。 那麼咱們就拿outRect開刀。TextItemDecoration中代碼以下:

//設置ItemView的內嵌偏移長度(inset)
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            //只是添加下面這一行代碼
            outRect.set(50, 50, 50, 50);
        }

複製代碼

把這個·ItemDecoration·放在下面的RecyclerView上面,
代碼以下:
recyclerView2.addItemDecoration(new TextItemDecoration());
運行效果,以下圖所示:源碼分析

再來一張單獨的圖片,以下所示:

  • 如上圖所示,RecyclerView 中的 ItemView 外面會包裹着一個矩形(outRect)。
  • 內嵌偏移長度:該矩形(outRect)與 ItemView的間隔.
  • 默認的狀況下,top、left、right、bottom都是0,因此矩形和ItemView就重疊了。

2.1源碼分析(直接上源碼):

下面的代碼都是在RecyclerView中,能夠在RecyclerView裏面找到源碼:優化

/測量全部的子view的寬和高,獲得這個子view的Rect。而後就能獲得這一塊真正的寬和高。
    //注意還有padding的值。
    public void measureChild(View child, int widthUsed, int heightUsed) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
        final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
        widthUsed += insets.left + insets.right;
        heightUsed += insets.top + insets.bottom;
        final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
                getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
                canScrollHorizontally());
        final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
                getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
                canScrollVertically());
        if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
            child.measure(widthSpec, heightSpec);
        }
    }

    //獲得每一個子view相應的Rect,
    //mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
    //上面的代碼就是設置相應的四個值,就和咱們的TextItemDecoration類裏面的代碼對應起來了。
    Rect getItemDecorInsetsForChild(View child) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (!lp.mInsetsDirty) {
            return lp.mDecorInsets;
        }

        if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
            // changed/invalid items should not be updated until they are rebound.
            return lp.mDecorInsets;
        }
        final Rect insets = lp.mDecorInsets;
        insets.set(0, 0, 0, 0);
        final int decorCount = mItemDecorations.size();
        for (int i = 0; i < decorCount; i++) {
            mTempRect.set(0, 0, 0, 0);
            mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
            //下面就是詳細的賦值
            insets.left += mTempRect.left;
            insets.top += mTempRect.top;
            insets.right += mTempRect.right;
            insets.bottom += mTempRect.bottom;
        }
        lp.mInsetsDirty = false;
        return insets;
    }

複製代碼

3. ondraw()

咱們先來看看咱們自定義的TextItemDecoration類裏面的onDraw()代碼,以下所示:ui

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDraw(c, parent, state);
        }
複製代碼

很明顯上面傳遞了一個Canvas 參數對象,因此它擁有了繪製的能力。this

注意: 1.getItemOffsets 是針對每個 ItemView,而 onDraw 方法倒是針對 RecyclerView 自己,因此在 onDraw 方法中須要遍歷屏幕上可見的 ItemView,分別獲取它們的位置信息,而後分別的繪製對應的分割線。 2.Itemdecoration的onDraw()繪製會先於ItemView的onDraw()繪製,spa

第二點會出現以下的狀況:3d

出現上面的問題解決方案是getItemOffsets()與onDraw()一塊使用。說的我一愣一愣的,最主要的問題onDrow()的時候,是怎樣獲得相應的點。code

英雄莫怕,請看上面的代碼onDraw(Canvas c, RecyclerView parent, RecyclerView.State state):orm

看第二個參數:RecyclerView parent 這就是咱們的突破點。(這裏有一個疑問點???咱們第4節處理。)
  int childCount = parent.getChildCount();
  View child = parent.getChildAt(i);
  上面的代碼就能解決咱們的問題。

複製代碼

上代碼實戰:要實現的效果以下:

做者:yzzCool 連接:https://www.jianshu.com/p/41ae13016243 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

代碼實現以下:

class TextItemDecoration extends RecyclerView.ItemDecoration {
        private Paint mPaint;

        public TextItemDecoration() {
            this.mPaint = new Paint();
            mPaint.setColor(Color.YELLOW);
            // 畫筆顏色設置爲黃色
        }

        //設置ItemView的內嵌偏移長度(inset)
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            outRect.set(50, 50, 50, 50);
        }

        // 在子視圖上設置繪製範圍,並繪製內容
        // 繪製圖層在ItemView如下,因此若是繪製區域與ItemView區域相重疊,會被遮擋
        @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);
                // 設置矩形(分割線)的寬度爲10px
                final int mDivider = 10;
                // 矩形左上頂點 = (ItemView的左邊界,ItemView的下邊界)
                final int left = child.getLeft();
                final int top = child.getBottom();
                // 矩形右下頂點 = (ItemView的右邊界,矩形的下邊界)
                final int right = child.getRight();
                final int bottom = top + mDivider;
                // 經過Canvas繪製矩形(分割線)
                c.drawRect(left, top, right, bottom, mPaint);
            }
        }


        //一樣是繪製內容,但與onDraw()的區別是:繪製在圖層的最上層
        @Override
        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
        }
    }

複製代碼

4.onDrawOver()

咱們先來看看咱們自定義的TextItemDecoration類裏面的onDrawOver()代碼,以下所示:

@Override
        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
        }

複製代碼

很明顯onDrawOver裏面的參數和onDraw裏面的參數如出一轍,那還要onDrawOver有什麼用呢???。請看下圖:

最主要的就是紫色區域,onDrawOver的使用方法和onDraw相似。如今咱們在每個條目的右上角加一個圖標。效果以下:

代碼實現以下:

class TextItemDecoration extends RecyclerView.ItemDecoration {
        private Paint mPaint;
        private Bitmap bitmap;

        public TextItemDecoration() {
            this.mPaint = new Paint();
            mPaint.setColor(Color.YELLOW);
            // 畫筆顏色設置爲黃色
            bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.email);
        }

        //設置ItemView的內嵌偏移長度(inset)
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            outRect.set(50, 50, 50, 50);
        }

        // 在子視圖上設置繪製範圍,並繪製內容
        // 繪製圖層在ItemView如下,因此若是繪製區域與ItemView區域相重疊,會被遮擋
        @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);
                // 設置矩形(分割線)的寬度爲10px
                final int mDivider = 10;
                // 矩形左上頂點 = (ItemView的左邊界,ItemView的下邊界)
                final int left = child.getLeft();
                final int top = child.getBottom();
                // 矩形右下頂點 = (ItemView的右邊界,矩形的下邊界)
                final int right = child.getRight();
                final int bottom = top + mDivider;
                // 經過Canvas繪製矩形(分割線)
                c.drawRect(left, top, right, bottom, mPaint);
            }
        }


        //一樣是繪製內容,但與onDraw()的區別是:繪製在圖層的最上層
        @Override
        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
            int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = parent.getChildAt(i);
                final int left = child.getRight() - bitmap.getWidth();
                final int top = child.getTop();
                c.drawBitmap(bitmap, left, top, mPaint);
            }
        }
    }


複製代碼

5.自定義 RecyclerView 中的頭部或者是粘性頭部

具體的思路以下圖所示:

咱們的操做在OutRect裏面處理。下面咱們用假數據處理,頁面中只保留一個RecyclerView。咱們只分析TextItemDecoration裏面的代碼。代碼以下:

class TextItemDecoration extends RecyclerView.ItemDecoration {
        private Paint mPaint;

        public TextItemDecoration() {
            this.mPaint = new Paint();
            // 畫筆顏色設置爲黃色
            mPaint.setColor(Color.YELLOW);
        }

        //設置ItemView的內嵌偏移長度(inset)
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            int position = parent.getChildAdapterPosition(view);
            if (position % 5 == 0) {
                outRect.set(0, 50, 0, 0);
            }
        }

        // 在子視圖上設置繪製範圍,並繪製內容
        // 繪製圖層在ItemView如下,因此若是繪製區域與ItemView區域相重疊,會被遮擋
        @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++) {
                View child = parent.getChildAt(i);
                int index = parent.getChildAdapterPosition(child);
                if (index % 5 == 0) {
                    int left = 0;
                    int top = child.getTop() - 50;
                    int right = child.getRight();
                    int bottom = child.getTop();
                    c.drawRect(left, top, right, bottom, mPaint);
                }
            }
        }


        //一樣是繪製內容,但與onDraw()的區別是:繪製在圖層的最上層
        @Override
        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
        }
    }

複製代碼

效果圖以下:

這距離咱們想要的效果越來躍進了。能不能在黃條在最上面的時候停留呢??還有就是推上去??不出所料全部的操做都是在這裏面了。 下面咱們直接上代碼,思考留給我親愛的讀者:

@Override
        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDrawOver(c, parent, state);

            // 獲取RecyclerView的Child view的個數
            int childCount = parent.getChildCount();
            // 遍歷每一個Item,分別獲取它們的位置信息,而後再繪製對應的分割線
            for (int i = 0; i < childCount; i++) {
                View child = parent.getChildAt(i);
                int index = parent.getChildAdapterPosition(child);
                if (index % 5 == 0) {
                    int item = (index) / 5;
                    if (i < 5) {
                        if (i == 1 && child.getTop() < 100) {
                            int left = 0;
                            int top = child.getTop() - 100;
                            int right = child.getRight();
                            int bottom = child.getTop() - 50;
                            c.drawRect(left, top, right, bottom, mPaint);
                            c.drawText("這是條目" + item + "視圖i是+" + i + "頂部的高低" + child.getTop() + "index++" + index, left, top + 50, textPaint);
                        } else {
                            int left = 0;
                            int top = 0;
                            int right = child.getRight();
                            int bottom = 50;
                            c.drawRect(left, top, right, bottom, mPaint);
                            if (i == 0) {
                                c.drawText("這是條目" + (item + 1) + "視圖i是+" + i + "頂部的高低" + child.getTop() + "index++" + index, left, top + 50, textPaint);
                            } else {
                                c.drawText("這是條目" + (item) + "視圖i是+" + i + "頂部的高低" + child.getTop() + "index++" + index, left, top + 50, textPaint);
                            }
                        }

                    }
                    if (i != 0) {
                        int left = 0;
                        int top = child.getTop() - 50;
                        int right = child.getRight();
                        int bottom = child.getTop();
                        c.drawRect(left, top, right, bottom, mPaint);
                        c.drawText("這是條目" + item + "視圖i是+" + i + "頂部的高低" + child.getTop() + "index++" + index, left, top + 50, textPaint);
                    }
                }
            }
        }


複製代碼

咱們的效果圖以下:

我把要打印的都給小夥伴們打印出來了。具體的優化看本身的需求優化就能夠了。

總結

本篇文章介紹了RecyclerView.ItemDecoration的使用,還有它的原理。其實仍是挺簡單的。我相信簡單的自定義小夥伴應該都會了。

相關文章
相關標籤/搜索