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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/M075097/article/details/79027222

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合成的MeasureSpec
private 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.java
protected 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.java
private 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)的逻辑则可以总结为我们经常能够见到的一张表格:

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
   @Override
protected 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系统的视图绘制机制,这里不再展开。以上为个人的代码阅读理解难免有错误的地方,一定请仅作为参考。

猜你喜欢

转载自blog.csdn.net/M075097/article/details/79027222