【Android进阶】RecyclerView之绘制流程(三)

【Android进阶】RecyclerView之绘制流程(三)

Android小彩虹2021-07-14 5:46:53170A+A-

前言

2021-01-05 修订版,源码基于 implementation 'androidx.recyclerview:recyclerview:1.1.0'

上一篇,说了RecyclerView的回收复用,这一篇,我们来说说RecyclerView的绘制流程。

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方法

      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方法设置宽高了

接着,是根据isAutoMeasureEnabledtruefalse,会走2套逻辑,通过查看源码可以发现,isAutoMeasureEnabledmAutoMeasureLayoutManager中,默认为false,但在LinearLayoutManager中为true

isAutoMeasureEnabled 为true

LinearLayoutManager相关代码

    public boolean isAutoMeasureEnabled() {
        return true;
    }

onMeasure的主要逻辑也是在isAutoMeasureEnabledtrue时,我们接着往下看

                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方法。

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

mLayoutStep默认值是 State.STEP_START即为1,关于dispatchLayoutStep1方法,其实没有必要过多分析,因为分析源码主要是对于绘制思想的理解,如果过多的纠结于每一行代码的含义,那么会陷入很大的困扰中。执行完之后,是this.mState.mLayoutStep = 2;STEP_LAYOUT状态。

接下来,是真正执行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状态。

isAutoMeasureEnabled 为false

	    if (mHasFixedSize) {
       		 //不需要重新测量
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
            // custom onMeasure
            if (mAdapterUpdateDuringMeasure) {
                startInterceptRequestLayout();
                onEnterLayoutOrScroll();
                processAdapterUpdatesAndSetAnimationFlags();
                onExitLayoutOrScroll();

                if (mState.mRunPredictiveAnimations) {
                    mState.mInPreLayout = true;
                } else {
                    // consume remaining updates to provide a consistent state with the layout pass.
                    mAdapterHelper.consumeUpdatesInOnePass();
                    mState.mInPreLayout = false;
                }
                mAdapterUpdateDuringMeasure = false;
                stopInterceptRequestLayout(false);
            } else if (mState.mRunPredictiveAnimations) {
                // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
                // this means there is already an onMeasure() call performed to handle the pending
                // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout
                // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time
                // because getViewForPosition() will crash when LM uses a child to measure.
                setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
                return;
            }

            if (mAdapter != null) {
                mState.mItemCount = mAdapter.getItemCount();
            } else {
                mState.mItemCount = 0;
            }
            startInterceptRequestLayout();
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            stopInterceptRequestLayout(false);
            mState.mInPreLayout = false; // clear

首先,当mHasFixedSize为true时不重新测量,其由setHasFixedSize方法设置

    /** * RecyclerView can perform several optimizations if it can know in advance that RecyclerView's * size is not affected by the adapter contents. RecyclerView can still change its size based * on other factors (e.g. its parent's size) but this size calculation cannot depend on the * size of its children or contents of its adapter (except the number of items in the adapter). * <p> * If your use of RecyclerView falls into this category, set this to {@code true}. It will allow * RecyclerView to avoid invalidating the whole layout when its adapter contents change. * * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView. */
    public void setHasFixedSize(boolean hasFixedSize) {
        mHasFixedSize = hasFixedSize;
    }

当Adapter内Item的改变不会影响RecyclerView宽高的时候,可以设置为true让RecyclerView避免重新计算大小。

这也是RecyclerView的内存优化方法之一

mAdapterUpdateDuringMeasure为true时,需要做测量,而mAdapterUpdateDuringMeasure是在triggerUpdateProcessor方法中设置为true的

        void triggerUpdateProcessor() {
            if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
                ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
            } else {
                mAdapterUpdateDuringMeasure = true;
                requestLayout();
            }
        }

triggerUpdateProcessor方法又被一下方法调用,

  • onItemRangeChanged
  • onItemRangeInserted
  • onItemRangeRemoved
  • onItemRangeMoved

即当调用Adapter的增删改插方法,最后就会根据mHasFixedSize这个值来判断需要不需要requestLayout()

之前也说过,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仍能显示出来的原因。

你的认可,是我坚持更新博客的动力,如果觉得有用,就请点个赞,谢谢

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们