Android view測(cè)量原理

我想了想,如果直接從ViewGroup里面的方法談起,可能和網(wǎng)上很多博客一樣了代赁,但是如果只是向framework開發(fā)者分析哪些,又分析不到應(yīng)用層兽掰,所以我覺得應(yīng)該從performMeasure()這個(gè)方法開始分析測(cè)量芭碍,因?yàn)槿绻谙騠ramework層深入,那就會(huì)接觸到WindowManagerService孽尽,這個(gè)過程需要掌握Binder知識(shí)窖壕,但是Binder知識(shí)很多人一時(shí)半會(huì)掌握不了,尤其是對(duì)于application開發(fā)者杉女,不關(guān)注這些瞻讽,所以從performMeasure()說起。

基本概念
  1. MeasureSpec封裝了父布局傳遞給子View的布局要求熏挎。
  2. MeasureSpec可以表示寬和高
  3. MeasureSpec由size和mode組成
//獲取大小或模式
int specSize = MeasureSpec.getSize(measureSpec)
int specMode = MeasureSpec.getMode(measureSpec)
//設(shè)置
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);

SpecMode一共有三種:

  • MeasureSpec.EXACTLY : 父容器已經(jīng)檢測(cè)出子View所需要的精確大小
  • MeasureSpec.AT_MOST : 父容器未能檢測(cè)出子View所需要的精確大小速勇,但是指定了一個(gè)可用大小即specSize在該模式下,View的測(cè)量大小不能超過SpecSize
  • MeasureSpec.UNSPECIFIED : 父容器不對(duì)子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對(duì)應(yīng)的布局是一個(gè)FrameLayout,所以我們進(jìn)入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中又調(diào)用了onMeasure(widthMeasureSpec, heightMeasureSpec)方法廉白。
并且DecorView重寫了onMeasure方法个初,在DecorView.onMeasure方法中主要是進(jìn)一步確定自己的widthMeasureSpec、heightMeasureSpec猴蹂,并調(diào)用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;
    //當(dāng)FrameLayout的寬和高,只有同時(shí)設(shè)置為match_parent或者指定的size磅轻,那么這個(gè)
    //measureMatchParentChlidren = false珍逸,否則為true。下面會(huì)用到這個(gè)變量

    mMatchParentChildren.clear();
    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;    //寬高的期望類型

    for (int i = 0; i < count; i++) {    //一次遍歷每一個(gè)不為GONE的子view

        final View child = getChildAt(i);    
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            //去掉FrameLayout的左右padding聋溜,子view的左右margin谆膳,這時(shí)候,再去

            //計(jì)算子view的期望的值

            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();


            /*maxWidth找到子View中最大的寬撮躁,高同理漱病,為什么要找到他,因?yàn)樵谶@里,F(xiàn)rameLayout是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都設(shè)置為match_parent 才不會(huì)執(zhí)行
            此處的mMatchParentChlidren的list里存的是設(shè)置為match_parent的子view漓穿。
            結(jié)合上面兩句話的意思,當(dāng)FrameLayout設(shè)置為wrap_content注盈,這時(shí)候要把所有寬高設(shè)置為
            match_parent的子View都記錄下來晃危,記錄下來干什么呢?
            這時(shí)候FrameLayout的寬高同時(shí)受子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());
    }

    //設(shè)置測(cè)量過的寬高
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
    count = mMatchParentChildren.size();//這個(gè)大小就是子view中設(shè)定為match_parent的個(gè)數(shù)

    if (count > 1) {
        for (int i = 0; i < count; i++) {
            //這里看上去重新計(jì)算了一遍

            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
             這個(gè)很容易理解,下面的高同理*/
            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重新計(jì)算大小

            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}

上面可以看出使用measureChildWithMargins測(cè)量子view的大小

仔細(xì)分析

我們?nèi)绾稳胧诌M(jìn)行學(xué)習(xí)呢?我的想法是從上到下胧砰,自下而上鳍鸵。意思就是我們不知道如何下手,所以從最大的開始了解朴则,比如從ViewGroup的measure開始了解流程权纤,自下而上是因?yàn)樽畲蟮碾y度大钓简,當(dāng)我們知道大致流程之后乌妒,我們就可以自下而上從View出發(fā)深入學(xué)習(xí),然后反推至上外邓。下面是ViewGroup對(duì)應(yīng)measure調(diào)用順序圖

LinearLayout.onMeasure(int, int)  (android.widget)//從ViewGroup容器開始向下調(diào)用
    LinearLayout.measureVertical(int, int)  (android.widget)//使用measureVertical測(cè)量垂直的情況
        LinearLayout.measureChildBeforeLayout(View, int, int, int, int, int)  (android.widget)//測(cè)量子view的情況
            ViewGroup.measureChildWithMargins(View, int, int, int, int)  (android.view)//測(cè)量子view

在ViewGroup中測(cè)量子View時(shí)會(huì)調(diào)用到measureChildWithMargins()方法

/**
 * @param child
 * 子View
 * @param parentWidthMeasureSpec
 * 父容器(比如LinearLayout)的寬的MeasureSpec
 * @param widthUsed
 * 父容器(比如LinearLayout)在水平方向已經(jīng)占用的空間大小
 * @param parentHeightMeasureSpec
 * 父容器(比如LinearLayout)的高的MeasureSpec
 * @param heightUsed
 * 父容器(比如LinearLayout)在垂直方向已經(jīng)占用的空間大小
 */
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);
    //測(cè)量子View
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

對(duì)getChildMeasureSpec()做分析

//傳遞的參數(shù)parentWidthMeasureSpec
//mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed = 父view用的size
//(ViewGroup.MarginLayoutParams) child.getLayoutParams().width  = 如果是精準(zhǔn)那就是一個(gè)大小值撤蚊,如果是wrap_content或者match_parent對(duì)應(yīng)為負(fù)數(shù)
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = View.MeasureSpec.getMode(spec);//父容器的模式
    int specSize = View.MeasureSpec.getSize(spec);//父容器的寬或者高的大小
    //父容器寬 -(父容器(比如LinearLayout)在水平方向已經(jīng)占用的空間大小 + mPaddingLeft和mPaddingRight表示父容器左右兩內(nèi)側(cè)的padding + lp.leftMargin和lp.rightMargin表示子View左右兩外側(cè)的margin + 父容器在水平已經(jīng)使用的寬)
    //表示子view占用的寬
    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;
    //根據(jù)父容器的測(cè)量模式
    switch (specMode) {
        case View.MeasureSpec.EXACTLY:
            //我們首先看到一個(gè)if判斷if (childDimension >= 0),或許看到這有點(diǎn)懵了:childDimension>=0是啥意思损话?難道還有小于0的情況侦啸?是的,請(qǐng)注意兩個(gè)系統(tǒng)常量:LayoutParams.MATCH_PARENT=-1和LayoutParams.WRAP_CONTENT=-2 
            if (childDimension >= 0) {
                resultSize = childDimension;//表示子View的寬或高不是match_parent丧枪,也不是wrap_content而是一個(gè)具體的數(shù)值光涂,比如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而是一個(gè)具體的數(shù)值恋博,比如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而是一個(gè)具體的數(shù)值,比如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);
}

這樣就計(jì)算出來子view的模式和大小,也同時(shí)說明了子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是用戶設(shè)置layout_height或者layout_width的具體值

下面進(jìn)入最后一步:

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

onMeasure( )源碼流程如下:
(1) 在onMeasure調(diào)用setMeasuredDimension( )設(shè)置View的寬和高.
(2) 在setMeasuredDimension()中調(diào)用getDefaultSize()獲取View的寬和高.
(3) 在getDefaultSize()方法中又會(huì)調(diào)用到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;//該方法的第一個(gè)輸入?yún)?shù)size就是調(diào)用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的寬或者高的大小饲宛。所以除去第一種情況也就是極少情況出現(xiàn)的UNSPECIFIED以外在measure階段View的寬和高由其measureSpec中的specSize決定!嗜价!

如果子View在XML布局文件中對(duì)于大小的設(shè)置采用wrap_content艇抠,那么不管父View的specMode是MeasureSpec.AT_MOST還是MeasureSpec.EXACTLY對(duì)于子View而言系統(tǒng)給它設(shè)置的specMode都是MeasureSpec.AT_MOST,并且其大小都是parentLeftSize即父View目前剩余的可用空間

最后setMeasuredDimension():

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

在此調(diào)用setMeasuredDimension( )設(shè)置View的寬和高的測(cè)量值久锥。

LinearLayout.layout()

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;//記錄內(nèi)部使用的高度家淤,別被字面意思誤導(dǎo)了以為是LinearLayout的高度
        int maxWidth = 0;
        int childState = 0;
        int alternativeMaxWidth = 0;
        int weightedMaxWidth = 0;
        boolean allFillParent = true;
        float totalWeight = 0;//權(quán)重值的總和

        final int count = getVirtualChildCount();//子view的數(shù)量,都是調(diào)用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;
            }
            //有時(shí)候我們?cè)诖a里面通過Inflater服務(wù),動(dòng)態(tài)加載一個(gè)布局瑟由,然后去設(shè)置他的LayoutParams絮重,
            //如果不引用父容器的LayoutParams就會(huì)報(bào)一個(gè)強(qiáng)轉(zhuǎn)錯(cuò)誤,原因就在這個(gè) 父容器在add,
            //measure的時(shí)候都會(huì)把子View的LayoutParams強(qiáng)轉(zhuǎn)成自己的類型
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;//計(jì)入總權(quán)重

            
            //這里就值得注意下了如果當(dāng)前的LinearLayout是EXACTLY模式歹苦,且子view的高度為0青伤,且權(quán)重大于0
            //這個(gè)子view只有在LinearLayout高度有剩余的時(shí)候,才會(huì)根據(jù)權(quán)重的占比去平分剩余空間
            //上文說的二次測(cè)量也就指的這部分
            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;
                //這里就開始測(cè)算子View了,使用measureChildWithMargins
                //如果當(dāng)前的LinearLayout不是EXACTLY模式殴瘦,且子View的weight大于0狠角,優(yōu)先會(huì)把當(dāng)前LinearLayout的全部可用高度用于子View測(cè)量
                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));
            }
        }

        // 當(dāng)測(cè)量完子View的大小后,總高度會(huì)再加上padding的高度
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        //如果設(shè)置了minimumheight屬性蚪腋,會(huì)根據(jù)當(dāng)前使用高度和最小高度進(jìn)行比較
        //然后取兩者中大的值
        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.
        //到了這里丰歌,會(huì)再對(duì)帶weight屬性的子View進(jìn)行一次測(cè)繪
        //首先計(jì)算屬于高度
        int remainingExcess = heightSize - mTotalLength
                + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
        if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
             //如果設(shè)置了weightSum就會(huì)使用你設(shè)置的weightSum,否則采用當(dāng)前所有子View的權(quán)重和屉凯。所以如果要手動(dòng)設(shè)置weightSum的時(shí)候
            float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

            mTotalLength = 0;
            //這里的代碼就和第一次測(cè)量很像了
            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);
        }
    }

遍歷每個(gè)子View立帖,并對(duì)每個(gè)子View調(diào)用measureChildBeforeLayout()在measureChildBeforeLayout()方法內(nèi)又會(huì)調(diào)用measureChildWithMargins()從而測(cè)量每個(gè)子View的大小。在該過程中mTotalLength保存了LinearLayout的高度悠砚,所以每當(dāng)測(cè)量完一個(gè)子View該值都會(huì)發(fā)生變化晓勇。最后通過setMeasuredDimension()設(shè)置LinearLayout的大小

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市哩簿,隨后出現(xiàn)的幾起案子宵蕉,更是在濱河造成了極大的恐慌,老刑警劉巖节榜,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羡玛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡宗苍,警方通過查閱死者的電腦和手機(jī)稼稿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門薄榛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人让歼,你說我怎么就攤上這事敞恋。” “怎么了谋右?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵硬猫,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我改执,道長(zhǎng)啸蜜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任辈挂,我火速辦了婚禮衬横,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘终蒂。我一直安慰自己蜂林,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布拇泣。 她就那樣靜靜地躺著噪叙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挫酿。 梳的紋絲不亂的頭發(fā)上构眯,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天愕难,我揣著相機(jī)與錄音早龟,去河邊找鬼。 笑死猫缭,一個(gè)胖子當(dāng)著我的面吹牛葱弟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播猜丹,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼芝加,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了射窒?” 一聲冷哼從身側(cè)響起藏杖,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脉顿,沒想到半個(gè)月后蝌麸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡艾疟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年来吩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了敢辩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弟疆,死狀恐怖戚长,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怠苔,我是刑警寧澤同廉,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站柑司,受9級(jí)特大地震影響恤溶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜帜羊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一咒程、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧讼育,春花似錦帐姻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至痹籍,卻和暖如春呢铆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹲缠。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工棺克, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人线定。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓娜谊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親斤讥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纱皆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容