从 Android 自定义 View 学习系列计划开始,到现在也有挺长时间了,上一篇 Android 自定义 View 之常用工具源码分析讲到了 Android 开发过程中常使用到的一些工具,都从源码上进行了分析,让大家对要使用的工具都有了一定的了解。

我们都知道,自定义 View 的三个重要过程分别是 measure、layout、draw,而 measure 处于这条处理链的首端,自然是非常重要的。所以接下来的这一篇 Android 自定义 View 之 onMeasure() 的源码分析及重写我们就来看一看自定义 View 里经常用到的 onMeasure() 方法的源码分析以及它们的重写!!


系统显示一个 View,首先需要通过测量(measure)该 View 来获得其长和宽从而确定显示该 View 时需要多大的空间。而在测量的过程中 MeasureSpec 贯穿全程,发挥着不可或缺的作用。 所以,了解 View 的测量过程,最合适的切入点就是 MeasureSpec。

MeasureSpec

我们来看一下 Google官方文档对 MeasureSpec 的描述

A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode.

大概意思就是,MeasureSpec 封装了从父节点传递给子节点的布局要求。每个 MeasureSpec 表示宽度或高度的要求。度量规格包括 size(大小)和 mode(模式)。

这句话里透漏了几个重点:

  • MeasureSpec 封装了从父节点传递给子节点的布局要求

  • MeasureSpec 可以表示宽度和高度

  • MeasureSpec 由 size 和 mode 组成

MeasureSpec 通常翻译为”测量规格”,它是一个 32 位的 int 数据。其中高 2 位代表 SpecMode 即某种测量模式(mode),低 30位 为 SpecSize 代表在该模式下的规格大小(size)。
可以通过如下方式分别获取这两个值:

获取模式(mode)

int specMode = MeasureSpec.getMode(measureSpec);

获取大小(size)

int specSize = MeasureSpec.getSize(measureSpec);

还可以通过上面的两个值生成新的MeasureSpec

int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);

那么所谓从父节点传递给子节点的布局要求是什么呢?MeasureSpec里有着三种模式(mode),我们来看一下:

UNSPECIFIED

The parent has not imposed any constraint on the child. It can be whatever size it wants.

父View没有对子View施加任何约束。它可以是任何它想要的大小。

EXACTLY

The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.

父View已确定子View的确切大小。子View将被给予父View所确定的大小,无论它想要多大。

AT_MOST

The child can be as large as it wants up to the specified size.

父View不限制View的大小,子View可以得到它想要达到的大小。

好了,接下来让我们从源码角度来分析一下它们是怎么形成的:

/**     * @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) {        final MarginLayoutParams lp = (ViewGroup.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);    }

需要注意里面的参数,通过这些参数看出,该方法要测量子View传进来的参数却包含了父容器的宽的MeasureSpec,父容器在水平方向已经占用的空间大小,父容器的高的MeasureSpec,父容器在垂直方向已经占用的空间大小等父View相关的信息。这在一定程度体现了:父View影响着子View的MeasureSpec的生成。

该方法主要有四步操作:

  • 得到子View的LayoutParams

  • 得到子View的宽的MeasureSpec

  • 得到子View的高的MeasureSpec

  • 测量子View

我们可以看到第二步和第三步都调用到了getChildMeasureSpec()方法,那么在该方法内部又做了哪些操作呢?我们来看一下getChildMeasureSpec( )的源码

 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {        int specMode = View.MeasureSpec.getMode(spec);        int specSize = View.MeasureSpec.getSize(spec);        int size = Math.max(0, specSize - padding);        int resultSize = 0;        int resultMode = 0;        switch (specMode) {            case View.MeasureSpec.EXACTLY:                if (childDimension >= 0) {                    resultSize = childDimension;                    resultMode = View.MeasureSpec.EXACTLY;                } else if (childDimension == LayoutParams.MATCH_PARENT) {                    resultSize = size;                    resultMode = View.MeasureSpec.EXACTLY;                } else if (childDimension == LayoutParams.WRAP_CONTENT) {                    resultSize = size;                    resultMode = View.MeasureSpec.AT_MOST;                }                break;            case View.MeasureSpec.AT_MOST:                if (childDimension >= 0) {                    resultSize = childDimension;                    resultMode = View.MeasureSpec.EXACTLY;                } else if (childDimension == LayoutParams.MATCH_PARENT) {                    resultSize = size;                    resultMode = View.MeasureSpec.AT_MOST;                } else if (childDimension == LayoutParams.WRAP_CONTENT) {                    resultSize = size;                    resultMode = View.MeasureSpec.AT_MOST;                }                break;            case View.MeasureSpec.UNSPECIFIED:                if (childDimension >= 0) {                    resultSize = childDimension;                    resultMode = View.MeasureSpec.EXACTLY;                } else if (childDimension == LayoutParams.MATCH_PARENT) {                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                    resultMode = View.MeasureSpec.UNSPECIFIED;                } else if (childDimension == LayoutParams.WRAP_CONTENT) {                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                    resultMode = View.MeasureSpec.UNSPECIFIED;                }                break;        }        return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }

应该可以看出来这就是确定子View的MeasureSpec的具体实现了吧!!

接下来我们对源码进行分析

首先来看一下getChildMeasureSpec()方法的内部参数

  • spec

父View(比如LinearLayout)的宽或高的MeasureSpec

  • padding

父容器(比如LinearLayout)在垂直方向或者水平方向已经被占用的空间。

为什么这么说呢?

我们可以看measureChildWithMargins()方法里调用getChildMeasureSpec()的这一部分,传递给getChildMeasureSpec()的第二个参数构成如下: 例如

mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin

其中

mPaddingLeft和mPaddingRight表示父容器左右两内侧的padding

lp.leftMargin和lp.rightMargin表示子View左右两外侧的margin

这四部分都不可以再被利用起来布局子View。所以说这些值的和表示:父容器在水平方向已经被占用的空间

同理,mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
表示父容器(比如LinearLayout)在垂直方向已被占用的空间。

  • childDimension

通过子View的LayoutParams获取到的子View的宽或高

从getChildMeasureSpec()方法的第一个参数spec和第二个参数padding也可以看出,父容器(如LinearLayout)的MeasureSpec和子View的LayoutParams共同决定着子View的MeasureSpec!!

好,了解了方法的参数后我们来分析一下getChildMeasureSpec()方法的具体实现步骤

  • 得到父容器的specMode和specSize

  • 得到父容器在水平方向或垂直方向可用的最大空间值

  • 确定子View的specMode和specSize

在这里有一个switch语句,该语句的判断条件就是父View的specMode;在此根据父View的specMode的不同来决定子View的specMode和specSize。

接下来我们来分析一下这个switch()语句

case 1 中父容器的specMode为EXACTLY,内部接 if else-if else-if语句,分别对应三种情况。

  • if (childDimension >= 0)

  • else if (childDimension == LayoutParams.MATCH_PARENT)

  • else if (childDimension == LayoutParams.WRAP_CONTENT)

有些朋友一看到 childDimension >= 0 就懵逼了,这是什么鬼?在这里我们有必要说明一下两个系统常量:

LayoutParams.MATCH_PARENT=-1

LayoutParams.WRAP_CONTENT=-2

所以 childDimension >= 0 的意思是除了MATCH_PARENT和WRAP_CONTENT这两种情况外的所有情况,其实也就是说当传入父容器的模式既不是MATCH_PARENT也不是WRAP_CONTENT,而是一个具体数值的情况时。

if (childDimension >= 0) 

所以上面这一句代码的意思表示子View的宽或高不是match_parent,也不是wrap_content而是一个具体的数值,比如100px。

此时子View的size就是childDimension,子View的mode也为MeasureSpec.EXACTLY,即:

resultSize = childDimension; resultMode = MeasureSpec.EXACTLY;
else if (childDimension == LayoutParams.MATCH_PARENT)

这一句代码表示子View的宽或高是LayoutParams.MATCH_PARENT。

此时子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size,子View的mode也为MeasureSpec.EXACTLY,即:

resultSize = size; resultMode = MeasureSpec.EXACTLY;
else if (childDimension == LayoutParams.WRAP_CONTENT)

这一句表示子View的宽或高是LayoutParams.WRAP_CONTENT。

此时子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size,子View的mode为MeasureSpec.AT_MOST,即:

resultSize = size; resultMode = MeasureSpec.AT_MOST;

另外两种模式传入时父容器的specMode不同,同样对应三种情况以及不同的size和mode,就不再讲解了,大家对着源码看就知道了。

到这一步,我们已经可以清楚的知道

子View的MeasureSpec由其父容器的MeasureSpec和该子View本身的布局参数LayoutParams共同决定。

经过测量得出的子View的MeasureSpec是系统给出的一个期望值(参考值),我们也可摒弃系统的这个测量流程,直接调用setMeasuredDimension( )设置子View的宽和高的测量值。


onMeasure()方法源码分析

到这里终于把MeasureSpec搞懂了,那么接下来就可以开始看我们的主角了!!

我们来看一下View的onMeasure()方法的源码

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宽和高的最小值

这些方法的调用顺序如下图所示

为了理解onMeasure到底做了什么,以及这几个方法分别做了什么,它们之间又有什么联系,我们从后面按顺序分别对这几个方法的源码进行分析,这样就可以更好的理解上一层方法是如何调用小一层方法的了。

首先是getSuggestedMinimumWidth()和getSuggestedMinimumHeight(),它们的源码如下

//Returns the suggested minimum width that the view should use protected int getSuggestedMinimumWidth() {      return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());  } 
//Returns the suggested minimum height that the view should use protected int getSuggestedMinimumHeight() {      return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());  } 

这两个方法分别返回View的宽度和高度的最小值MinimumWidth、MinimumHeight

在此需要注意该View是否有背景

(1) 若该View没有背景
那么该MinimumWidth为View本身的最小宽度即mMinWidth

有两种方法可以设置该mMinWidth值:

  • 在XML布局文件中定义minWidth

  • 调用View的setMinimumWidth()方法为该值赋值

(2) 若该View有背景
那么该MinimumWidth为View本身最小宽度mMinWidth和View背景的最小宽度的最大值

getSuggestedMinimumHeight()方法与getSuggestedMinimumWidth()类似,不再多说


然后是getDefaultSize(),我们来看一下它的源码

public static int getDefaultSize(int size, int measureSpec) {       int result = size;       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;  } 

该方法用于获取View的宽或者高的大小

该方法的第一个输入参数size就是通过调用getSuggestedMinimumWidth()方法获得的View的宽或高的最小值

从getDefaultSize()的源码里的switch语句可看出该方法的返回值有两种情况:

(1) measureSpec的specMode为MeasureSpec.UNSPECIFIED 时
在此情况下该方法的返回值就是View的宽或者高最小值
这种情况很少见,可忽略不计

(2) measureSpec的specMode为MeasureSpec.AT_MOST或MeasureSpec.EXACTLY 时:
在此情况下getDefaultSize()的返回值就是该子View的measureSpec中的specSize

除去第一种情况以外,可知,在measure阶段View的宽和高由其measureSpec中的specSize决定!!

如果子View在XML布局文件中对于大小的设置采用wrap_content,那么不管父View的specMode是 MeasureSpec.AT_MOST还是MeasureSpec.EXACTLY 对于子View而言系统给它设置的specMode都是MeasureSpec.AT_MOST,并且其大小都是parentLeftSize即父View目前剩余的可用空间。这时wrap_content就失去了原本的意义,变成了match_parent一样了。

所以自定义View在重写onMeasure()的过程中应该手动处理View的宽或高为wrap_content的情况


最后是setMeasuredDimension(),同样的,我们来看一下它的源码

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

经过调用前面的各种方法,终于得到了View的宽度和高度

在这里就可以通过setMeasuredDimension()方法来设置View的宽度和高度的测量值


重写onMeasure()方法

上面说到,当子View在XML布局文件设置的宽或高为wrap_content时,系统给子View设置的specMode都是MeasureSpec.AT_MOST,这时wrap_content就失去了原本的意义,变成了match_parent一样。所以此时我们需要重写onMeasure来处理这种情况。

那么我们该怎么对这两种情况进行处理呢?

第一种情况:
如果在xml布局中View的宽和高均用wrap_content,那么需要设置View的宽和高为mWidth和mHeight

第二种情况:
如果在xml布局中View的只是宽或高其中一个为wrap_content,那么就将该值设置为默认的宽或高,另外的一个值采用系统测量的specSize即可

具体的实现可以这样做:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec , heightMeasureSpec);      int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);      int widthSpceSize = MeasureSpec.getSize(widthMeasureSpec);      int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);      int heightSpceSize=MeasureSpec.getSize(heightMeasureSpec);      if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){          setMeasuredDimension(mWidth, mHeight);      }else if(widthSpecMode==MeasureSpec.AT_MOST){          setMeasuredDimension(mWidth, heightSpceSize);      }else if(heightSpecMode==MeasureSpec.AT_MOST){          setMeasuredDimension(widthSpceSize, mHeight);      }   }  

上面的代码主要分两步完成

  • 调用super.onMeasure()

  • 处理View的大小为wrap_content时的情况

我们看下面这张图,会更清晰一点,要进行处理的两种情况是最后一行左边两个mode为AT_MOST的

parentSpecMode\childLayoutParams EXACTLY AT_MOST UNSPECIFIED
dp/px EXACTLY/childSize EXACTLY/childSize EXACTLY/childSize
match_parent EXACTLY/parentS ize AT_MOST/parentSize UNSPECIFIED/0
wrap_content AT_MOST/parentSize AT_MOST/parentSize UNSPECIFIED/0

其实,Andorid系统的控件比如TextView等也在onMeasure()中对其大小为wrap_content这一情况作了特殊的处理。

请注意在第二步的代码中用的判断条件:

widthSpecMode==MeasureSpec.AT_MOST或者heightSpecMode==MeasureSpec.AT_MOST或者兼而有之;

这里的关键是模式(mode)MeasureSpec.AT_MOST


ViewGroup.measure

看完了View中的onMeasure方法,接下来我们继续看ViewGroup中的measure()方法

public abstract class ViewGroup extends View implements ViewParent,ViewManager{ }

我们可以看到ViewGroup是一个抽象类,它没有重写View的onMeasure( )但它提供了measureChildren( )测量所有的子View。在measureChildren()方法中调用measureChild( )测量每个子View,在该方法内又会调用到child.measure( )方法,这又回到了前面熟悉的流程。

即ViewGroup中测量子View的流程:

既然ViewGroup继承自View而且它是一个抽象类,那么ViewGroup的子类就应该根据自身的要求和特点重写onMeasure( )方法。

那么这些ViewGroup的子类会怎么测量子View和确定自身的大小呢?
如果ViewGroup知道了每个子View的大小,将它们累加起来是不是就知道了自身的大小呢?

顺着这个思路,我们选择ViewGroup的子类LinearLayout来看,就从它的onMeasure( )开始看吧!!

 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        if (mOrientation == VERTICAL) {            measureVertical(widthMeasureSpec, heightMeasureSpec);        } else {            measureHorizontal(widthMeasureSpec, heightMeasureSpec);        }    }

我们可以看到,代码里分了水平线性和垂直线性这两种情况进行测量,类似的逻辑在其onLayout( )阶段也会看到。我们就选measureVertical( )继续往下看

  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;        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)) {                mTotalLength += mDividerHeight;            }            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();            totalWeight += lp.weight;            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;                }                measureChildBeforeLayout(                       child, i, widthMeasureSpec, 0, heightMeasureSpec,                       totalWeight == 0 ? mTotalLength : 0);                if (oldHeight != Integer.MIN_VALUE) {                   lp.height = oldHeight;                }                final int childHeight = child.getMeasuredHeight();                final int totalLength = mTotalLength;                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +                       lp.bottomMargin + getNextLocationOffset(child));                if (useLargestChild) {                    largestChildHeight = Math.max(childHeight, largestChildHeight);                }            }            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {               mBaselineChildTop = mTotalLength;            }            if (i < baselineChildIndex && lp.weight > 0) {                throw new RuntimeException("Exception");            }            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();                final int totalLength = mTotalLength;                mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));            }        }        mTotalLength += mPaddingTop + mPaddingBottom;        int heightSize = mTotalLength;        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());        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) {                    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)) {                        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));                    }                    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));            }            mTotalLength += mPaddingTop + mPaddingBottom;        } else {            alternativeMaxWidth = Math.max(alternativeMaxWidth,                                           weightedMaxWidth);            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;        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),                heightSizeAndState);        if (matchWidth) {            forceUniformWidth(count, heightMeasureSpec);        }    }

代码有点多,但没关系,我们来分析一下其中的关键点,这段代码的主要操作其实就只有下面两点

  • 遍历每个子View,并对每个子View调用measureChildBeforeLayout(),代码115-133行

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

  • 设置 LinearLayout 的大小,代码241行

更多相关文章

  1. Android之——任意时刻从子线程切换到主线程的实现(插曲)
  2. Android(安卓)----SQLiteDate(Cursor接口)
  3. React Native带你从源码解决启动白屏(Android)
  4. Android事件分发机制学习笔记
  5. [置顶] Android6.0权限系统
  6. android onPause()和onStop()区别
  7. Android(安卓)WebView内容宽度自适应
  8. 使用HTML来生产Android界面
  9. Android(安卓)Drawable之Bitmap

随机推荐

  1. Android加载Gif和ImageView的通用解决方
  2. Android电池管理系统系统分析
  3. Android开发规范(编码+性能+UI)
  4. Android触摸事件机制
  5. 将一个Android项目作为另一个Android(安
  6. 享受Android应用程序的Java技术盛宴
  7. android:layout_gravity 和 android:grav
  8. TS3.0 引入 opengl es 1.x, opengl es 2.
  9. Android的消息机制,用Android线程…
  10. 【转】有关Android线程的学习