Android事件分发和View绘制流程分析(三)

本篇就Android中View的测量整体流程简单记录如下<下面涉及到的Android源码均为API23的源码>

二、Android中View树的绘制

对于View树的绘制起始点可以追溯到在ActivityThread的performResumeActivity()–>WindowManagerGloble.addView()–>new RootViewImpl()+root.setView(DecorView)–>RootViewImpl.setView()—->RootViewImpl.requestLayout()以上步骤在第一篇文章中均可以找到,最终调用到RootViewImpl.requestLayout()方法

下面我们就从RootViewImpl.requestLayout()开始:

2.1、RootViewImpl.requestLayout()的代码,最终会调用到performTraversals(),其简单的代码逻辑如下:

    @ViewRootImpl.java  @Override//-----------1、在setView()中有调用该方法,开始进行视图绘制----------public void requestLayout() {    if (!mHandlingLayoutInLayoutRequest) {        checkThread();        mLayoutRequested = true;        scheduleTraversals();//schedule:安排 Traversals:穿越    }}//---------------2、异步调用绘制视图 Choreographer:Coordinates the timing of animations, input and drawing.--------- void scheduleTraversals() {    if (!mTraversalScheduled) {        mTraversalScheduled = true;        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();        //--------***mTraversalRunnable 是一个TraversalRunnable类型可执行对象,视图绘制专用线程***-------------        mChoreographer.postCallback(                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);        if (!mUnbufferedInputDispatch) {            scheduleConsumeBatchedInput();        }        notifyRendererOfFramePending();        pokeDrawLockIfNeeded();    }}//-------------3、TraversalRunnable定义-------------------    @ViewRootImpl.java  final class TraversalRunnable implements Runnable {    @Override    public void run() {        //在该处调用doTraversal()        doTraversal();    }}//--------------4、mTraversalRunnable初始化---------------final TraversalRunnable mTraversalRunnable = new TraversalRunnable();//--------------5、一般常见的View开始绘制的调用performTraversals();在此方法中调用--------------void doTraversal() {    if (mTraversalScheduled) {        mTraversalScheduled = false;        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);        if (mProfile) {            Debug.startMethodTracing("ViewAncestor");        }        performTraversals();//---------6.performTraversals()----        if (mProfile) {            Debug.stopMethodTracing();            mProfile = false;        }    }}

2.2 我们下面看performTraversals()的具体实现,该部分代码是在ViewRootImpl。实现如下:

2.2.1 从2.1中调用到 performTraversals()
2.2.2 把mWindowAttributes赋值给lp,此处的lp会在下面合成DecorView的测量模式时使用。mWindowAttributes实际就是当前从当前的PhoneWindow的参数中传递过来的,而PhoneWindow中的该值是在其Window初始化话时赋值的,其默认宽高都是LayoutParams.MATCH_PARENT 具体代码在Window中,代码如下:
@Window private final WindowManager.LayoutParams mWindowAttributes =    new WindowManager.LayoutParams();@WindowManager.LayoutParams//LayoutParams 的默认构造函数实现如下,可知其宽高默认都是LayoutParams.MATCH_PARENT的public LayoutParams() {        super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);        type = TYPE_APPLICATION;        format = PixelFormat.OPAQUE;    }
2.2.3 初始化测量开始时DecorView所需的具体size大小,默认为实际屏幕的宽高
2.2.4 该处最终会回调到Activity的onAttatchWindow()
2.2.5 mApplyInsetsRequested该值是在requestFitSystemWindows()方法中设置的,如果该值满足为true,则会执行2.2.6,否则跳过2.2.6

我们在View中调用的requestFitSystemWindows()最终会调用到ViewRootImpl中的requestFitSystemWindows() 并设置mApplyInsetsRequested 为true,大致是在设置了Window的属性之后例如SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 属性之后应该调用一次该方法,以保证得到我们想要的效果

2.2.6 在此处调用measureHierarchy(),此处的lp为WindowManager.LayoutParams desiredWindowWidth为 屏幕宽度,该方法最终会调用到 ViewRootImpl的performMeasure(),和在2.2.8处调用的是同一个方法
2.2.7 生成顶级DecorView的MeasureSpec,默认是Mode为:EXACTLY Size:屏幕相应的宽高
2.2.8 performMeasure() 正式测量确认各子View的尺寸,会回调所有View的onMeasure()方法确定其大小。(前面2.2.6处的执行依赖2.2.5中mApplyInsetsRequested的值不一定会执行)
2.2.9 如果Window的Layoutparams中设置的有权重值则会根据权重再次测量,一般也不会执行
2.2.10 测量完毕继续调用执行layout,此时则会回调所有ViewGroup及View中的onLayout(),即我们常说的onLayout()方法
2.2.11 布局完之后开始调用performDraw(),最终会回调到所有可绘制View的onDraw()
 @ViewRootImpl.java private void performTraversals() {//---------2.2.1 经过上面的逻辑最终会调用到此处performTraversals()    // cache mView since it is used so much below...    final View host = mView;    WindowManager.LayoutParams lp = mWindowAttributes;//------2.2.2下面measureHierarchy()方法中调用的getRootMeasureSpec()方法参数中的lp 实际即为当前Window的LayoutParams    int desiredWindowWidth;//下面measureHierarchy()方法中调用的getRootMeasureSpec()方法参数中宽度大小,实际即为屏幕的宽    int desiredWindowHeight;//下面measureHierarchy()方法中调用的getRootMeasureSpec()方法参数中高度大小,实际即为屏幕的高    ...    if(mFirst){        ....        //----2.2.3初始化desiredWindowWidth和desiredWindowHeight 对应下面measureHierarchy()第一个参数初始化位置;对于普通Activity即为屏幕的宽高        if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL                        || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {                    // NOTE -- system code, won't try to do compat mode.                    Point size = new Point();                    mDisplay.getRealSize(size);                    desiredWindowWidth = size.x;                    desiredWindowHeight = size.y;                } else {                    DisplayMetrics packageMetrics =                        mView.getContext().getResources().getDisplayMetrics();                    desiredWindowWidth = packageMetrics.widthPixels;                    desiredWindowHeight = packageMetrics.heightPixels;                }        ....        host.dispatchAttachedToWindow(mAttachInfo, 0);//------2.2.4、该处最终会回调到Activity的onAttatchWindow()    }    ...    if (mApplyInsetsRequested) {//-----2.2.5 mApplyInsetsRequested该值是在requestFitSystemWindows()方法中设置的,我们在View中调用的requestFitSystemWindows()最终会调用到ViewRootImpl中的requestFitSystemWindows() 并设置mApplyInsetsRequested 为true,大致是在设置了Window的属性之后例如SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 属性之后需要调用一次该方法        mApplyInsetsRequested = false;        mLastOverscanRequested = mAttachInfo.mOverscanRequested;        dispatchApplyInsets(host);        if (mLayoutRequested) {            // Short-circuit catching a new layout request here, so            // we don't need to go through two layout passes when things            // change due to fitting system windows, which can happen a lot.            //----------2.2.6在此处调用measureHierarchy()开始measure 此处的lp为WindowManager.LayoutParams desiredWindowWidth为 屏幕宽度 desiredWindowHeight为屏幕高度---------            windowSizeMayChange |= measureHierarchy(host, lp,                    mView.getContext().getResources(),                    desiredWindowWidth, desiredWindowHeight);        }    }    ...     if (!mStopped || mReportNextDraw) {            boolean focusChangedDueToTouchMode = ensureTouchModeLocally(                    (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()                    || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {                -----2.2.7拿取根部局的MeasureSpec-----                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);//---1.拿取根部局的MeasureSpec---                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);//---2.拿取根部局的MeasureSpec---                if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed!  mWidth="                        + mWidth + " measuredWidth=" + host.getMeasuredWidth()                        + " mHeight=" + mHeight                        + " measuredHeight=" + host.getMeasuredHeight()                        + " coveredInsetsChanged=" + contentInsetsChanged);                 // Ask host how big it wants to be                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//------2.2.8正式测量确认各子View的尺寸------                // Implementation of weights from WindowManager.LayoutParams                // We just grow the dimensions as needed and re-measure if                // needs be                int width = host.getMeasuredWidth();                int height = host.getMeasuredHeight();                boolean measureAgain = false;                if (lp.horizontalWeight > 0.0f) {//--------如果权重值不管是横向的还是竖向的都需要再次measure()                    width += (int) ((mWidth - width) * lp.horizontalWeight);                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,                            MeasureSpec.EXACTLY);                    measureAgain = true;                }                if (lp.verticalWeight > 0.0f) {//----------如果权重值不管是横向的还是竖向的都需要再次measure()                    height += (int) ((mHeight - height) * lp.verticalWeight);                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,                            MeasureSpec.EXACTLY);                    measureAgain = true;                }                if (measureAgain) {//---------如果需要再次测量                    if (DEBUG_LAYOUT) Log.v(TAG,                            "And hey let's measure once more: width=" + width                            + " height=" + height);                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ------2.2.9 如果Window的Layoutparams中设置的有权重值则会根据权重再次测量                }                layoutRequested = true;            }        }    } else {//???--wtf如果不是第一次测量&&window/insets/visibility都没有变化,只是窗口有移动        ...//省略代码    }     final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);    boolean triggerGlobalLayoutListener = didLayout            || mAttachInfo.mRecomputeGlobalAttributes;    if (didLayout) {        performLayout(lp, desiredWindowWidth, desiredWindowHeight);//------2.2.10、执行layout,会回调所有ViewGroup及View中的onLayout()-------------        //By this point all views have been sized and positioned 到此则所有View的大小及位置都已经确定完毕        // We can compute the transparent area         if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {            // start out transparent            // TODO: AVOID THAT CALL BY CACHING THE RESULT?            //----##首先确认DecorView在Window中的位置##-----            host.getLocationInWindow(mTmpLocation);            ...            }        }    mFirst = false;    mWillDrawSoon = false;    mNewSurfaceNeeded = false;    mViewVisibility = viewVisibility;    ...     if (!cancelDraw && !newSurface) {        if (!skipDraw || mReportNextDraw) {            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {                for (int i = 0; i < mPendingTransitions.size(); ++i) {                    mPendingTransitions.get(i).startChangingAnimations();                }                mPendingTransitions.clear();            }            performDraw();//------------2.2.11该方法中会回调所有onDraw()-----        }    } else {        if (viewVisibility == View.VISIBLE) {            // Try again            scheduleTraversals();        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {            for (int i = 0; i < mPendingTransitions.size(); ++i) {                mPendingTransitions.get(i).endChangingAnimations();            }            mPendingTransitions.clear();        }    }    mIsInTraversal = false;}

2.3 在2.2.6中的ViewRootImpl的measureHierarchy()最终会调用到和2.2.8处相同的方法performMeasure(),而performMeasure()最终会调用到DecorView的measure()方法,measureHierarchy()和performMeasure()的实现代码如下:

2.3.1 在performMeasure()中,会调用到mView.measure(),而该mView正是DecorView,至此进入我们比较熟悉的View的measure()方法中
  private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,        final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {    int childWidthMeasureSpec;    int childHeightMeasureSpec;    boolean windowSizeMayChange = false;    if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(TAG,            "Measuring " + host + " in display " + desiredWindowWidth            + "x" + desiredWindowHeight + "...");    boolean goodMeasure = false;    if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {//当Window设置的为WrapContent时        // On large screens, we don't want to allow dialogs to just        // stretch to fill the entire width of the screen to display        // one line of text.  First try doing the layout at a smaller        // size to see if it will fit.        ....    }    if (!goodMeasure) {//一般的Activity中的Window在初始化时都是ViewGroup.LayoutParams.MATCH_PARENT的,上面的逻辑不会走直接走到此处        childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);        childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);        //---------执行perforMeasure()-----------        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);        if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {            windowSizeMayChange = true;        }    }    if (DBG) {        System.out.println("======================================");        System.out.println("performTraversals -- after measure");        host.debug();    }    return windowSizeMayChange;}//----------至此开始调用DecorView的measure()方法,Activity中一般给入的参数都是屏幕的实际宽高和MeasureSpec.EXACTLY合成的MeasureSpecprivate void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");        try {            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);//----2.3.1 该mView正是DecorView        } finally {            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }    }

2.4 View测量开始:因为DecorView继承自FrameLayout因此其是一个ViewGroup,从此处开始就是常见的ViewGroup和View的measure过程了。

View的测量开始是从View.measure()方法开始的,我们先理清一下View树的测量逻辑

  • 1.对于顶级DecorView的measure()我们在2.2中已经看到是在ViewRootImpl中触发,然后会调用的DecorView的onMeasure()方法(需要明确的是DecorView是一个ViewGroup);
  • 2.对于一个子View(不管是单个的View还是一个ViewGroup)其measure()方法的触发逻辑为:当其父View在执行onMeasure的时候会依次调用测量子View,最终会调用到子View的measure()方法。
  • 3.如果子View是一个普通的View,当触发其onMeasure之后(该过程可能执行不止一次),该View会根据传入的MeasureSpecMode和自己的测量规则算出自己的大小(只算大小,不管位置),并调用setMeasuredDimension()保存自己的测量出来的大小。
  • 4.如果子View还是一个ViewGroup,则会再次在其onMeasure()方法中按照实际的逻辑依次触发子View的measure()方法,并调用setMeasuredDimension()保存自己的测量出来的大小;
  • 5.进一步依次递归
2.4.1 在测量过程中,我们知道View的measure()最终都会回调到View的onMeasure(),onMeasure()在View中的默认实现如下:
@View.javaprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    //测量完成后调用setMeasuredDimension(),只有在调用该方法之后View的getMeasureWidth()才会有值    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
2.4.2 在上面的View的onMeasure()回调方法中的有两个参数widthMeasureSpec和heightMeasureSpec,我们暂撇开DecorView的具体测量,首先先明确一下onMeasure函数中的两个参数是怎么来的:
2.4.2.1 对于DecorView因为其是一个顶级的布局View,DecorView之上并没有父布局,其WidthMeasureSpec和heightMeasureSpec从2.2中我们可以看到是在ViewRootImpl中通过getRootMeasureSpec()生成,然后调用DecorView的measure()方法传递进去的,getRootMeasureSpec()方法如下:

对于常见的Activity,DecorView默认其是由屏幕的实际宽高和MeasureSpec.EXACTLY合成的,具体其他的情况可以参考源码查看

@ViewRootImpl.javaprivate static int getRootMeasureSpec(int windowSize, int rootDimension) {//---此处的rootDimension默认是LayoutParams.MATCH_PARENT    int measureSpec;    switch (rootDimension) {    case ViewGroup.LayoutParams.MATCH_PARENT:        // Window can't resize. Force root view to be windowSize.        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);        break;    case ViewGroup.LayoutParams.WRAP_CONTENT:        // Window can resize. Set max size for root view.        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);        break;    default:        // Window wants to be an exact size. Force root view to be that size.        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);        break;    }    return measureSpec;}

一般对于Activity来说其WindowSize即为屏幕的宽高;rootDimension则对应为WindowManager.LayputParams,因为一般默认系统生成的PhoneWindow都会设定为Match_PARENT,因此Mode就对应为就对应MeasureSpec.EXACTLY。但是该LayoutParams是可以改变的,我们比较常用的手动设置的场景是在自定义Dialog时根据需要设置Dialog的Window的相关参数

2.4.2.2 对于一般的View无论是一个View还是一个ViewGroup,这两个参数都是在父布局中生成并最终传入的,而只要是父布局则肯定是ViewGroup,其测量模式都是在父布局中生成的:

在ViewGroup中不同的ViewGroup实现的onMeasure由于工作机制不同,其measureChildren()和measureChild()的实现也不相同,默认实现如下:

    @ViewGroup.java   protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {    final int size = mChildrenCount;    final View[] children = mChildren;    for (int i = 0; i < size; ++i) {        final View child = children[i];        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {            measureChild(child, widthMeasureSpec, heightMeasureSpec);        }    }}//measureChild的默认实现如下 protected void measureChild(View child, int parentWidthMeasureSpec,        int parentHeightMeasureSpec) {    final LayoutParams lp = child.getLayoutParams();    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,            mPaddingLeft + mPaddingRight, lp.width);    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,            mPaddingTop + mPaddingBottom, lp.height);    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

但是不管ViewGroup的具体实现是什么,最终都会调用getChildMeaureSpec()来生成子View所需的MeasureSpec,实现如下:

@ViewGroup.java//getChildMeasureSpec()实现如下public static int getChildMeasureSpec(int spec, int padding, int childDimension) {        int specMode = MeasureSpec.getMode(spec);        int specSize = MeasureSpec.getSize(spec);        int size = Math.max(0, specSize - padding);        int resultSize = 0;        int resultMode = 0;        switch (specMode) {        // Parent has imposed an exact size on us        case MeasureSpec.EXACTLY:            if (childDimension >= 0) {                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size. So be it.                resultSize = size;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size. It can't be                // bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        // Parent has imposed a maximum size on us        case MeasureSpec.AT_MOST:            if (childDimension >= 0) {                // Child wants a specific size... so be it                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size, but our size is not fixed.                // Constrain child to not be bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size. It can't be                // bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        // Parent asked to see how big we want to be        case MeasureSpec.UNSPECIFIED:            if (childDimension >= 0) {                // Child wants a specific size... let him have it                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size... find out how big it should                // be                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                resultMode = MeasureSpec.UNSPECIFIED;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size.... find out how                // big it should be                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                resultMode = MeasureSpec.UNSPECIFIED;            }            break;        }        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }

而该getChildMeasureSpec(int spec, int padding, int childDimension)的逻辑则可以总结为我们经常能够见到的一张表格:

Android事件分发和View绘制流程分析(三)_第1张图片

2.5 以上我们知道了在View的测量起始位置measure(),然后会调用onMeasure(,以及onMeasure()方法中的两个参数的来历,下面就以一个常见的LinearLayout来走一下一个ViewGroup具体的measure过程:

首先:我们带着以下几个问题来看其测量过程

  • 1.在测量中为什么有时measure会调用多次?
  • 2.LinearLayout中设置Weight后是怎么测量的?
  • 3.什么时候getMeasureWidth(),getMeasureHeight()才能得到正确的值?
  • 4.什么时候getWidth()和getHeight()才能拿到正确的值?
2.5.1 我们先看LinearLayout的onMeasure方法,再提一下View(不管是View还是ViewGroup)的onMeasure方法的调用逻辑为:当父View在执行onMeasure的时候依次调用子View的measure()方法,而在View的measure中会回调onMeasure():
2.5.1.1在LinearLayout的onMeasure()中方法中,首先会根据当前LinearLayout设置的orientation调用来调用相关的测量方法,代码如下:
@LinearLayout.java   @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    if (mOrientation == VERTICAL) {        measureVertical(widthMeasureSpec, heightMeasureSpec);//设置Orientation = VERTICAL 时会使用该方法进行测量    } else {        measureHorizontal(widthMeasureSpec, heightMeasureSpec);    }}
2.5.1.2 我们只以orientation设置为VERTICAL的来进行分析,measureVertical的代码如下:**
  • 1、在该方法中会首先置mTotalLength为0,该字段记录当前LinearLayout的总高,每测量出来一个子View的总高时即会加上该子View的高度,以及其设置的margin值
  • 2、在该方法中会声明局部变量totalWeight用来记录子View设置的权重值
  • 3.拿取当前LinearLayout所有的子View个数,不管是是否已经设置为了GONE,但是在下面测量中设置为GONE的子View将不会测量
  • 4.然后会得到当前LinearLayout的widthMode和heightMode,以便在确定子View的测量模式时使用
  • 5.for循环拿取依次遍历子View,对于每一个子View实现以下luoji
  • 6.如果该子View为设置为了GONE,则直接continue,跳过该View的测量并统计跳过的View
  • 7.判断该子View是否有Divider,如果有的话则总高加上一个Divider的高度
  • 8.拿取子View设置的LayoutParams,即在XML中的声明的相关LayoutParams或代码中设置的相应LayoutParams
  • 9.1如果当前LinearLayout的测量模式是MeasureSpec.EXACTLY并且该子View的LayoutParams的lp.height == 0 并且lp.weight > 0,此时会先跳过该子View的测量,但是mTotalLength会加上该子View设置的margin,并设置skippedMeasure标志位为true,标识有跳过的子View未测量,以便在下面的逻辑中测量
  • 10.如果不满足9.1的条件,该开始对子View会进行一次测量
  • 10.1、如果当该子View的lp.height == 0 并且lp.weight > 0时,此时当前LinearLayout的测量模式肯定不为MeasureSpec.EXACTLY,则设更改该子View的lpt的参数lp.height为LayoutParams.WRAP_CONTENT,表示当当前LinearLayout的尺寸高度不确定时,该子类View的在测量时尽量利用所有的有效空间
  • 10.2 调用 measureChildBeforeLayout(),正式开始测量子View的大小,该方法会调用ViewGroup中的 measureChildWithMargins(child,widthMeasureSpec, totalWidth,heightMeasureSpec, totalHeight);其中的参数totalHeight即是当前已经测量出来的mTotalLength。该参数最终会被当做已经占用的高度(heightused),在合成子View的MeasureSpec时候使用的size会相应的减掉该值
  • 11、根据已经占用的高度计算剩余的有效高度空间,此时满足9.1条件的View还没有进行测量不再已占用的高度之内,另外需要注意的一个场景是当一个View的高设置为WrapContent,同时weight>0次数该View的高度会按照WrapConten的测量模式,执行measureChildBeforeLayout()进行过一次测量一遍并已经计入了mTotalLength。
  • 12、到该步骤则View大致是对应9.1的条件满足还没有进行过测量的,以及不管是否有weight值,但是已经按照相应的测量模式测量过一次了的
  • 13.如果满足有跳过的View没有测量(即有满足9.1条件的View)或者统计到的weight值大于0同时还有剩余空间,则循环遍历所有子View
  • 14、如果子View的weight>0,则根据剩余高度和统计出来的所有子View的weight值,对剩余空间进行切分,并把该值按比例添加到子View已经测量出来的高度上并和MeasureSpec.EXACTLY合成子View测量模式进行重新测量,总之就是把剩余的空间按比例重新分配到子View上去,需要注意对于前面满足9.1跳过的子View,不会有把当前的高度加上相应比例剩余空间的操作,而是直接按照其需要占用的剩余空间的大小进行重新测量。因为之前没有对其测量过根本
  • 15、如果没有跳过任何子View的测量的(对应13条件的else{}),且满足设置了使用useLargestChild为true且当前的LinearLayout的测量模式不为MeasureSpec.EXACTLY,ps:一般的情形不会走到该逻辑中
  • 16、当前LinearLayout测量完毕 调用setMeasuredDimension()设置其宽高。其中的入参heightSizeAndState,在第11步代码之前已经确认,因为如果没有weight则那是已经计算出了总高,如果是设置有weight则weight就是要占据所有剩余空间,此时height也是可以确定的

具体LinearLayout的代码及相应的简单注释如下:

@LinearLayout.java//竖直布局时使用的测量方法void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {    mTotalLength = 0;//---1.用来记录当前LinearLayout已经占用的高度(或者简单的说每测量一个子View该值都会加上该子View的高度)    int maxWidth = 0;    int childState = 0;    int alternativeMaxWidth = 0;    int weightedMaxWidth = 0;    boolean allFillParent = true;    float totalWeight = 0;//---2.权重值统计    final int count = getVirtualChildCount();//-----3.拿取子View的个数,但不一定是最终子View的个数,子View的Visible属性为GONE时会直接跳过不再测量    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);//----4.当前布局的测量模式--对于该LinearLayout的子View来书这个即使父布局的测量模式,在合成子View的MeasureSpecMode的时候父布局的测量模式即使从此处得到    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);//当前布局的测量模式    boolean matchWidth = false;    boolean skippedMeasure = false;    final int baselineChildIndex = mBaselineAlignedChildIndex;            final boolean useLargestChild = mUseLargestChild;    int largestChildHeight = Integer.MIN_VALUE;    // See how tall everyone is. Also remember max width.----5、 开始所有子View的测量,ViewGrop的测量师递归的即如果子View也是一个ViewGroup则会直接进入该子View其内部进行测量    for (int i = 0; i < count; ++i) {        final View child = getVirtualChildAt(i);        if (child == null) {            mTotalLength += measureNullChild(i);            continue;        }        if (child.getVisibility() == View.GONE) {//----6、如果子View的Visibility属性设置为GONE则统计后直接continue           i += getChildrenSkipCount(child, i);           continue;        }        if (hasDividerBeforeChildAt(i)) {//------7、是否有Divider            mTotalLength += mDividerHeight;        }        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();//----8、拿取子View的LayoutParams        totalWeight += lp.weight;//统计子View设置的权重值        if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {//----9.如果LinearLayout的SpecMode为EXACTLY&&子View的height ==0 且子View设置的weight>0(简单书就是如果当前的Linearlayout的高度尺寸已经确定同时该子View的高度设置为0且该子View的weight值不为0)---            // Optimization: don't bother measuring children who are going to use            // 优化:不再过度的去测量将会使用剩余空间的子View            // leftover space. These views will get measured again down below if            //这些子View将会在下面再次得到测量当还有剩余空间时            // there is any leftover space.            final int totalLength = mTotalLength;            mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);            skippedMeasure = true;//测量子View过程中是否有跳过        } else {//----10、否则            int oldHeight = Integer.MIN_VALUE;            if (lp.height == 0 && lp.weight > 0) {//----11、如果子View的Weight>0(即有权重值)且设置的高度为0                 // heightMode is either UNSPECIFIED or AT_MOST, and this                //不管当前布局的测量模式是 ATMOST还是UNSPECIFIED(不可能再会是EXACTLY),                // child wanted to stretch to fill available space.                //并且该子View想要填充当前Linearlayout剩余的有效空间。                // Translate that to WRAP_CONTENT so that it does not end up                //则设置该子View的LayoutParams为WrapContent                // with a height of 0                oldHeight = 0;                lp.height = LayoutParams.WRAP_CONTENT;//设置子View为WrapContent            }            // Determine how big this child would like to be. If this or            //确认该子View想要得到多大的空间如果该子View或者之前有子View设置过Weight,            // previous children have given a weight, then we allow it to            //那么会允许子View得到所有的有效空间(但是如果需要的话我们在后面可能会收缩该空间)            // use all available space (and we will shrink things later            // if needed).            measureChildBeforeLayout(//                   child, i, widthMeasureSpec, 0, heightMeasureSpec,                   totalWeight == 0 ? mTotalLength : 0);            if (oldHeight != Integer.MIN_VALUE) {               lp.height = oldHeight;            }            final int childHeight = child.getMeasuredHeight();            final int totalLength = mTotalLength;            mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +                   lp.bottomMargin + getNextLocationOffset(child));            if (useLargestChild) {                largestChildHeight = Math.max(childHeight, largestChildHeight);            }        }        /**         * If applicable, compute the additional offset to the child's baseline         * we'll need later when asked {@link #getBaseline}.         */        if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {           mBaselineChildTop = mTotalLength;        }        // if we are trying to use a child index for our baseline, the above        // book keeping only works if there are no children above it with        // weight.  fail fast to aid the developer.        if (i < baselineChildIndex && lp.weight > 0) {            throw new RuntimeException("A child of LinearLayout with index "                    + "less than mBaselineAlignedChildIndex has weight > 0, which "                    + "won't work.  Either remove the weight, or don't set "                    + "mBaselineAlignedChildIndex.");        }        boolean matchWidthLocally = false;        if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {            // The width of the linear layout will scale, and at least one            // child said it wanted to match our width. Set a flag            // indicating that we need to remeasure at least that view when            // we know our width.            matchWidth = true;            matchWidthLocally = true;        }        final int margin = lp.leftMargin + lp.rightMargin;        final int measuredWidth = child.getMeasuredWidth() + margin;        maxWidth = Math.max(maxWidth, measuredWidth);        childState = combineMeasuredStates(childState, child.getMeasuredState());        allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;        if (lp.weight > 0) {            /*             * Widths of weighted Views are bogus if we end up             * remeasuring, so keep them separate.             */            weightedMaxWidth = Math.max(weightedMaxWidth,                    matchWidthLocally ? margin : measuredWidth);        } else {            alternativeMaxWidth = Math.max(alternativeMaxWidth,                    matchWidthLocally ? margin : measuredWidth);        }        i += getChildrenSkipCount(child, i);    }    if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {        mTotalLength += mDividerHeight;    }    if (useLargestChild &&            (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {        mTotalLength = 0;        for (int i = 0; i < count; ++i) {            final View child = getVirtualChildAt(i);            if (child == null) {                mTotalLength += measureNullChild(i);                continue;            }            if (child.getVisibility() == GONE) {                i += getChildrenSkipCount(child, i);                continue;            }            final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)                    child.getLayoutParams();            // Account for negative margins            final int totalLength = mTotalLength;            mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +                    lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));        }    }    // Add in our padding    mTotalLength += mPaddingTop + mPaddingBottom;    int heightSize = mTotalLength;    // Check against our minimum height    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());    // Reconcile our calculated size with the heightMeasureSpec    int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);//  ------heightSizeAndState 确认    heightSize = heightSizeAndState & MEASURED_SIZE_MASK;    // Either expand children with weight to take up available space or    // shrink them if they extend beyond our current bounds. If we skipped    // measurement on any children, we need to measure them now.    int delta = heightSize - mTotalLength;//----11、算出之前没有跳过的所有子View已经占据的空间打小,注意当子View设置为WrapContent weight>0时,此时mTotallenrth中是包含实际按AtMost算出来的大小值的,该处可以说明weight是所有子View计算之后剩余子View的剩余大小再按权重分配的---    //-----12、到该步骤则View大致是对应9.1的条件满足还没有进行过测量的,以及不管是否有weight值,但是已经按照相应的测量模式测量过一次了的    if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {        float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;        mTotalLength = 0;        //----13、循环遍历子View 根据相应的规则进行重新测量        for (int i = 0; i < count; ++i) {            final View child = getVirtualChildAt(i);            if (child.getVisibility() == View.GONE) {                continue;            }            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();            float childExtra = lp.weight;            if (childExtra > 0) {//14、如果子View的权重值大于0 按以下机制再次计算,------------                // Child said it could absorb extra space -- give him his share                int share = (int) (childExtra * delta / weightSum);//12.计算需要该子View的weight所需要占用的剩余空间                weightSum -= childExtra;                delta -= share;                final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,                        mPaddingLeft + mPaddingRight +                                lp.leftMargin + lp.rightMargin, lp.width);                // TODO: Use a field like lp.isMeasured to figure out if this                // child has been previously measured                if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {//此处逻辑(对应之前跳过没有测量的View):子View满足lp.weight>0的同时lp.height!=0或者当前LinearLayout的SpecMode不是EXACTLY                 //此处对应一种情况是当weight>0 lp.height为wrapcontent时,当前LinearLayout为EXACTLY时,得到的效果即是,该子View的大小为按照权重得到的剩余空间+上面按照ATMOST已经测量出来的大小。我们可以设置该多个子View的LayoutParamas为WrapContent同时设置其weight都为1,会看到子View的所占的高度是不等的,但是其减去实际占的位置之后,剩余的空间是相等的                    // child was measured once already above...                    // base new measurement on stored values                    int childHeight = child.getMeasuredHeight() + share;//把剩余的空间按照比例添加到子View上                    if (childHeight < 0) {                        childHeight = 0;                    }                    child.measure(childWidthMeasureSpec,                            MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));//重新测量子View                } else {//此处逻辑是子View满足(lp.weight>0&&lp.height==0&&当前Linearlayout的heightSpecMode==EXACTLY)简单的说即是上面直接跳过的没有进行过测量的子View                    // child was skipped in the loop above.                    // Measure for this first time here                     // 一开始跳过的子View此时第一次测量 尺寸大小为根据剩余尺寸和所占的权重计算出来的size大小,SpecMode为EXACTLY                         child.measure(childWidthMeasureSpec,                            MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,                                    MeasureSpec.EXACTLY));                }                // Child may now not fit in vertical dimension.                childState = combineMeasuredStates(childState, child.getMeasuredState()                        & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));            }            final int margin =  lp.leftMargin + lp.rightMargin;            final int measuredWidth = child.getMeasuredWidth() + margin;            maxWidth = Math.max(maxWidth, measuredWidth);            boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&                    lp.width == LayoutParams.MATCH_PARENT;            alternativeMaxWidth = Math.max(alternativeMaxWidth,                    matchWidthLocally ? margin : measuredWidth);            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;            final int totalLength = mTotalLength;            mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +                    lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));//再次统计当前Linearlayout的高度        }        // Add in our padding        mTotalLength += mPaddingTop + mPaddingBottom;        // TODO: Should we recompute the heightSpec based on the new total length?    } else {//----如果没有跳过任何子View的测量的则执行以下逻辑----        alternativeMaxWidth = Math.max(alternativeMaxWidth,                                       weightedMaxWidth);        // We have no limit, so make all weighted views as tall as the largest child.        // Children will have already been measured once.        if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {//----15、如果没有跳过任何子View的测量的则执行以下逻辑,且满足设置了使用useLargestChild为true且当前的LinearLayout的测量模式不为MeasureSpec.EXACTLY----            for (int i = 0; i < count; i++) {                final View child = getVirtualChildAt(i);                if (child == null || child.getVisibility() == View.GONE) {                    continue;                }                final LinearLayout.LayoutParams lp =                        (LinearLayout.LayoutParams) child.getLayoutParams();                float childExtra = lp.weight;                if (childExtra > 0) {//如果有权重则再次测量其所占比例                    child.measure(                            MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),                                    MeasureSpec.EXACTLY),                            MeasureSpec.makeMeasureSpec(largestChildHeight,                                    MeasureSpec.EXACTLY));                }            }        }    }    if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {        maxWidth = alternativeMaxWidth;    }    maxWidth += mPaddingLeft + mPaddingRight;    // Check against our minimum width    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),            heightSizeAndState);//----16、当前LinearLayout测量完毕 调用setMeasuredDimension    if (matchWidth) {        forceUniformWidth(count, heightMeasureSpec);    }}
2.5.2、上面我们大致的走了LinearLayout的整体测量流程。我们就来解释一下我们一开始提出的几个问题:
2.5.2.1 在测量中为什么有时measure会调用多次?

对于LinearLayout如果一旦子View设置了weight,并且在当前LinearLayout的SpecMode不为EXACTLY的时候,会首先该子View会首先按照ATMOST+剩余有效空间大小合成子View的SpecMode进行一次测量,然后当所有子View测量完毕之后,会把剩余的空间按照该View占总权重的比例算出一个高度值,并把该值添加到已经测量出的高度值上与MeasureSpec.EXACTLY合成之后再次测量,此时子View的measure就不止调用一次,当让这只是一种情况。但说明measure调用的次数是和具体的测量策略有一定关系的

2.5.2.2 LinearLayout中设置Weight后是怎么测量的?
问题1中也已经回答了该问题,有一个比较特殊的场景可以验证,假设当前LinearLayout高满足测量模式为MeasureSpec.EXACTLY,有三个子View,当把所有子View设置为WrapContent,weight值为1时,如果三个子View的占用高度不同但是加起来当前LinearLayout仍有剩余空间时,则每个子View的实际测量高度为其有效占用高度+剩余空间的1/3,简单的各子View的最终测量出来的大小是不同的。(子View嵌套又有设置了Weight的子View的场景除外)。但是如果三个子View的高度设置为0,则会满足2.4.1中9.1的条件,此时最终子View的测量出来的高度值相同为当前LinearLayout高度的1/3。因此想要用weight值来让子View之间的间距看起来一样的时候,需要设置为WrapContent但是不建议使用此种方法,因为一旦子View中还有子View且设置了Weight值那么就不一样了。
2.5.5.3 什么时候getMeasureWidth(),getMeasureHeight()才能得到正确的值?

这个我们在跟进代码setMeasuredDimension()之后就能清除看到只有测量完毕调用了该方法之后getMeasureWidth()和getMeasureHeight()才能拿到有效的值。

//setMeasuredDimension() 会调用到下面的方法@View.java private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {    mMeasuredWidth = measuredWidth;//给mMeasuredWidth赋值,在这之后getMeasureHeight()才能拿取到值    mMeasuredHeight = measuredHeight;    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;}
2.5.5.4什么时候getWidth()和getHeight()才能拿到正确的值?

mRight和mLeft则是在onLayout之后才能确定,所以getWidth()想要拿取到正确的值需要等到layout完毕之后才行

对于这个问题我们看代码:@View.java public final int getWidth() {    return mRight - mLeft;}
结尾,对于View的layout则相对来说逻辑较为简单,而对于View的onDraw调用过程也相对简单,但是在绘制中则有涉及到Android系统的视图绘制机制,这里不再展开。以上为个人的代码阅读理解难免有错误的地方,一定请仅作为参考。

更多相关文章

  1. android中Bitmap的放大和缩小的方法
  2. Android遍历文件Listfile返回值为null问题解决方法适用Android8.
  3. Android 最新获取手机内置存储大小,SD卡存储空间大小方法
  4. Android获取状态栏和标题栏的高度
  5. Android 启动浏览器的方法
  6. Android根据电量变化为不同图片的方法【电池电量提示】
  7. Android之日期时间选择器使用方法
  8. Android 图片旋转(使用Matrix.setRotate方法)
  9. Android Http请求失败解决方法

随机推荐

  1. android 动画 ——视图动画(View Animati
  2. AndroidStudio快捷键整理--3
  3. Android的Activity滑动切换动画
  4. Android(安卓)GridView等控件的属性集合
  5. 好用的Android的UI第三方开源框架
  6. Android(安卓)开发笔记 动画效果 --Anima
  7. Android海康监控视频调用demo
  8. Android中TabHost的原理及使用
  9. Android(安卓)记录一次开发微信分享功能
  10. Android面试时的问题,实现半透明的popupw