安卓5.1原始碼解析 : RecyclerView解析從繪製流程,ViewHolder複用機制,LayoutManger,ItemAnimator等流程全面講解

yangxi_001發表於2017-08-01

最近一直在研究安卓中幾個常用控制元件的原始碼,希望能通過學習原始碼學習到google大牛在封裝一些複雜view的思想,為以後自己造輪子提供更好的思路.

RecyclerView是一個使用者可以全面定製的元件,本文將全面分析RecyclerView的各種機制,包括viewholder複用機制,LayoutManager佈局機制,ItemAnimatoritem動畫等RecyclerView暴露給使用者的所有可以自定義的部分在原始碼中的體現.RecylerView完全區別於ListView,尤其在Item的複用方面,RecyclerView不在讓使用者關注於Item的複用,讓使用者可以更專注去處理UI上的邏輯.

關於ListView大家可以看我上一篇部落格去了解一下他的Item回收機制.

本文將從以下幾個方面對RecyclerView進行講解

注 : 其中會穿插著對LayoutManager,ItemAnimator等使用者自定義元件的分析.

onMeasure

  • RecyclerView的onMeasure方法
    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {

        ...

        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
        } else {
        //呼叫LayoutManager中的方法測量view
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        }

        mState.mInPreLayout = false; // clear
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

可以看到ReyelcerViewonMeasure這裡有個判斷 如果mLayout不為空的時候,會呼叫mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);進行測量,這個mLayout其實就是LayoutManager,負責了RecyelcerView的測量.一般來說如果我們想自定義ReyclerViewonMeasure方法,只要在setLayoutManager方法中放入自己的自定義LayoutManger就可以了,系統為我們實現了LinearLayoutManager用來擺放,而這個類裡面並沒有重寫LayoutManager的onMeasure方法,所以我們直接檢視LayoutManaer預設的測量方法看看.

下面我們來通過分析LayoutManager看一下它是怎麼進行對onMeasure的處理

  • LinearLayoutManager的onMeasure
     public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
        mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
    }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

它這裡又呼叫了recyclerView的defaultOnMeasure(widthSpec, heightSpec);預設的measure方法

  • mRecyclerView.defaultOnMeasure(widthSpec, heightSpec)
    private void defaultOnMeasure(int widthSpec, int heightSpec) {
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);
        final int widthSize = MeasureSpec.getSize(widthSpec);
        final int heightSize = MeasureSpec.getSize(heightSpec);

        int width = 0;
        int height = 0;

        switch (widthMode) {
            case MeasureSpec.EXACTLY:
            case MeasureSpec.AT_MOST:
                width = widthSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                width = ViewCompat.getMinimumWidth(this);
                break;
        }

        switch (heightMode) {
            case MeasureSpec.EXACTLY:
            case MeasureSpec.AT_MOST:
                height = heightSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                height = ViewCompat.getMinimumHeight(this);
                break;
        }

        setMeasuredDimension(width, height);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

很熟悉的程式碼,主要就是對RecyclerView根據測量模式進行測量,最後通過setMeasuredDimension(width, height);給成員變數measuredWidthmeasuredHeight賦值.可是這時候就會有疑惑,這裡並沒有看到對子view的測量,ListView在這裡就會對子view進行測量了,為什麼RecyclerView沒有,難道是我們分析錯了嗎?我們接著往下看…..

onLayout

我們看下ReyclerView的onLayout方法

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        eatRequestLayout();
        //分發Layout事件
        dispatchLayout();
        resumeRequestLayout(false);
        mFirstLayoutComplete = true;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

這裡有個dispatchLayout方法,根據方法名我們可以猜到應該是給子控制元件分發layout事件的方法,我們點進去看看

  • dispatchLayout(); 進行佈局的方法
    void dispatchLayout() {
        //第一次onLayout的時候mAdapter 和 mLayout肯定為空,所以不會有下面的邏輯,只有當我們呼叫setAdapter,或者其他第二次
        //重繪的方法,才會繼續下面的邏輯
        if (mAdapter == null) {
            Log.e(TAG, "No adapter attached; skipping layout");
            return;
        }
        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            return;
        }

        //這裡通過我們傳入的Adapter的getItemCount方法拿到了Item的個數
        mState.mItemCount = mAdapter.getItemCount();

        ....進行些佈局前的初始化操作...

        // Step 2: Run layout
        //開始layout
        mState.mInPreLayout = false;
        //具體怎麼佈局,會呼叫LayoutManager裡面的方法進行佈局
        mLayout.onLayoutChildren(mRecycler, mState);

        ....後面是對ItemAnimator動畫的執行,我們後面再講解...

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

可以看出dispatchLayout()最後還是會呼叫mLayout.onLayoutChildren(mRecycler, mState);,我們上面說過mLayout就是我們傳遞入的LayoutManager,呼叫LayoutManageronLayoutChildren進行佈局,我們去看看LinearLayoutManager的onLayoutChildren是如何進行佈局的.

  • LinearLayoutManager.onLayoutChildren

關於佈局錨點: onLayoutChildren中會先確認佈局錨點mAnchor,然後從佈局錨點為開始位置,以此為起點向開始和結束方向填充ItemView.

mAnchor包含了子控制元件在Y軸上起始繪製偏移量(coordinate),ItemView在Adapter中的索引位置(position)和佈局方向

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
         // layout algorithm:   佈局演算法
        // 1) by checking children and other variables, find an anchor coordinate and an anchor
        //  item position.      
        //通過檢查孩子和其他變數,找到錨座標和錨點專案位置   mAnchor為佈局錨點 
        //mAnchor包含了子控制元件在Y軸上起始繪製偏移量(coordinate),ItemView在Adapter中的索引位置(position)和佈局方向(mLayoutFromEnd)
        // 2) fill towards start, stacking from bottom ,  開始填充, 從底部堆疊    從開始位置開始,向結束為位置堆疊填充itemView
        // 3) fill towards end, stacking from top    結束填充,從頂部堆疊
        // 4) scroll to fulfill requirements like stack from bottom.  //滾動以滿足堆疊從底部的要求
        // create layout state

        ...
        //這個方法會根據LinearLayoutManger構造中傳入的佈局方向給mShouldReverseLayout賦值
        //如果是豎直方向(VERTICAL),mShouldReverseLayout為false
        resolveShouldLayoutReverse();
        //重置mAnchorInfo
        mAnchorInfo.reset();
        //得到堆疊方向   mShouldReverseLayout為false   mStackFromEnd預設為false
        //我們假定傳入的方向是垂直方向,所以mAnchorInfo.mLayoutFromEnd = false
        mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;

        ...

        //重要的堆疊Item的方法,根據堆疊方向進行堆疊
        //如果是end方向    從底部開始堆疊
        if (mAnchorInfo.mLayoutFromEnd) {  
            // fill towards start  // 開始填充
            updateLayoutStateToFillStart(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
            if (mLayoutState.mAvailable > 0) {
                extraForEnd += mLayoutState.mAvailable;
            }
            // fill towards end    //結束填充
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
        } else { 
            //如果排列方向是VERTICAL,走這裡
            // fill towards end      //結束填充
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
            //重要的填充方法
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
            if (mLayoutState.mAvailable > 0) {
                extraForStart += mLayoutState.mAvailable;
            }
            // fill towards start      //開始填充
            updateLayoutStateToFillStart(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            //重要的填充方法
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

上面的程式碼,會計算mAnchorInfo.mLayoutFromEnd的值,這個值是通過mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;進行計算的, mShouldReverseLayout的值在resolveShouldLayoutReverse();中獲取,其中會根據佈局朝向去給mShouldReverseLayout賦值,如果佈局朝向是VERTICAL,就為false,反之true.mStackFromEnd是通過public void setStackFromEnd(boolean stackFromEnd)方法進行賦值,這個方法需要呼叫者呼叫,我們一般不呼叫,所以為初始值false.所以根據或運算,如果是豎直方向mAnchorInfo.mLayoutFromEnd為false.

得到了佈局方向,就會呼叫相應的邏輯進行佈局,最後填充的方法為fill.


Item測量,Item佈局

  • fill
    //具體的填充方法
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        ...
        while (remainingSpace > 0 && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            //填充核心方法,從複用集合中取ViewHolder
            layoutChunk(recycler, state, layoutState, layoutChunkResult);

            ... 
            //向複用集合中存ViewHolder
            recycleByLayoutState(recycler, layoutState);
        }         
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

fill方法中核心的填充方法layoutChunk,他會先從快取中取ViewHolder,如果沒有,就回去建立,之後會將建立好的ViewHolder放入複用集合中.我們先看layoutChunk如何填充的

  • layoutChunk

這個方法就是核心的佈局方法,layoutState.next(recycler);是從快取機制從取Item的具體方法,這個我們下面會說到.

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
            //獲取ItemView,會先從Scrap中取,如果沒有會從一級快取,二級快取取,最後檢查ReyclerViewPool 如果都沒有 就建立ViewHolder
        View view = layoutState.next(recycler);
        if (view == null) {
            if (DEBUG && layoutState.mScrapList == null) {
                throw new RuntimeException("received null view when unexpected");
            }
            // if we are laying out views in scrap, this may return null which means there is
            // no more items to layout.
            result.mFinished = true;
            return;
        }
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        //如果mScrapList為空,就將view填充進去,這個mScrapList就是Item被移除螢幕被快取起來的集合,如果沒有在mScrapList中
        //說明需要新增到RecyerView顯示介面中
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                    //addView  就是 viewGroup的addView方法  將子view填充到RecylerView中
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        //測量子控制元件的大小
        measureChildWithMargins(view, 0, 0);
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        int left, top, right, bottom;
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = layoutState.mOffset - result.mConsumed;
            } else {
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } else {
            top = getPaddingTop();
            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = layoutState.mOffset - result.mConsumed;
            } else {
                left = layoutState.mOffset;
                right = layoutState.mOffset + result.mConsumed;
            }
        }
        // We calculate everything with View's bounding box (which includes decor and margins)
        // To calculate correct layout position, we subtract margins.
        //佈局子view,這個方法裡面呼叫了child.layout方法,引數就是計算出來的child位置.
        layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
                right - params.rightMargin, bottom - params.bottomMargin);
        if (DEBUG) {
            Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
                    + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
                    + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
        }
        // Consume the available space if the view is not removed OR changed
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.isFocusable();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

這個方法會先從RecyelrVIew快取中View,然後判斷layoutState.mScrapList是否為null,如果為空,就表示這個view不在移除螢幕的位置,就要進行填充,呼叫addView方法,將view填充進來,這個方法內部就式呼叫viewGroup的addView方法.

之後呼叫measureChildWithMargins(view, 0, 0);對view進行測量,這就是為什麼在RecyerlView的構造方法中沒有看到對子view的測量,原來在這裡測量.

之後呼叫layoutDecorated對view進行layout佈局, 這個方法裡面就是呼叫了child.layout方法對控制元件進行佈局.

到了這裡,recycleView的填充就此結束了,所有應該在recycleView可見區域的view就被填充到螢幕上了.

ItemDecoraction

我們一般通過addItemDecoration對分割線進行繪製,谷歌為我們實現的DividerItemDecoration,其實內部就算呼叫了系統ListView的分割線樣式進行繪製,在ItemDecoration的onDraw方法中繪製分割線,我們就來研究下這個ItemDecoration在原始碼中的體現.

我們都知道一個view的繪製是通過draw方法開始的,所以我們從draw方法查詢他的痕跡.

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

        final int count = mItemDecorations.size();
        //ItemDecoration 的數量
        for (int i = 0; i < count; i++) {
            //呼叫ItemDecoration 的onDrawOver 方法繪製ReyclervView的背景   
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
       ...
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

darw方法中呼叫ItemDecorationonDrawOver,可以看到這個方法是在super.draw(c);執行完畢後呼叫的,根據View的繪製邏輯,在draw方法呼叫過後,表示系統的繪製流程已經結束了,也就是說這個onDrawOver是在view的繪製流程全部結束以後呼叫的方法.

如果看過View的繪製流程,我們知道在super.draw(c)方法中,會呼叫onDraw方法進行繪製,這個一般才是繪製內容的方法,我們找一下有沒有重寫這個方法.

  • onDraw
    @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);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

看到了嗎,這裡才是真正呼叫ItemDecorationonDraw方法的地方,在這個地方,我們就可以呼叫ItemDecoration的onDraw方法繪製分割線了.

但是還有個方法需要我們注意,如果我們想要自定義Item的間距怎麼辦,我們知道ItemDecoration中有個getItemOffSets方法可以自定義間距,那麼這個方法是怎麼發生作用的呢?

根據View的繪製流程,我們猜想,如果要給view新增邊距,那麼一定會在測量view的時候對padding,margin進行賦值,我們回到剛才的核心填充方法layoutChunk中對Item測量的方法measureChildWithMargins(view, 0, 0);.

  • measureChildWithMargins
    public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        //用來修改Item的邊距 ,也就是說在child.measure之前會先設定好邊距
        final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
        ....
        child.measure(widthSpec, heightSpec);
   }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • getItemDecorInsetsForChild
    Rect getItemDecorInsetsForChild(View child) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (!lp.mInsetsDirty) {
            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);
            //這裡可以通過ItemDecoration 修改邊距
            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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

這樣是不是感覺豁然開朗了,原來是這裡會呼叫getItemOffsets拿到間距,這樣就實現了使用者自定義佈局邊距.

ReyclerView的滑動以及ViewHolder機制

如果看過我上一篇ListView解析,一定會記得ListView就是在滑動過程中完成的對Item的回收,這裡我們將通過對RecyclerView的滑動講解,進一步的分析ReyclerView中重要的ViewHolder機制.

在研究原始碼之前,我們先了解下RecyclerView的滑動狀態 : RecyclerView的滑動過程可以分為2個階段,手指在螢幕上移動,使RecyclerView滑動的過程,可以稱為scroll;手指離開螢幕,RecyclerView繼續滑動一段距離的過程,可以稱為fling。

先看scroll過程,手指沒有離開介面,還在滑動過程中

  • onTouchEvent

既然還沒有離開介面,那一定在ACITON_MOVE中

    case MotionEvent.ACTION_MOVE: {
        final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
        if (index < 0) {
            Log.e(TAG, "Error processing scroll; pointer index for id " +
                    mScrollPointerId + " not found. Did any MotionEvents get skipped?");
            return false;
        }

        final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
        final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
        if (mScrollState != SCROLL_STATE_DRAGGING) {
            //計算出手指移動距離
            final int dx = x - mInitialTouchX;
            final int dy = y - mInitialTouchY;
            boolean startScroll = false;
            if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
                //mTouchSlop  滑動閥值
                mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
                startScroll = true;
            }
            if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
                mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
                startScroll = true;
            }
            if (startScroll) {
                //如果滑動距離大於 閥值得話, scrollState 就為SCROLL_STATE_DRAGGING
                setScrollState(SCROLL_STATE_DRAGGING);
            }
        }
        if (mScrollState == SCROLL_STATE_DRAGGING) {
            final int dx = x - mLastTouchX;
            final int dy = y - mLastTouchY;
            //滑動   第一階段scroll完成
            if (scrollByInternal(
                    canScrollHorizontally ? -dx : 0, canScrollVertically ? -dy : 0)) {
                getParent().requestDisallowInterceptTouchEvent(true);
            }
        }
        mLastTouchX = x;
        mLastTouchY = y;
    } break;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

這個方法中會進行一些對滑動的判斷,只要滑動有效,就會呼叫scrollByInternal方法.

  • scrollByInternal
     //滑動
    boolean scrollByInternal(int x, int y) {
        int overscrollX = 0, overscrollY = 0;
        int hresult = 0, vresult = 0;
        consumePendingUpdateOperations();
        if (mAdapter != null) {
            eatRequestLayout();
            mRunningLayoutOrScroll = true;
            if (x != 0) {
                //呼叫LayoutManager的scrollHorzontallBy方法
                hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
                overscrollX = x - hresult;
            }
            if (y != 0) {
                //呼叫LayoutManager的scrollVerticallyBy方法
                vresult = mLayout.scrollVerticallyBy(y, mRecycler, mState);
                overscrollY = y - vresult;
            }
            ...
        }
        ....
        return hresult != 0 || vresult != 0;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

可以看到這裡通過對滑動方向的判斷呼叫相對應的滑動方法,如果是我們是垂直滑動的話,會呼叫mLayout.scrollVerticallyBy(y, mRecycler, mState);方法,也就說具體的滑動邏輯也是由LayoutManager處理的.

我們來看看LinearLayoutManagerscrollVerticallyBy方法

  • scrollVerticallyBy
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        if (mOrientation == HORIZONTAL) {
            return 0;
        }
        return scrollBy(dy, recycler, state);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

呼叫scrollBy(dy, recycler, state);方法

    int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getChildCount() == 0 || dy == 0) {
            return 0;
        }
        mLayoutState.mRecycle = true;
        ensureLayoutState();
        final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        final int absDy = Math.abs(dy);
        updateLayoutState(layoutDirection, absDy, true, state);
        final int freeScroll = mLayoutState.mScrollingOffset;
        final int consumed = freeScroll + fill(recycler, mLayoutState, state, false);
        if (consumed < 0) {
            if (DEBUG) {
                Log.d(TAG, "Don't have any more elements to scroll");
            }
            return 0;
        }
        final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
        //如上文所講到的fill()方法,作用就是向可繪製區間填充ItemView
        //,那麼在這裡,可繪製區間就是滑動偏移量!再看方法mOrientationHelper.offsetChildren()作用就是平移ItemView。
        //平移ItemView。  這裡就完成了移動
        mOrientationHelper.offsetChildren(-scrolled);
        if (DEBUG) {
            Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
        }
        return scrolled;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

看到了嗎,這個方法中,會呼叫fill方法對向前介面的Item進行填充,最後再對Item進行平移,這裡我們回到fill方法,我們回一下其中的核心填充方法layoutChunk,其中有這麼一行View view = layoutState.next(recycler);,這個方法會實現從RecyclerView的複用機制中獲取View,我們這裡研究下這個方法是怎麼實現的,這個方法內部會呼叫getViewForPosition方法進行具體的獲取操作.

  • getViewForPosition

這裡有這麼幾個需要注意的複用物件 :

<1>scrapped : 從RecyclerView中刪除的view

<2>cached : 是ItemView的一級快取,cached集合的大小預設為2

<3>exCached : 是ItemView的二級快取,exCached是需要我們通過RecyclerView.ViewCacheExtension自己實現的,預設沒有

<4>recycled : 集合其實是一個Map,定義在RecyclerView.RecycledViewPool中將ItemView以ItemType分類儲存了下來,這裡算是RecyclerView設計上的亮點,通過RecyclerView.RecycledViewPool可以實現在不同的RecyclerView之間共享ItemView,只要為這些不同RecyclerView設定同一個RecyclerView.RecycledViewPool就可以了。

    //獲取某個位置需要展示的View,先檢查是否有可複用的View,沒有則建立新View並返回。具體過程為:  
    //step1 檢查mChangedScrap,若匹配到則返回相應holder  
    //step2 檢查AttachedScrap,若匹配到且holder有效則返回相應holder  
    //step3 查mViewCacheExtension,若匹配到則返回相應holder  
    //step4 檢查mRecyclerPool,若匹配到則返回相應holder  
    //step5 否則執行Adapter.createViewHolder(),新建holder例項  
    //step6 返回holder.itemView  
    //step7 注:以上每步匹配過程都可以匹配position或itemId(如果有stableId)
    public View getViewForPosition(int position) {
        return getViewForPosition(position, false);
    }

    //根據列表位置獲取ItemView,先後從scrapped、cached、exCached、recycled
    //集合中查詢相應的ItemView,如果沒有找到,就建立(Adapter.createViewHolder()),最後與資料集繫結。
    View getViewForPosition(int position, boolean dryRun) {
        if (position < 0 || position >= mState.getItemCount()) {
            throw new IndexOutOfBoundsException("Invalid item position " + position
                    + "(" + position + "). Item count:" + mState.getItemCount());
        }
        boolean fromScrap = false;
        ViewHolder holder = null;
        //先後從scrapped、cached、exCached、recycled集合中查詢相應的ItemView
        // 0) If there is a changed scrap, try to find from there
        if (mState.isPreLayout()) {
            //檢查mChangedScrap,若匹配到則返回相應holder  
            holder = getChangedScrapViewForPosition(position);
            fromScrap = holder != null;
        }
        // 1) Find from scrap by position 
        if (holder == null) {
            //檢查AttachedScrap,若匹配到且holder有效則返回相應holder  
            holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
            if (holder != null) {
                if (!validateViewHolderForOffsetPosition(holder)) {
                    // recycle this scrap
                    if (!dryRun) {
                        // we would like to recycle this but need to make sure it is not used by
                        // animation logic etc.
                        holder.addFlags(ViewHolder.FLAG_INVALID);
                        if (holder.isScrap()) {
                            removeDetachedView(holder.itemView, false);
                            holder.unScrap();
                        } else if (holder.wasReturnedFromScrap()) {
                            holder.clearReturnedFromScrapFlag();
                        }
                        recycleViewHolderInternal(holder);
                    }
                    holder = null;
                } else {
                    fromScrap = true;
                }
            }
        }
        if (holder == null) {
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
                throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                        + "position " + position + "(offset:" + offsetPosition + ")."
                        + "state:" + mState.getItemCount());
            }

            final int type = mAdapter.getItemViewType(offsetPosition);
            // 2) Find from scrap via stable ids, if exists
            if (mAdapter.hasStableIds()) {
                //一級快取,先檢查一級快取, 如果有就返回viewHolder
                holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                if (holder != null) {
                    // update position
                    holder.mPosition = offsetPosition;
                    fromScrap = true;
                }
            }
            if (holder == null && mViewCacheExtension != null) {
                // We are NOT sending the offsetPosition because LayoutManager does not
                // know it.
                //二級快取, 是開發者自定義的快取, 從二級快取中拿到ItemView
                final View view = mViewCacheExtension
                        .getViewForPositionAndType(this, position, type);
                if (view != null) {
                    holder = getChildViewHolder(view);
                    if (holder == null) {
                        throw new IllegalArgumentException("getViewForPositionAndType returned"
                                + " a view which does not have a ViewHolder");
                    } else if (holder.shouldIgnore()) {
                        throw new IllegalArgumentException("getViewForPositionAndType returned"
                                + " a view that is ignored. You must call stopIgnoring before"
                                + " returning this view.");
                    }
                }
            }
            if (holder == null) { // fallback to recycler
                // try recycler.
                // Head to the shared pool.
                if (DEBUG) {
                    Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
                            + "pool");
                }
                //檢查mRecyclerPool,若匹配到則返回相應holder  
                holder = getRecycledViewPool()
                        .getRecycledView(mAdapter.getItemViewType(offsetPosition));
                if (holder != null) {
                    holder.resetInternal();
                    if (FORCE_INVALIDATE_DISPLAY_LIST) {
                        invalidateDisplayListInt(holder);
                    }
                }
            }
            if (holder == null) {
                //否則執行Adapter.createViewHolder(),新建holder例項  
                holder = mAdapter.createViewHolder(RecyclerView.this,
                        mAdapter.getItemViewType(offsetPosition));
                if (DEBUG) {
                    Log.d(TAG, "getViewForPosition created new ViewHolder");
                }
            }
        }
        boolean bound = false;
        if (mState.isPreLayout() && holder.isBound()) {
            // do not update unless we absolutely have to.
            holder.mPreLayoutPosition = position;
        } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
            if (DEBUG && holder.isRemoved()) {
                throw new IllegalStateException("Removed holder should be bound and it should"
                        + " come here only in pre-layout. Holder: " + holder);
            }
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            mAdapter.bindViewHolder(holder, offsetPosition);
            attachAccessibilityDelegate(holder.itemView);
            bound = true;
            if (mState.isPreLayout()) {
                holder.mPreLayoutPosition = position;
            }
        }

        final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        final LayoutParams rvLayoutParams;
        if (lp == null) {
            rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else if (!checkLayoutParams(lp)) {
            rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else {
            rvLayoutParams = (LayoutParams) lp;
        }
        rvLayoutParams.mViewHolder = holder;
        rvLayoutParams.mPendingInvalidate = fromScrap && bound;
        //返回holder.itemView  
        return holder.itemView;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150

上面註釋的已經十分清楚,就是從RecyclerView的幾個快取中去獲取,一級一級向下取,最後如果沒有,這時候會呼叫AdaptercreateViewHolder來建立ViewHolder,這個就是自己建立的ViewHolder,最後這個方法返回ViewHolder中存放的ItemView就拿到了每個Item的View物件.

如果有向快取中取,那麼一定有存,我們來看看這個複用機制是怎麼存的,在fill中,有這麼一個方法在layoutChunk執行之後,recycleByLayoutState,這個方法向裡面一直呼叫,最終會有個recycleView方法,看名字真有那麼點意思,裡面最後呼叫了recycleViewHolderInternal方法,這個就是RecyclerView最終呼叫的新增方法,我們一起來分析看看.

  • recycleViewHolderInternal
    void recycleViewHolderInternal(ViewHolder holder) {
        ...

        if (forceRecycle || holder.isRecyclable()) {
            boolean cached = false;
            if (!holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved()) &&
                    !holder.isChanged()) {
                // Retire oldest cached view
                final int cachedViewSize = mCachedViews.size();
                //首先判斷cachedView 是否滿了   最大mViewCacheMax = 2
                ////如果已満就從cached集合中移出一個到recycled集合中去,再把新的ItemView新增到cached集合
                if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
                    //如果滿了 就從cachedViews中移除一個, 
                    recycleCachedViewAt(0);
                }

                if (cachedViewSize < mViewCacheMax) {
                    //再把新的ItemView新增到cached集合
                    mCachedViews.add(holder);
                    cached = true;
                }
            }

            if (!cached) {
                //如果沒有被快取  快取到recycleViewPool中
                addViewHolderToRecycledViewPool(holder);
            }
        } else if (DEBUG) {
            Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
                    + "re-visit here. We are stil removing it from animation lists");
        }
        // even if the holder is not removed, we still call this method so that it is removed
        // from view holder lists.
        mState.onViewRecycled(holder);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

這個註釋分析的很清楚,就是對ViewHolder的一個儲存過程.到這裡,就將RecyclerView的複用機制分析完成.

RecyclerView動畫

RecyclerView定義了4種針對資料集的操作,分別是ADD、REMOVE、UPDATE、MOVE,封裝在了AdapterHelper.UpdateOp類中,並且所有操作由一個大小為30的物件池管理著。當我們要對資料集作任何操作時,都會從這個物件池中取出一個UpdateOp物件,放入一個等待佇列中,最後呼叫 
RecyclerView.RecyclerViewDataObserver.triggerUpdateProcessor()方法,根據這個等待佇列中的資訊,對所有子控制元件重新測量、佈局並繪製且執行動畫。以上就是我們呼叫Adapter.notifyItemXXX()系列方法後發生的事。

我們以remove操作為例 :

  • notifyItemRemove()
    public final void notifyItemRemoved(int position) {
         mObservable.notifyItemRangeRemoved(position, 1);
    }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

呼叫被觀察者的notifyItemRangeRemoved方法,我們到RecyclerView的被觀察者AdapterDataObservable中看看.

    public void notifyItemRangeRemoved(int positionStart, int itemCount) {
        // since onItemRangeRemoved() is implemented by the app, it could do anything, including
        // removing itself from {@link mObservers} - and that could cause problems if
        // an iterator is used on the ArrayList {@link mObservers}.
        // to avoid such problems, just march thru the list in the reverse order.
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

這裡呼叫了觀察者RecyclerViewDataObserveronItemRangeRemoved的方法

    @Override
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        assertNotInLayoutOrScroll(null);
        //第一步  將 removed資訊放到集合中,物件為UpdateOp
        if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
    void triggerUpdateProcessor() {
        if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
            ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
        } else {
            mAdapterUpdateDuringMeasure = true;
            //介面重新佈局,也就式呼叫 RecyclerView的onLayout方法
            requestLayout();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

這裡我們又回到了onLayout方法中,還記得我們之前省略過一部分嗎,就是在那裡實現的

  • dispatchLayout
    void dispatchLayout() {

        ... 上面為填充方法,已經分析過....

        if (mState.mRunSimpleAnimations) {

            //removed動畫
            int preLayoutCount = mState.mPreLayoutHolderMap.size();
            for (int i = preLayoutCount - 1; i >= 0; i--) {
                ViewHolder itemHolder = mState.mPreLayoutHolderMap.keyAt(i);
                if (!mState.mPostLayoutHolderMap.containsKey(itemHolder)) {
                    ItemHolderInfo disappearingItem = mState.mPreLayoutHolderMap.valueAt(i);
                    mState.mPreLayoutHolderMap.removeAt(i);

                    View disappearingItemView = disappearingItem.holder.itemView;
                    mRecycler.unscrapView(disappearingItem.holder);
                    //執行動畫 remove動畫
                    animateDisappearance(disappearingItem);
                }
            }

            ...其他動畫操作...
         }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

這裡呼叫了animateDisappearance執行removed動畫

    private void animateDisappearance(ItemHolderInfo disappearingItem) {
        View disappearingItemView = disappearingItem.holder.itemView;
        addAnimatingView(disappearingItem.holder);
        int oldLeft = disappearingItem.left;
        int oldTop = disappearingItem.top;
        int newLeft = disappearingItemView.getLeft();
        int newTop = disappearingItemView.getTop();
        if (oldLeft != newLeft || oldTop != newTop) {
            disappearingItem.holder.setIsRecyclable(false);
            disappearingItemView.layout(newLeft, newTop,
                    newLeft + disappearingItemView.getWidth(),
                    newTop + disappearingItemView.getHeight());
            if (DEBUG) {
                Log.d(TAG, "DISAPPEARING: " + disappearingItem.holder +
                        " with view " + disappearingItemView);
            }
            if (mItemAnimator.animateMove(disappearingItem.holder, oldLeft, oldTop,
                    newLeft, newTop)) {
                postAnimationRunner();
            }
        } else {
            if (DEBUG) {
                Log.d(TAG, "REMOVED: " + disappearingItem.holder +
                        " with view " + disappearingItemView);
            }
            disappearingItem.holder.setIsRecyclable(false);
            //remove動畫,這就進行了動畫執行,這裡的動畫就是使用者自定義的實現方式
            if (mItemAnimator.animateRemove(disappearingItem.holder)) {
                postAnimationRunner();
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

這裡最後呼叫了mItemAnimator.animateRemove(disappearingItem.holder)方法,如果自定義過動畫的朋友一定會知道,我們通過整合ItemAnimator,重寫它的animateXXX(ViewHolder holder) 中拿到holder.itemView 就能通過這個View進行動畫操作了.

到這裡,所有關於RecyclerView的原始碼機械就到這裡結束了,我們可以看出,RecyclerView相對於ListView有更好的模組化,更加低耦合,讓使用者可以通過它提供的各種介面遊刃有餘的自定義RecyclerView的各種樣式,並且考慮到使用者根本沒有必要對View的複用有所關注所以加入了ViewHolder機制,這種思想真是值得我們深入學習.

後面我還會對ViewPager,Behavor 進行原始碼解析,希望通過這種方式能夠對自定義View有更深入的理解.

轉自:http://blog.csdn.net/hfyd_/article/details/53910631

相關文章