【Android進階】RecyclerView之繪製流程(三)

前言

上一篇,說了RecyclerView的回收複用,這一篇,咱們來講說RecyclerView的繪製流程。java

onMeasure

咱們先看看RecyclerView#onMeasure()方法緩存

protected void onMeasure(int widthSpec, int heightSpec) {
        if (this.mLayout == null) {
            this.defaultOnMeasure(widthSpec, heightSpec);
        } else {
            if (!this.mLayout.isAutoMeasureEnabled()) {
                if (this.mHasFixedSize) {
                    this.mLayout.onMeasure(this.mRecycler, this.mState, widthSpec, heightSpec);
                    return;
                }

                if (this.mAdapterUpdateDuringMeasure) {
                    this.startInterceptRequestLayout();
                    this.onEnterLayoutOrScroll();
                    this.processAdapterUpdatesAndSetAnimationFlags();
                    this.onExitLayoutOrScroll();
                    if (this.mState.mRunPredictiveAnimations) {
                        this.mState.mInPreLayout = true;
                    } else {
                        this.mAdapterHelper.consumeUpdatesInOnePass();
                        this.mState.mInPreLayout = false;
                    }

                    this.mAdapterUpdateDuringMeasure = false;
                    this.stopInterceptRequestLayout(false);
                } else if (this.mState.mRunPredictiveAnimations) {
                    this.setMeasuredDimension(this.getMeasuredWidth(), this.getMeasuredHeight());
                    return;
                }

                if (this.mAdapter != null) {
                    this.mState.mItemCount = this.mAdapter.getItemCount();
                } else {
                    this.mState.mItemCount = 0;
                }

                this.startInterceptRequestLayout();
                this.mLayout.onMeasure(this.mRecycler, this.mState, widthSpec, heightSpec);
                this.stopInterceptRequestLayout(false);
                this.mState.mInPreLayout = false;
            } else {
                int widthMode = MeasureSpec.getMode(widthSpec);
                int heightMode = MeasureSpec.getMode(heightSpec);
                this.mLayout.onMeasure(this.mRecycler, this.mState, widthSpec, heightSpec);
                boolean measureSpecModeIsExactly = widthMode == 1073741824 && heightMode == 1073741824;
                if (measureSpecModeIsExactly || this.mAdapter == null) {
                    return;
                }

                if (this.mState.mLayoutStep == 1) {
                    this.dispatchLayoutStep1();
                }

                this.mLayout.setMeasureSpecs(widthSpec, heightSpec);
                this.mState.mIsMeasuring = true;
                this.dispatchLayoutStep2();
                this.mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
                if (this.mLayout.shouldMeasureTwice()) {
                    this.mLayout.setMeasureSpecs(MeasureSpec.makeMeasureSpec(this.getMeasuredWidth(), 1073741824), MeasureSpec.makeMeasureSpec(this.getMeasuredHeight(), 1073741824));
                    this.mState.mIsMeasuring = true;
                    this.dispatchLayoutStep2();
                    this.mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
                }
            }

        }
    }

複製代碼

咱們從上往下看,首先,mLayout即爲LayoutManager,若是其爲null會執行defaultOnMeasure方法ide

void defaultOnMeasure(int widthSpec, int heightSpec) {
        int width = RecyclerView.LayoutManager.chooseSize(widthSpec, this.getPaddingLeft() + this.getPaddingRight(), ViewCompat.getMinimumWidth(this));
        int height = RecyclerView.LayoutManager.chooseSize(heightSpec, this.getPaddingTop() + this.getPaddingBottom(), ViewCompat.getMinimumHeight(this));
        this.setMeasuredDimension(width, height);
    }
複製代碼

能夠看到,這裏沒有測量item的高度就直接調用setMeasuredDimension方法設置寬高了post

接着,是根據isAutoMeasureEnabledtruefalse,會走2套邏輯,經過查看源碼能夠發現,isAutoMeasureEnabledmAutoMeasureLayoutManager中,默認爲false,但在LinearLayoutManager中爲truethis

LinearLayoutManager相關代碼spa

public boolean isAutoMeasureEnabled() {
        return true;
    }
複製代碼

onMeasure的主要邏輯也是在isAutoMeasureEnabledtrue時,咱們接着往下看code

int widthMode = MeasureSpec.getMode(widthSpec);
                int heightMode = MeasureSpec.getMode(heightSpec);
                this.mLayout.onMeasure(this.mRecycler, this.mState, widthSpec, heightSpec);
                boolean measureSpecModeIsExactly = widthMode == 1073741824 && heightMode == 1073741824;
                if (measureSpecModeIsExactly || this.mAdapter == null) {
                    return;
                }
複製代碼

若是寬和高的測量值是絕對值時,直接跳過onMeasure方法。ip

if (this.mState.mLayoutStep == 1) {
                    this.dispatchLayoutStep1();
                }
複製代碼

mLayoutStep默認值是 State.STEP_START即爲1,關於dispatchLayoutStep1方法,其實沒有必要過多分析,由於分析源碼主要是對於繪製思想的理解,若是過多的糾結於每一行代碼的含義,那麼會陷入很大的困擾中。執行完以後,是this.mState.mLayoutStep = 2;STEP_LAYOUT狀態。get

接下來,是真正執行LayoutManager繪製的地方dispatchLayoutStep2源碼

private void dispatchLayoutStep2() {
        this.startInterceptRequestLayout();
        this.onEnterLayoutOrScroll();
        this.mState.assertLayoutStep(6);
        this.mAdapterHelper.consumeUpdatesInOnePass();
        this.mState.mItemCount = this.mAdapter.getItemCount();
        this.mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
        this.mState.mInPreLayout = false;
        this.mLayout.onLayoutChildren(this.mRecycler, this.mState);
        this.mState.mStructureChanged = false;
        this.mPendingSavedState = null;
        this.mState.mRunSimpleAnimations = this.mState.mRunSimpleAnimations && this.mItemAnimator != null;
        this.mState.mLayoutStep = 4;
        this.onExitLayoutOrScroll();
        this.stopInterceptRequestLayout(false);
    }
複製代碼

能夠看到,RecyclerViewitem的繪製交給了LayoutManager,即mLayout.onLayoutChildren(this.mRecycler, this.mState);,關於LayoutManager將會在下一篇中詳細介紹。

這裏執行完以後,是this.mState.mLayoutStep = 4;STEP_ANIMATIONS狀態。

以前也說過,onMeasure的主要邏輯在isAutoMeasureEnabledtrue時,那麼爲何LayoutManager中默認值爲false

若是isAutoMeasureEnabledfalseitem能正常繪製嗎?讓咱們作個嘗試

咱們重寫isAutoMeasureEnabled方法,返回false

class MyLinLayoutManager extends LinearLayoutManager {
        public MyLinLayoutManager(Context context) {
            super(context);
        }

        @Override
        public boolean isAutoMeasureEnabled() {
            return false;
        }
    }
複製代碼

而後將其設置給RecyclerView,運行時,會發現item還能正常顯示,這是爲何?這裏就要說是onLayout方法

onLayout

protected void onLayout(boolean changed, int l, int t, int r, int b) {
        TraceCompat.beginSection("RV OnLayout");
        this.dispatchLayout();
        TraceCompat.endSection();
        this.mFirstLayoutComplete = true;
    }
複製代碼

這裏的就比較簡單了,來看看dispatchLayout方法

void dispatchLayout() {
        if (this.mAdapter == null) {
            Log.e("RecyclerView", "No adapter attached; skipping layout");
        } else if (this.mLayout == null) {
            Log.e("RecyclerView", "No layout manager attached; skipping layout");
        } else {
            this.mState.mIsMeasuring = false;
            if (this.mState.mLayoutStep == 1) {
                this.dispatchLayoutStep1();
                this.mLayout.setExactMeasureSpecsFrom(this);
                this.dispatchLayoutStep2();
            } else if (!this.mAdapterHelper.hasUpdates() && this.mLayout.getWidth() == this.getWidth() && this.mLayout.getHeight() == this.getHeight()) {
                this.mLayout.setExactMeasureSpecsFrom(this);
            } else {
                this.mLayout.setExactMeasureSpecsFrom(this);
                this.dispatchLayoutStep2();
            }

            this.dispatchLayoutStep3();
        }
    }
複製代碼

能夠看到,這裏將onMeasure的主要邏輯從新執行了一遍,也解釋了以前,當咱們給RecyclerView設置固定的寬高的時候,onMeasure是直接跳過了執行,而子view仍能顯示出來的緣由。

你的承認,是我堅持更新博客的動力,若是以爲有用,就請點個贊,謝謝

相關文章
相關標籤/搜索