RecyclerView 擴展(一) - 手把手教你認識ItemDecoration

  RecyclerView源碼分析系列文章已經告一個段落了,從今天開始,我將續源碼分析系列的文章,補充RecyclerView其餘內容。這個系列的文章沒有固定性,多是源碼分析,也有多是踩坑經驗,還有多是一些自定義操做。bash

  ItemDecoration做爲RecyclerView4大相關組成部分之一,其重要性就不用我來介紹。同時,相信你們都比較熟悉它,自定義分割線就是用它來實現的。可是,你們有沒有想過,ItemDecoration是怎麼實現分割線的?它內部的原理又是什麼呢?這就是本文須要解決的問題。本文打算從以下幾個方面來介紹ItemDecoration:ide

  1. 自定義ItemDecoration
  2. ItemDecoration原理解析。

  在閱讀本文以前,建議已經對RecyclerView的三大流程有必定的瞭解,有興趣的同窗能夠參考個人文章:RecyclerView 源碼分析(一) - RecyclerView的三大流程源碼分析

1. 概述

  在介紹本文的內容以前,我先來對ItemDecoration作一個小小的概述,咱們在自定義ItemDecoration時,都知道重寫其中的兩個方法:onDrawgetItemOffsets兩個。這其中,咱們使用onDraw方法用來繪製分割線,getItemOffsets方法設置ItemView的間距。佈局

  這裏,我結合onDrawOver方法來解釋一下onDraw方法的真正做用。首先,到如今爲止,有可能還認爲onDraw方法是用來分割線。我在這裏統一解釋一下:this

方法名 做用
onDraw ItemView繪製完成以後,會調用此方法繪製。也就是說,onDraw方法繪製的內容在ItemView的上面。從這裏,咱們能夠看出來,onDraw方法的做用不只僅是繪製分割線。
onDrawOver onDraw繪製完成以後,會調用此方法進行繪製。也就是說,onDraw方法繪製的內容在onDraw方法繪製的內容上面。
getItemOffsets 設置每一個ItemView上下左右的間距。

  而分割線是怎麼繪製出來的呢?由於getItemOffsets給每一個ItemView設置間距,而onDraw剛好在這個間距裏面繪製內容,從而就造成了分割線。實際上,onDraw繪製的範圍不只僅是ItemView的間距,而是能夠在整個RecyclerView內部繪製;同時onDraw方法不是繪製分割線的惟一方法,其實onDrawOver方法也是能夠繪製分割線。只不過是,咱們一般這樣來理解,onDraw用來會分割線部分,而onDrawOver方法用來繪製最上層的內容,好比說陰影部分。spa

2. 自定義ItemDecoration

  既然手把手教你們認識ItemDecoration,自定義一個ItemDecoration天然不能少。咱們先來看一下效果: 3d

  在介紹實現以前,我簡單的描述一下這個效果,首先 ItemView有一個黃色的分割線,其次我在每一個 ItemView上面繪製了一個半透明的陰影。

  而後,咱們再來看看實現:code

public class CustomItemDecoration extends RecyclerView.ItemDecoration {

    private final Drawable mDividerDrawable;
    private final int mDivider;
    private final Drawable mShadowDrawable;

    public CustomItemDecoration(Context context) {
        mDividerDrawable = context.getResources().getDrawable(R.drawable.divider);
        mShadowDrawable = context.getResources().getDrawable(R.drawable.shadow);
        mDivider = mDividerDrawable.getIntrinsicHeight();
    }


    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        // 繪製分割線
        final int left = parent.getPaddingLeft();
        final int right = parent.getMeasuredWidth() - parent.getPaddingRight();
        final int count = parent.getChildCount();
        for (int i = 0; i < count; i++) {
            View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
            final int top = child.getBottom() + layoutParams.bottomMargin;
            final int bottom = top + mDivider;
            mDividerDrawable.setBounds(left, top, right, bottom);
            mDividerDrawable.draw(c);
        }
    }

    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        // 繪製陰影
        final int left = parent.getPaddingLeft();
        final int right = parent.getMeasuredWidth() - parent.getPaddingRight();
        final int count = parent.getChildCount();
        for (int i = 0; i < count; i++) {
            View child = parent.getChildAt(i);
            final int top = child.getTop();
            final int bottom = child.getBottom();
            mShadowDrawable.setBounds(left, top, right, bottom);
            mShadowDrawable.draw(c);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        // 設置間距
        int layoutPosition = parent.getChildViewHolder(view).getAdapterPosition();
        if (layoutPosition == parent.getAdapter().getItemCount() - 1) {
            outRect.set(0, 0, 0, 0);
        } else {
            outRect.set(0, 0, 0, mDivider);
        }
    }
}
複製代碼

  整個ItemDecoration的實現是很是簡單,從上面的代碼中,咱們能夠獲得:cdn

  1. 經過onDraw方法來繪製分割線,這其中,計算了left、top、right和bottom,而這個範圍剛好就是ItenView的間距範圍。
  2. 經過onDrawOver方法來繪製陰影,繪製的範圍剛好就在ItemView的範圍。
  3. 經過getItemOffsets方法來設置ItemView的間距。

  自定義ItemDecoration就是這麼的簡單,這裏就很少餘的介紹了,接下來咱們來看一下ItemDecoration是怎麼實現分割線的。對象

3. ItemDecoration的原理解析

  咱們要向瞭解ItemDecoration的原理,其實從下面兩個方面來了解就OK了:

  1. ItemDecoration是怎麼實現ItemView間距的。
  2. ItemDecoration是進行繪製的。

  咱們想要知道上面兩個問題的答案,就必須從RecyclerView的源碼入手。咱們先來看看間距部分。

(1). ItemDecoration怎麼實現間距?

  間距的實現,咱們主要從兩個方面去尋找答案:1.ItemView的測量;2.ItemView的佈局。

  首先咱們來看一下ItemView的測量,實現過程在LayoutManagermeasureChildWithMargins方法:

public void measureChildWithMargins(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()
                            + lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
                    canScrollHorizontally());
            final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
                    getPaddingTop() + getPaddingBottom()
                            + lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
                    canScrollVertically());
            if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
                child.measure(widthSpec, heightSpec);
            }
        }
複製代碼

  從上面的代碼中,咱們能夠看到,調用了RecyclerViewgetItemDecorInsetsForChild方法來獲取全部ItemDecoration設置的間距之和。這裏我不對getItemDecorInsetsForChild方法展開,由於很是的簡單,有興趣的同窗能夠看一下,會重點分析這個方法。得到間距之和以後,保存在一個Rect對象裏面,而後再測量ItemView時,將這部分的間距之和計算在padding以內,因此在測量ItemView,就考慮過ItemDecoration設置的間距。

  而後,咱們在來看看佈局的過程,代碼主要體如今LayoutManagerlayoutDecoratedWithMargins方法裏面:

public void layoutDecoratedWithMargins(View child, int left, int top, int right,
                int bottom) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Rect insets = lp.mDecorInsets;
            child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
                    right - insets.right - lp.rightMargin,
                    bottom - insets.bottom - lp.bottomMargin);
        }
複製代碼

  從上面的代碼中,咱們發現,在佈局ItemView時,考慮到ItemDecoration設置的間距。因此到這裏,咱們已經知道ItemDecoration是怎麼實現ItemView的間距的,那是由於在測量時和佈局時,都會考慮到ItemDecoarion設置的間距。

(2). ItemDecoration的繪製

  ItemDecoration的繪製過程主要體如今onDraw方法和onDrawOver方法裏面。而這兩個方法都是在RecyclerView的draw過程被回調的,咱們來看看代碼:

@Override
    public void draw(Canvas c) {
        super.draw(c);

        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
       // ······
    }
    @Override
    public void onDraw(Canvas c) {
        super.onDraw(c);

        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDraw(c, this, mState);
        }
    }
複製代碼

  熟悉View的三大流程的同窗應該都知道,draw方法調用super.draw(c)會執行到onDraw方法。而在onDraw方法裏面,咱們發現先是執行super.onDraw(c),這個方法主要是繪製ItemView,而後就是調用ItemDecorationonDraw方法,最後在draw方法裏面調用ItemDecorationonDrawOver方法。從這裏,咱們就知道,爲何ItemView的繪製在最下面,onDraw繪製的內容在中間,而onDrawOver繪製的內容在最上面,由於他們方法執行有順序。

4. 總結

  總的來講,ItemDecoration是很是的簡單,在這裏,我對此作一個簡單的總結。

  1. 咱們使用ItemDecoration,只須要關注它的三個方法便可,分別是:getItemOffsetsonDrawonDrawOver方法。其中getItemOffsets方法主要是給每一個ItemView設置間距,onDraw方法和onDrawOver方法都是用來繪製的,其中onDraw先繪製,其次纔是onDrawOver方法。不過兩個方法繪製的內容都是在ItemView的上面。
  2. ItemDecoration之因此可以實現間距,是由於在測量ItemView和繪製ItemView時,都考慮到ItemDecoration的存在。
相關文章
相關標籤/搜索