Android事件分发和View绘制流程分析(三)
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)的逻辑则可以总结为我们经常能够见到的一张表格:
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系统的视图绘制机制,这里不再展开。以上为个人的代码阅读理解难免有错误的地方,一定请仅作为参考。
更多相关文章
- android中Bitmap的放大和缩小的方法
- Android遍历文件Listfile返回值为null问题解决方法适用Android8.
- Android 最新获取手机内置存储大小,SD卡存储空间大小方法
- Android获取状态栏和标题栏的高度
- Android 启动浏览器的方法
- Android根据电量变化为不同图片的方法【电池电量提示】
- Android之日期时间选择器使用方法
- Android 图片旋转(使用Matrix.setRotate方法)
- Android Http请求失败解决方法