从源码层剖析View的Measure过程

View展示出来一共有三个过程,大致为:测量每个View大小(measure)-->把每个View放置到相应的位置(layout)-->绘制每个View(draw
今天本文主要详解的是测量每个View大小(measure)这个过程
主要先从如下流程图开始,从上往下一路详解其主要的方法原理

viewroot.jpeg

主要方法简介

handleLaunchActivity

该方法会执行很多方法,这个是入口,简单来说会创建Activity对象

  • 调用其启动生命周期,attachonCreateonStartonResume,以及添加到WindowManager中
  • 我们在Activity的attach函数中新建了PhoneWindow对象,在PhoneWindowsetContentView函数中会调用installDector来创建DecorView对象

handleResumeActivity

进入到绘制界面后,会走到handleResumeActivity方法,通过performResumeActivity调用activity的onResume方法
如下是进入绘制界面的主要步骤

  • 第一步通过ViewManager wm = a.getWindowManager(),绑定WindowManager获取其对象。
  • 第二步wm.addView(decor, l)将decorView传入,而WindowManager的实现类是WindowManagerImpl,而它则是通过WindowManagerGlobal代理实现addView的

WindowManagerGlobal

WindowManagerImpl这种工作模式是典型的桥接模式,将所有的操作委托给WindowManagerGlobal来实现,如下是其实现的addView方法

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        ViewRootImpl root;
        View panelParentView = null;
        ...
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        //ViewRootImpl开始绘制view
        root.setView(view, wparams, panelParentView);
        ...
    }

ViewRootImpl.setView

  • 先调用requestLayout(),完成第一次layout布局过程,以确保在收到任何系统事件后面重新布局。
  • 接着会通过WindowSession最终来完成Window的添加过程。在下面的代码中mWindowSession类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,也就是说这其实是一次IPC过程,远程调用了Session中的addToDisPlay方法。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                //requestLayout最终会调用performTraversals方法来完成View的绘制
                requestLayout();
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
    }

performTraversals

ViewRootImpl调用performTraversals方法开始对view的测量布局绘制
关键方法有三个

  • performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)
  • performLayout(lp, desiredWindowWidth, desiredWindowHeight)
  • performDraw()

pic.jpeg

其中当窗口的最新尺寸与ViewRootImpl中的现有尺寸不同时
layoutRequested会设置会true,这个时候会要求进行预测量即measureHierarchy

measureHierarchy方法

该方法用于测量整个控件树,通常针对悬浮弹框布局,通过预测量来调整弹框的显示大小,达到最好的视觉显示效果(如下图右边)。
一共有两次协商测量机会(两次协商测量仅在其width等于wrap_content下进行,因为match_parent及设置大小均没必要进行协商测量,因为一个是填充满父view,另一个是已设定好的大小),第一次协商使用系统资源预设好的大小配置,若设置后仍不满意则进入到第二次协商即 (baseSize+desiredWindowWidth)/2,若仍不满意,则放弃所有限制进入最终测量

dialog.jpeg

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        int childWidthMeasureSpec;// 合成后的用于描述宽度的MeasureSpec
        int childHeightMeasureSpec;// 合成后的用于描述高度的MeasureSpec
        boolean windowSizeMayChange = false;// 表示测量结果是否可能导致窗口的尺寸发生变化
        boolean goodMeasure = false;// 表示了测量是否能满足控件树充分显示内容的要求
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            //预测量只在wrap_content中进行
            final DisplayMetrics packageMetrics = res.getDisplayMetrics();
            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
           //第一次协商是通过使用它最期望的宽度限制进行测量。这一宽度限制定义为一个系统资源
            int baseSize = 0;
            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                baseSize = (int)mTmpValue.getDimension(packageMetrics);
            }
            if (baseSize != 0 && desiredWindowWidth > baseSize) {
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
               //第一次测量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
               //通过获取view的测量结果来判断,如果满足条件则认为是测量满意否则进入第二次协商
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    goodMeasure = true;
                } else {
                    // 第二次协商
                    baseSize = (baseSize+desiredWindowWidth)/2;
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                    // 第二次测量
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                        goodMeasure = true;
                    }
                }
            }
        }

        if (!goodMeasure) {
            // 最终测量。当控件树对上述两次协商的结果都不满意时,measureHierarchy()放弃所有限制
            // 做最终测量。这一次将不再检查控件树是否满意了,因为即便其不满意,measurehierarchy()也没有更多的空间供其使用了
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
               //如果测量结果与viewrootimpl的高宽不一致则需要进行调整
                windowSizeMayChange = true;
            }
        }
        return windowSizeMayChange;
    }

onMeasure()

从上面所说的当我们进入到performTraversals后,会执行performMeasure来进入view的测量即onMeasure
下图是view测量的流程图

measure_pic1.jpeg

注意:FrameLayout(id/content)往下走的view为自定义的layout布局

从上图可知view的根view是DecorView,DecorView由TitleViewContentView构成,而ContentView就是我们启动activity时setContentView进去的xml布局,也就是说当我们xml中的textview组件需要绘制的时候,必须先从DecorView开始,由外层view一路到最内层view的绘制过程。

MeasureSpec

MeasureSpec类封装了一个View的规格尺寸,包括View的宽和高的信息,但是要注意,MeasureSpec并不是指View的测量宽高,这是不同的,是根据MeasueSpec而测出测量宽高。
在系统中组件的大小模式有三种:

  • 精确模式(MeasureSpec.EXACTLY
    在这种模式下,尺寸的值是多少,那么这个组件的长或宽就是多少。

  • 最大模式(MeasureSpec.AT_MOST
    特指当前组件的宽或高大小只能在父组件给出的最大空间里定义,不得超出

  • 未指定模式(MeasureSpec.UNSPECIFIED
    父控件对子控件不加任何束缚,子元素可以得到任意想要的大小,这种MeasureSpec一般是由父控件自身的特性决定的。比如ScrollView,它的子View可以随意设置大小,无论多高,都能滚动显示,这个时候,size一般就没什么意义。

一个int型整数表示两个东西(大小模式和大小的值),一个int类型我们知道有32位。而模式有三种,要表示三种状态,至少得2位二进制位。于是系统采用了最高的2位表示模式。如图:

pic3.png

最高两位是00的时候表示"未指定模式"。即MeasureSpec.UNSPECIFIED
最高两位是01的时候表示"'精确模式"。即MeasureSpec.EXACTLY
最高两位是11的时候表示"最大模式"。即MeasureSpec.AT_MOST
当然我们并不需要刻意的去记住,因为MeasureSpec类已提供getSize、getMode等方法给我们,但我们应该大概了解即可。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
      //判断当前布局的宽高是否是match_parent模式,如果是则置measureMatchParent为false.
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();

        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
               // 该方法主要把margin 及  padding 也作为子视图大小的一部分并返回MeasureSpec给到子view进行测绘
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    //子view为宽或高为LayoutParams.MATCH_PARENT模式则加入mMatchParentChildren
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        // Account for padding too
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));

        count = mMatchParentChildren.size();
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
               //根据当前布局的宽高来测量模式为LayoutParams.MATCH_PARENT的子view
               //子view可以覆盖的范围是FrameLayout的测量宽度,减去padding和margin后剩下的空间。
                final int childWidthMeasureSpec;
                if (lp.width == LayoutParams.MATCH_PARENT) {
                    final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } else {
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }

                final int childHeightMeasureSpec;
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }
                 //对于这部分的子View需要重新进行measure过程
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

measureChildWithMargins方法

该方法主要把margin 及 padding 也作为子视图大小的一部分并返回MeasureSpec给到子view进行测绘

   protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

getChildMeasureSpec方法

根据获取的specMode、specSize来重新计算resultSize、resultMode

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) {
        case MeasureSpec.EXACTLY:
           //在这种模式下,尺寸的值是多少,那么这个组件的长或宽就是多少
            if (childDimension >= 0) {
               //如果子view设置具体值, 则取子view大小,mode设置为   MeasureSpec.EXACTLY(即match_parent) 
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
               //子view的大小是match_parent,则填充父view空间,size为父的空间大小减去子view的margin边距(如果是计算子view的高度空间,则减去顶部和底部margin,反之则左右margin)
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
               //与match_parent一致,但是mode则为AT_MOST,说明希望子View的大小不要超过父View的大小
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.AT_MOST:
           //这个也就是父组件,能够给出的最大的空间,当前组件的长或宽只能在其父组件给出的范围内
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
             //子View的大小为父View的size,但是mode则为AT_MOST,说明希望子View的大小不要超过父View的大小
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
             //与上面一致
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        case MeasureSpec.UNSPECIFIED:
         //当前组件,可以随便用空间,不受限制。
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //大小自己设置
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

measureVertical方法

setContentView中的布局文件是一个以LinearLayout为根布局 其子view是TextView为例
从上面DecorView的onMeasure到其继承的FrameLayout onMeasure一路遍历其子view测量,最后进入setContentView
LinearLayout的onMeasure一共有两个方法,根据其布局属性来执行,分别为

  • measureVertical(widthMeasureSpec, heightMeasureSpec)
  • measureHorizontal(widthMeasureSpec, heightMeasureSpec)

以measureVertical为例

先获取子view数量然后进行遍历,如果heightMode为Match_Parent且高度为0权重大于0 则统计当前Linearlayout总大小,并设置skippedMeasure为true,进入到权重测量(即根据weight来进行二次measure),否则则进入measureChildBeforeLayout(关键方法)该方法点进去其实就是measureChildWithMargins用于把 margin 及 padding 也作为子视图大小的一部分返回,最后进入child.measure计算测量,当子view测量完成后,再由父view设置setMeasuredDimension(widthSizeAndState,heightSizeAndState)决定当前容器大小

widthSizeAndState:

主要由两种情况得出maxWidth

  • maxWidth由所有子view的宽度+margin叠加
  • 当不填充满父view及父的widthMode != MeasureSpec.EXACTLY都满足下
    则取alternativeMaxWidth,alternativeMaxWidth及getSuggestedMinimumWidth通过max得出的值maxWidth,再由resolveSizeAndState(maxWidth, widthMeasureSpec, childState)返回一个合适的大小即widthSizeAndState
    其中weightedMaxWidth:权重 的最大宽
    其中alternativeMaxWidth:改变本地最大宽度
    关于alternativeMaxWidth的获取如下:
  if (lp.weight > 0) {
      //如果子view设置了weight
      weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
  } else {
      alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
  }

  if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
       ····
      alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
  }else{
       ····
      alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                           weightedMaxWidth);
  }

heightSizeAndState:

主要由mTotalLength及getSuggestedMinimumHeight()通过max得出的值heightSize,再由resolveSizeAndState(heightSize, heightMeasureSpec, 0)返回一个合适的大小即heightSizeAndState
其中mTotalLength:包含所有子view大小、mDividerHeight

 void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;
        int maxWidth = 0;
        int childState = 0;
        int alternativeMaxWidth = 0;
        int weightedMaxWidth = 0;
        boolean allFillParent = true;
        float totalWeight = 0;

        final int count = getVirtualChildCount();
        
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        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.
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);

            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }

            if (hasDividerBeforeChildAt(i)) {//如果设置了divider,则加上divider高度
                mTotalLength += mDividerHeight;
            }

            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;
            //如果heightMode为Match_Parent且高度为0权重大于0 则统计当前Linearlayout总大小,并设置skippedMeasure为true,进入到权重测量
            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                int oldHeight = Integer.MIN_VALUE;

                if (lp.height == 0 && lp.weight > 0) {
                    oldHeight = 0;
                    lp.height = LayoutParams.WRAP_CONTENT;
                }

                //该方法主要是将LinearLayout大小传入进去让子view根据传入的mTotalLength计算padding 因为是列表形式延伸子view,所以widthUsed传0 只需传heightUsed,若是纵向延伸子view,则传widthUsed,而heightUsed为0,进入measureChildWithMargins方法(如上有详解)
                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) {
                    //该属性为true的时候, 所有带权重的子元素都会具有最大子元素的最小尺寸。
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }

            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
               mBaselineChildTop = mTotalLength;
            }

            boolean matchWidthLocally = false;
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                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) {
                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);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;

        int delta = heightSize - mTotalLength;
        if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

            mTotalLength = 0;

            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) {
                    // Child said it could absorb extra space -- give him his share
                    int share = (int) (childExtra * delta / weightSum);
                    weightSum -= childExtra;
                    delta -= share;

                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight +
                                    lp.leftMargin + lp.rightMargin, lp.width);

                    if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
                        // child was measured once already above...
                        // base new measurement on stored values
                        int childHeight = child.getMeasuredHeight() + share;
                        if (childHeight < 0) {
                            childHeight = 0;
                        }                   
                        child.measure(childWidthMeasureSpec,
                                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
                    } else {
                        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));
            }

            // Add in our padding
            mTotalLength += mPaddingTop + mPaddingBottom;
            // TODO: Should we recompute the heightSpec based on the new total length?
        } else {
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                           weightedMaxWidth);


            //我们没有限制,所以把所有的加权视图和最大的子view一样高。
            if (useLargestChild && heightMode != 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);

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

resolveSizeAndState 方法

传入最大的大小、父类限制的大小、子view的大小
最终返回一个合适的大小

 public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            case MeasureSpec.AT_MOST:
                if (specSize < size) {
                //当specMode为AT_MOST,并且父控件指定的尺寸specSize小于View自己想要的尺寸时,
                //我们就会用掩码MEASURED_STATE_TOO_SMALL向量算结果加入尺寸太小的标记
                //这样其父ViewGroup就可以通过该标记其给子View的尺寸太小了,
                //然后可能分配更大一点的尺寸给子View
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);//使用了位运行 返回一个带大小和状态的值
    }

getDefaultSize方法

    /**
     * 作用是返回一个默认的值,如果MeasureSpec没有强制限制的话则使.     用提供的大小.否则在允许范围内可任意指定大小
     * 第一个参数size为提供的默认大小,第二个参数为测量的大小
     */
    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        switch (specMode) {
            // Mode = UNSPECIFIED时使用提供的默认大小
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            // Mode = AT_MOST,EXACTLY时使用测量的大小
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }

getSuggestedMinimumHeight方法

获取最小的推荐高度

    protected int getSuggestedMinimumHeight() {
     //如果没有给View设置背景,那么就返回View本身的最小宽度mMinWidth
    //如果给View设置了背景,那么就取View本身最小宽度mMinWidth和背景的最小宽度的最大值
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容