美文网首页
从源码层剖析View的Measure过程

从源码层剖析View的Measure过程

作者: 马小鹏marco | 来源:发表于2018-07-08 16:41 被阅读0次

    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());
        }
    

    相关文章

      网友评论

          本文标题:从源码层剖析View的Measure过程

          本文链接:https://www.haomeiwen.com/subject/mnhuyftx.html