美文网首页Android自定义View
Android view测量原理

Android view测量原理

作者: 我叫王菜鸟 | 来源:发表于2018-06-01 10:35 被阅读9次

    我想了想,如果直接从ViewGroup里面的方法谈起,可能和网上很多博客一样了,但是如果只是向framework开发者分析哪些,又分析不到应用层,所以我觉得应该从performMeasure()这个方法开始分析测量,因为如果在向framework层深入,那就会接触到WindowManagerService,这个过程需要掌握Binder知识,但是Binder知识很多人一时半会掌握不了,尤其是对于application开发者,不关注这些,所以从performMeasure()说起。

    基本概念
    1. MeasureSpec封装了父布局传递给子View的布局要求。
    2. MeasureSpec可以表示宽和高
    3. MeasureSpec由size和mode组成
    //获取大小或模式
    int specSize = MeasureSpec.getSize(measureSpec)
    int specMode = MeasureSpec.getMode(measureSpec)
    //设置
    int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
    

    SpecMode一共有三种:

    • MeasureSpec.EXACTLY : 父容器已经检测出子View所需要的精确大小
    • MeasureSpec.AT_MOST : 父容器未能检测出子View所需要的精确大小,但是指定了一个可用大小即specSize在该模式下,View的测量大小不能超过SpecSize
    • MeasureSpec.UNSPECIFIED : 父容器不对子View的大小做限制

    深入流程

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
    

    mView是根View,也就是decorView,decorView对应的布局是一个FrameLayout,所以我们进入FrameLayout的measure方法

      public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        // measure ourselves, this should set the measured dimension flag back
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ......
      }
    

    在View.measure中又调用了onMeasure(widthMeasureSpec, heightMeasureSpec)方法。
    并且DecorView重写了onMeasure方法,在DecorView.onMeasure方法中主要是进一步确定自己的widthMeasureSpec、heightMeasureSpec,并调用super.onMeasure(widthMeasureSpec, heightMeasureSpec)即FrameLayout.onMeasure方法。

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        //当FrameLayout的宽和高,只有同时设置为match_parent或者指定的size,那么这个
        //measureMatchParentChlidren = false,否则为true。下面会用到这个变量
    
        mMatchParentChildren.clear();
        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;    //宽高的期望类型
    
        for (int i = 0; i < count; i++) {    //一次遍历每一个不为GONE的子view
    
            final View child = getChildAt(i);    
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                //去掉FrameLayout的左右padding,子view的左右margin,这时候,再去
    
                //计算子view的期望的值
    
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    
    
                /*maxWidth找到子View中最大的宽,高同理,为什么要找到他,因为在这里,FrameLayout是wrap
                -content.他的宽高肯定受子view的影响*/
    
                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());
    
                /*下面的判断,只有上面的FragLayout的width和height都设置为match_parent 才不会执行
                此处的mMatchParentChlidren的list里存的是设置为match_parent的子view。
                结合上面两句话的意思,当FrameLayout设置为wrap_content,这时候要把所有宽高设置为
                match_parent的子View都记录下来,记录下来干什么呢?
                这时候FrameLayout的宽高同时受子View的影响*/
    
                 if (measureMatchParentChildren) {
                    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();//这个大小就是子view中设定为match_parent的个数
    
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                //这里看上去重新计算了一遍
    
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
                int childWidthMeasureSpec;
                int childHeightMeasureSpec;
                /*如果子view的宽是match_parent,则宽度期望值是总宽度-padding-margin
                 如果子view的宽是指定的比如100dp,则宽度期望值是padding+margin+width
                 这个很容易理解,下面的高同理*/
                if (lp.width == LayoutParams.MATCH_PARENT) {
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
                            getPaddingLeftWithForeground() - getPaddingRightWithForeground() -
                            lp.leftMargin - lp.rightMargin,
                            MeasureSpec.EXACTLY);
                } else {
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
                            getPaddingTopWithForeground() - getPaddingBottomWithForeground() -
                            lp.topMargin - lp.bottomMargin,
                            MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }
                //把这部分子view重新计算大小
    
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }
    

    上面可以看出使用measureChildWithMargins测量子view的大小

    仔细分析

    我们如何入手进行学习呢?我的想法是从上到下,自下而上。意思就是我们不知道如何下手,所以从最大的开始了解,比如从ViewGroup的measure开始了解流程,自下而上是因为最大的难度大,当我们知道大致流程之后,我们就可以自下而上从View出发深入学习,然后反推至上。下面是ViewGroup对应measure调用顺序图

    LinearLayout.onMeasure(int, int)  (android.widget)//从ViewGroup容器开始向下调用
        LinearLayout.measureVertical(int, int)  (android.widget)//使用measureVertical测量垂直的情况
            LinearLayout.measureChildBeforeLayout(View, int, int, int, int, int)  (android.widget)//测量子view的情况
                ViewGroup.measureChildWithMargins(View, int, int, int, int)  (android.view)//测量子view
    

    在ViewGroup中测量子View时会调用到measureChildWithMargins()方法

    /**
     * @param child
     * 子View
     * @param parentWidthMeasureSpec
     * 父容器(比如LinearLayout)的宽的MeasureSpec
     * @param widthUsed
     * 父容器(比如LinearLayout)在水平方向已经占用的空间大小
     * @param parentHeightMeasureSpec
     * 父容器(比如LinearLayout)的高的MeasureSpec
     * @param heightUsed
     * 父容器(比如LinearLayout)在垂直方向已经占用的空间大小
     */
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
        //得到子View的LayoutParams
        final MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
        //得到子View的宽的MeasureSpec
        final int childWidthMeasureSpec =
                  getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight +
                                      lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
        //得到子View的高的MeasureSpec
        final int childHeightMeasureSpec =
                  getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom +
                                      lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
        //测量子View
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    

    对getChildMeasureSpec()做分析

    //传递的参数parentWidthMeasureSpec
    //mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed = 父view用的size
    //(ViewGroup.MarginLayoutParams) child.getLayoutParams().width  = 如果是精准那就是一个大小值,如果是wrap_content或者match_parent对应为负数
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = View.MeasureSpec.getMode(spec);//父容器的模式
        int specSize = View.MeasureSpec.getSize(spec);//父容器的宽或者高的大小
        //父容器宽 -(父容器(比如LinearLayout)在水平方向已经占用的空间大小 + mPaddingLeft和mPaddingRight表示父容器左右两内侧的padding + lp.leftMargin和lp.rightMargin表示子View左右两外侧的margin + 父容器在水平已经使用的宽)
        //表示子view占用的宽
        int size = Math.max(0, specSize - padding);
    
        int resultSize = 0;
        int resultMode = 0;
        //根据父容器的测量模式
        switch (specMode) {
            case View.MeasureSpec.EXACTLY:
                //我们首先看到一个if判断if (childDimension >= 0),或许看到这有点懵了:childDimension>=0是啥意思?难道还有小于0的情况?是的,请注意两个系统常量:LayoutParams.MATCH_PARENT=-1和LayoutParams.WRAP_CONTENT=-2 
                if (childDimension >= 0) {
                    resultSize = childDimension;//表示子View的宽或高不是match_parent,也不是wrap_content而是一个具体的数值,比如100px。
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    //子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size,子View的mode也为MeasureSpec.EXACTLY
                    resultSize = size;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    //子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size,子View的mode为MeasureSpec.AT_MOST
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
                }
                break;
    
            case View.MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    //表示子View的宽或高不是match_parent,也不是wrap_content而是一个具体的数值,比如100px
                    //子View的size就是childDimension,子View的mode也为MeasureSpec.EXACTLY
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    //子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size,子View的mode也为MeasureSpec.AT_MOST
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    //子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size,子View的mode也为MeasureSpec.AT_MOST
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
                }
                break;
    
            case View.MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    //表示子View的宽或高不是match_parent,也不是wrap_content而是一个具体的数值,比如100px
                    //子View的size就是childDimension,子View的mode也为MeasureSpec.EXACTLY
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    //子View的size为0,子View的mode为MeasureSpec.UNSPECIFIED
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = View.MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    //子View的size为0,子View的mode为MeasureSpec.UNSPECIFIED
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = View.MeasureSpec.UNSPECIFIED;
                }
                break;
        }
        return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
    

    这样就计算出来子view的模式和大小,也同时说明了子view的大小和模式依赖于父view的大小和模式

    . 具体大小 match_parent wrap_content
    EXACTLY childDimension / EXACTLY size / EXACTLY size / AT_MOST
    AT_MOST childDimension / EXACTLY size / AT_MOST size / AT_MOST
    UNSPECIFIED childDimension / EXACTLY 0 / UNSPECIFIED 0 / UNSPECIFIED
    • size是父容器的剩余大小,包括除去padding,margin,父容器使用空间
    • childDimension是用户设置layout_height或者layout_width的具体值

    下面进入最后一步:

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    

    onMeasure( )源码流程如下:
    (1) 在onMeasure调用setMeasuredDimension( )设置View的宽和高.
    (2) 在setMeasuredDimension()中调用getDefaultSize()获取View的宽和高.
    (3) 在getDefaultSize()方法中又会调用到getSuggestedMinimumWidth()或者getSuggestedMinimumHeight()获取到View宽和高的最小值.

    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
    

    该方法返回View的宽度的最小值MinimumWidth.

    下面看getDefaultSize():

    public static int getDefaultSize(int size, int measureSpec) {
         int result = size;//该方法的第一个输入参数size就是调用getSuggestedMinimumWidth()方法获得的View的宽或高的最小值。
         int specMode = MeasureSpec.getMode(measureSpec);
         int specSize = MeasureSpec.getSize(measureSpec);
         switch (specMode) {
           case MeasureSpec.UNSPECIFIED:
               result = size;
               break;
           case MeasureSpec.AT_MOST:
           case MeasureSpec.EXACTLY:
               result = specSize;
               break;
         }
         return result;
    }
    
    0.png

    该方法用于获取View的宽或者高的大小。所以除去第一种情况也就是极少情况出现的UNSPECIFIED以外在measure阶段View的宽和高由其measureSpec中的specSize决定!!

    如果子View在XML布局文件中对于大小的设置采用wrap_content,那么不管父View的specMode是MeasureSpec.AT_MOST还是MeasureSpec.EXACTLY对于子View而言系统给它设置的specMode都是MeasureSpec.AT_MOST,并且其大小都是parentLeftSize即父View目前剩余的可用空间

    最后setMeasuredDimension():

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
         mMeasuredWidth = measuredWidth;
         mMeasuredHeight = measuredHeight;
         mPrivateFlags |= MEASURED_DIMENSION_SET;
    }
    

    在此调用setMeasuredDimension( )设置View的宽和高的测量值。

    LinearLayout.layout()

        void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
            mTotalLength = 0;//记录内部使用的高度,别被字面意思误导了以为是LinearLayout的高度
            int maxWidth = 0;
            int childState = 0;
            int alternativeMaxWidth = 0;
            int weightedMaxWidth = 0;
            boolean allFillParent = true;
            float totalWeight = 0;//权重值的总和
    
            final int count = getVirtualChildCount();//子view的数量,都是调用getChildCount()
    
            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;
            int consumedExcessSpace = 0;
    
            int nonSkippedChildCount = 0;
    
            // See how tall everyone is. Also remember max width.
            for (int i = 0; i < count; ++i) {
                //获取view
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }
                //如果是GONE就过
                if (child.getVisibility() == View.GONE) {
                   i += getChildrenSkipCount(child, i);
                   continue;
                }
                
                nonSkippedChildCount++;
                //有分割线就加上
                if (hasDividerBeforeChildAt(i)) {
                    mTotalLength += mDividerHeight;
                }
                //有时候我们在代码里面通过Inflater服务,动态加载一个布局,然后去设置他的LayoutParams,
                //如果不引用父容器的LayoutParams就会报一个强转错误,原因就在这个 父容器在add,
                //measure的时候都会把子View的LayoutParams强转成自己的类型
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    
                totalWeight += lp.weight;//计入总权重
    
                
                //这里就值得注意下了如果当前的LinearLayout是EXACTLY模式,且子view的高度为0,且权重大于0
                //这个子view只有在LinearLayout高度有剩余的时候,才会根据权重的占比去平分剩余空间
                //上文说的二次测量也就指的这部分
                final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
                if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                    skippedMeasure = true;
                } else {
                    if (useExcessSpace) {
                        lp.height = LayoutParams.WRAP_CONTENT;
                    }
                    final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                    //这里就开始测算子View了,使用measureChildWithMargins
                    //如果当前的LinearLayout不是EXACTLY模式,且子View的weight大于0,优先会把当前LinearLayout的全部可用高度用于子View测量
                    measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                            heightMeasureSpec, usedHeight);
    
                    final int childHeight = child.getMeasuredHeight();
                    if (useExcessSpace) {
                        // Restore the original height and record how much space
                        // we've allocated to excess-only children so that we can
                        // match the behavior of EXACTLY measurement.
                        lp.height = 0;
                        consumedExcessSpace += childHeight;
                    }
    
                    final int totalLength = mTotalLength;
                    //加上子View的margin值
                    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 (nonSkippedChildCount > 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));
                }
            }
    
            // 当测量完子View的大小后,总高度会再加上padding的高度
            mTotalLength += mPaddingTop + mPaddingBottom;
    
            int heightSize = mTotalLength;
    
            //如果设置了minimumheight属性,会根据当前使用高度和最小高度进行比较
            //然后取两者中大的值
            heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
    
            // Reconcile our calculated size with the heightMeasureSpec
            int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
            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.
            //到了这里,会再对带weight属性的子View进行一次测绘
            //首先计算属于高度
            int remainingExcess = heightSize - mTotalLength
                    + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
            if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
                 //如果设置了weightSum就会使用你设置的weightSum,否则采用当前所有子View的权重和。所以如果要手动设置weightSum的时候
                float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
    
                mTotalLength = 0;
                //这里的代码就和第一次测量很像了
                for (int i = 0; i < count; ++i) {
                    final View child = getVirtualChildAt(i);
                    if (child == null || child.getVisibility() == View.GONE) {
                        continue;
                    }
    
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                    final float childWeight = lp.weight;
                    if (childWeight > 0) {
                        final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                        remainingExcess -= share;
                        remainingWeightSum -= childWeight;
    
                        final int childHeight;
                        if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
                            childHeight = largestChildHeight;
                        } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                                || heightMode == MeasureSpec.EXACTLY)) {
                            // This child needs to be laid out from scratch using
                            // only its share of excess space.
                            childHeight = share;
                        } else {
                            // This child had some intrinsic height to which we
                            // need to add its share of excess space.
                            childHeight = child.getMeasuredHeight() + share;
                        }
    
                        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                                Math.max(0, childHeight), MeasureSpec.EXACTLY);
                        final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                                lp.width);
                        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    
                        // 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);
    
    
                // 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) {
                    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);
            }
        }
    

    遍历每个子View,并对每个子View调用measureChildBeforeLayout()在measureChildBeforeLayout()方法内又会调用measureChildWithMargins()从而测量每个子View的大小。在该过程中mTotalLength保存了LinearLayout的高度,所以每当测量完一个子View该值都会发生变化。最后通过setMeasuredDimension()设置LinearLayout的大小

    参考:
    https://blog.csdn.net/lfdfhl/article/details/51347818
    https://my.oschina.net/u/1777377/blog/415845

    相关文章

      网友评论

        本文标题:Android view测量原理

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