重写onmeasure支持wrap content写死的吗

Android自定义View学习教程02之onMeasure()的源码分析及重写
获取大小(size) int specSize = MeasureSpec.getSize(measureSpec);
还可以通过上面的两个值生成新的MeasureSpecint 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
* @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 = childD
resultMode = View.MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize =
resultMode = View.MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize =
resultMode = View.MeasureSpec.AT_MOST;
case View.MeasureSpec.AT_MOST:
if (childDimension &= 0) {
resultSize = childD
resultMode = View.MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize =
resultMode = View.MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize =
resultMode = View.MeasureSpec.AT_MOST;
case View.MeasureSpec.UNSPECIFIED:
if (childDimension &= 0) {
resultSize = childD
resultMode = View.MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 :
resultMode = View.MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 :
resultMode = View.MeasureSpec.UNSPECIFIED;
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语句,分别对应三种情况。 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 =
resultMode = MeasureSpec.EXACTLY;
else if (childDimension == LayoutParams.WRAP_CONTENT)
这一句表示子View的宽或高是LayoutParams.WRAP_CONTENT。
此时子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size,子View的mode为MeasureSpec.AT_MOST,即: resultSize =
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 =
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specS
该方法用于获取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 = measuredW
mMeasuredHeight = measuredH
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的
其实,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);
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 =
float totalWeight = 0;
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth =
boolean skippedMeasure =
final int baselineChildIndex = mBaselineAlignedChildI
final boolean useLargestChild = mUseLargestC
int largestChildHeight = Integer.MIN_VALUE;
for (int i = 0; i & ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerH
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight & 0) {
final int totalLength = mTotalL
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure =
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 = oldH
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalL
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 = mTotalL
if (i & baselineChildIndex && lp.weight & 0) {
throw new RuntimeException("Exception");
boolean matchWidthLocally =
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
matchWidth =
matchWidthLocally =
final int margin = lp.leftMargin + lp.rightM
final int measuredWidth = child.getMeasuredWidth() +
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);
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
i += getChildrenSkipCount(child, i);
if (mTotalLength & 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerH
if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i & ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
final int totalLength = mTotalL
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
mTotalLength += mPaddingTop + mPaddingB
int heightSize = mTotalL
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
int delta = heightSize - mTotalL
if (skippedMeasure || delta != 0 && totalWeight & 0.0f) {
float weightSum = mWeightSum & 0.0f ? mWeightSum : totalW
mTotalLength = 0;
for (int i = 0; i & ++i) {
final View child = getVirtualChildAt(i);
if (child.getVisibility() == View.GONE) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.
if (childExtra & 0) {
int share = (int) (childExtra * delta / weightSum);
weightSum -= childE
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight +
lp.leftMargin + lp.rightMargin, lp.width);
if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
int childHeight = child.getMeasuredHeight() +
if (childHeight & 0) {
childHeight = 0;
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
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.rightM
final int measuredWidth = child.getMeasuredWidth() +
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 = mTotalL
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
mTotalLength += mPaddingTop + mPaddingB
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
for (int i = 0; i & i++) {
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.
if (childExtra & 0) {
child.measure(
MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(largestChildHeight,
MeasureSpec.EXACTLY));
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxW
maxWidth += mPaddingLeft + mPaddingR
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收藏分享举报{&debug&:false,&apiRoot&:&&,&paySDK&:&https:\u002F\\u002Fapi\u002Fjs&,&wechatConfigAPI&:&\u002Fapi\u002Fwechat\u002Fjssdkconfig&,&name&:&production&,&instance&:&column&,&tokens&:{&X-XSRF-TOKEN&:null,&X-UDID&:null,&Authorization&:&oauth c3cef7c66aa9e6a1e3160e20&}}{&database&:{&Post&:{&&:{&isPending&:false,&contributes&:[],&title&:&Android自定义View学习教程02之onMeasure()的源码分析及重写&,&author&:&jim-charles-56&,&content&:&我的个人网站 \u003Cp\u003E\u003Ca href=\&xuejianxin.github.io)\& data-editable=\&true\& data-title=\&Xuejianxin's Blog\&\u003EXuejianxin's Blog\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003EGoogle Blog \u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EXuejianxin's Blog\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003E---------------------------------------------------------------------------------------------------------------------------\u003C\u002Fp\u003E\u003Cp\u003E\nAndroid自定义View学习 \u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\u002Fblog.csdn.net\u002Fjim__charles\u002Farticle\u002Fdetails\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EAndroid自定义View学习教程01之常用工具源码分析 - jim__charles的博客 - 博客频道 - CSDN.NET\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\u002Fblog.csdn.net\u002Fjim__charles\u002Farticle\u002Fdetails\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EAndroid自定义View学习教程02之onMeasure()的源码分析及重写 - jim__charles的博客 - 博客频道 - CSDN.NET\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003E---------------------------------------------------------------------------------------------------------------------------\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\n如果觉得我的文章还行的话,也可以关注我的公众号,里面也会第一时间更新,并且会有更多的关于技术的最新资讯和一些个人感想。 \u003C\u002Fp\u003E\u003Cp\u003E\n扫码关注 \u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\\u002Fr\u002FRjtHX1TExAm7rRA8925U\& class=\& external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E\u003Cspan class=\&invisible\&\u003Ehttp:\u002F\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&visible\&\\u002Fr\u002FRjtHX1T\u003C\u002Fspan\u003E\u003Cspan class=\&invisible\&\u003EExAm7rRA8925U\u003C\u002Fspan\u003E\u003Cspan class=\&ellipsis\&\u003E\u003C\u002Fspan\u003E\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E (二维码自动识别)\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E\n我的微信\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\\u002Fr\u002FhVFEXDXEbWgSrXE-9wSX\& class=\& external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E\u003Cspan class=\&invisible\&\u003Ehttp:\u002F\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&visible\&\\u002Fr\u002FhVFEXDX\u003C\u002Fspan\u003E\u003Cspan class=\&invisible\&\u003EEbWgSrXE-9wSX\u003C\u002Fspan\u003E\u003Cspan class=\&ellipsis\&\u003E\u003C\u002Fspan\u003E\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E (二维码自动识别)\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E---------------------------------------------------------------------------------------------------------------------------\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\n从Android自定义View学习系列计划开始,到现在也有挺长时间了,上一篇\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\u002Fblog.csdn.net\u002Fjim__charles\u002Farticle\u002Fdetails\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EAndroid自定义View学习教程01之常用工具源码分析 - jim__charles的博客 - 博客频道 - CSDN.NET\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E讲到了Android开发过程中常使用到的一些工具,都从源码上进行了分析,让大家对要使用的工具都有了一定的了解。\n\n我们都知道,自定义View的三个重要过程分别是measure、layout、draw,而measure处于这条处理链的首端,自然是非常重要的。所以接下来的这一篇\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\u002Fblog.csdn.net\u002Fjim__charles\u002Farticle\u002Fdetails\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EAndroid自定义View学习教程02之onMeasure()的源码分析及重写 - jim__charles的博客 - 博客频道 - CSDN.NET\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E我们就来看一看自定义View里经常用到的onMeasure()方法的源码分析以及它们的重写!!\n\n---\n\n系统显示一个View,首先需要通过测量(measure)该View来获得其长和宽从而确定显示该View时需要多大的空间。而在测量的过程中MeasureSpec贯穿全程,发挥着不可或缺的作用。 所以,了解View的测量过程,最合适的切入点就是MeasureSpec。 \u003C\u002Fp\u003E\u003Cp\u003E---------------------------------------------------------------------------------------------------------------------------\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003EMeasureSpec \u003C\u002Fh2\u003E\u003Cp\u003E\n我们来看一下Google官方文档对\u003Ca href=\&http:\u002F\\u002F?target=https%3A\u002F\\u002Freference\u002Fandroid\u002Fview\u002FView.MeasureSpec.html\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EMeasureSpec\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E的描述 \u003C\u002Fp\u003E\u003Cblockquote\u003EA 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. \u003C\u002Fblockquote\u003E\u003Cp\u003E\n\n大概意思就是,MeasureSpec封装了从父节点传递给子节点的布局要求。每个MeasureSpec表示宽度或高度的要求。度量规格包括size(大小)和mode(模式)。 \u003C\u002Fp\u003E\u003Cp\u003E\n这句话里透漏了几个重点: \u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EMeasureSpec封装了从父节点传递给子节点的布局要求 \u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003EMeasureSpec可以表示宽度和高度 \u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003EMeasureSpec由size和mode组成\n\nMeasureSpec通常翻译为”测量规格”,它是一个32位的int数据。\u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cp\u003E其中高2位代表SpecMode即某种测量模式(mode),低30位为SpecSize代表在该模式下的规格大小(size)。 \u003C\u002Fp\u003E\u003Cp\u003E可以通过如下方式分别获取这两个值: \u003C\u002Fp\u003E\u003Cp\u003E\n获取模式(mode)\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Eint specMode = MeasureSpec.getMode(measureSpec);\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n获取大小(size) \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Eint specSize = MeasureSpec.getSize(measureSpec);\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n还可以通过上面的两个值生成新的MeasureSpec\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Eint measureSpec=MeasureSpec.makeMeasureSpec(size, mode);\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n那么所谓从父节点传递给子节点的布局要求是什么呢?MeasureSpec里有着三种模式(mode),我们来看一下:\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003EUNSPECIFIED \u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cblockquote\u003EThe parent has not imposed any constraint on the child. It can be whatever size it wants. \u003C\u002Fblockquote\u003E\u003Cp\u003E\n父View没有对子View施加任何约束。它可以是任何它想要的大小。 \u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003EEXACTLY \u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cblockquote\u003EThe 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.\u003C\u002Fblockquote\u003E\u003Cp\u003E\n\n父View已确定子View的确切大小。子View将被给予父View所确定的大小,无论它想要多大。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003EAT_MOST \u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cblockquote\u003EThe child can be as large as it wants up to the specified size. \u003C\u002Fblockquote\u003E\u003Cp\u003E\n父View不限制View的大小,子View可以得到它想要达到的大小。 \u003C\u002Fp\u003E\u003Cp\u003E\n好了,接下来让我们从源码角度来分析一下它们是怎么形成的: \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u002F**\n
* @param child\n
* 子View\n
* @param parentWidthMeasureSpec\n
* 父容器(比如LinearLayout)的宽的MeasureSpec\n
* @param widthUsed\n
* 父容器(比如LinearLayout)在水平方向已经占用的空间大小\n
* @param parentHeightMeasureSpec\n
* 父容器(比如LinearLayout)的高的MeasureSpec\n
* @param heightUsed\n
* 父容器(比如LinearLayout)在垂直方向已经占用的空间大小\n
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,\n
int parentHeightMeasureSpec, int heightUsed) {\n
final MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();\n
final int childWidthMeasureSpec =\n
getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight +\n
lp.leftMargin + lp.rightMargin + widthUsed, lp.width);\n
final int childHeightMeasureSpec =\n
getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom +\n
lp.topMargin + lp.bottomMargin + heightUsed, lp.height);\n
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);\n
}\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n需要注意里面的参数,通过这些参数看出,该方法要测量子View传进来的参数却包含了父容器的宽的MeasureSpec,父容器在水平方向已经占用的空间大小,父容器的高的MeasureSpec,父容器在垂直方向已经占用的空间大小等父View相关的信息。这在一定程度体现了:父View影响着子View的MeasureSpec的生成。 \u003C\u002Fp\u003E\u003Cp\u003E\n该方法主要有四步操作: \u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E得到子View的LayoutParams \u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E得到子View的宽的MeasureSpec \u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E得到子View的高的MeasureSpec \u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cul\u003E\u003Cli\u003E测量子View \u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cp\u003E\n我们可以看到第二步和第三步都调用到了getChildMeasureSpec()方法,那么在该方法内部又做了哪些操作呢?我们来看一下getChildMeasureSpec( )的源码 \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E public static int getChildMeasureSpec(int spec, int padding, int childDimension) {\n
int specMode = View.MeasureSpec.getMode(spec);\n
int specSize = View.MeasureSpec.getSize(spec);\n\n
int size = Math.max(0, specSize - padding);\n\n
int resultSize = 0;\n
int resultMode = 0;\n\n
switch (specMode) {\n
case View.MeasureSpec.EXACTLY:\n
if (childDimension &= 0) {\n
resultSize = childD\n
resultMode = View.MeasureSpec.EXACTLY;\n
} else if (childDimension == LayoutParams.MATCH_PARENT) {\n
resultSize =\n
resultMode = View.MeasureSpec.EXACTLY;\n
} else if (childDimension == LayoutParams.WRAP_CONTENT) {\n
resultSize =\n
resultMode = View.MeasureSpec.AT_MOST;\n
case View.MeasureSpec.AT_MOST:\n
if (childDimension &= 0) {\n
resultSize = childD\n
resultMode = View.MeasureSpec.EXACTLY;\n
} else if (childDimension == LayoutParams.MATCH_PARENT) {\n
resultSize =\n
resultMode = View.MeasureSpec.AT_MOST;\n
} else if (childDimension == LayoutParams.WRAP_CONTENT) {\n
resultSize =\n
resultMode = View.MeasureSpec.AT_MOST;\n
case View.MeasureSpec.UNSPECIFIED:\n
if (childDimension &= 0) {\n
resultSize = childD\n
resultMode = View.MeasureSpec.EXACTLY;\n
} else if (childDimension == LayoutParams.MATCH_PARENT) {\n
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 :\n
resultMode = View.MeasureSpec.UNSPECIFIED;\n
} else if (childDimension == LayoutParams.WRAP_CONTENT) {\n
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 :\n
resultMode = View.MeasureSpec.UNSPECIFIED;\n
return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);\n
}\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n应该可以看出来这就是确定子View的MeasureSpec的具体实现了吧!! \u003C\u002Fp\u003E\u003Cp\u003E\n接下来我们对源码进行分析 \u003C\u002Fp\u003E\u003Cp\u003E\n首先来看一下getChildMeasureSpec()方法的内部参数 \u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003Espec \u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cp\u003E父View(比如LinearLayout)的宽或高的MeasureSpec\n\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003Epadding \u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cblockquote\u003E父容器(比如LinearLayout)在垂直方向或者水平方向已经被占用的空间。 \u003C\u002Fblockquote\u003E\u003Cp\u003E\n\n为什么这么说呢? \u003C\u002Fp\u003E\u003Cp\u003E\n我们可以看measureChildWithMargins()方法里调用getChildMeasureSpec()的这一部分,传递给getChildMeasureSpec()的第二个参数构成如下: 例如 \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003EmPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n\n其中\n\nmPaddingLeft和mPaddingRight表示父容器左右两内侧的padding\n\nlp.leftMargin和\u003C\u002Fp\u003E\u003Cp\u003Elp.rightMargin表示子View左右两外侧的margin \u003C\u002Fp\u003E\u003Cp\u003E\n\n这四部分都不可以再被利用起来布局子View。所以说这些值的和表示:父容器在水平方向已经被占用的空间 \u003C\u002Fp\u003E\u003Cp\u003E\n\n同理\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003EmPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n表示父容器(比如LinearLayout)在垂直方向已被占用的空间。 \u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EchildDimension \u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cblockquote\u003E通过子View的LayoutParams获取到的子View的宽或高 \u003C\u002Fblockquote\u003E\u003Cp\u003E\n从getChildMeasureSpec()方法的第一个参数spec和第二个参数padding也可以看出,父容器(如LinearLayout)的MeasureSpec和子View的LayoutParams共同决定着子View的MeasureSpec!! \u003C\u002Fp\u003E\u003Cp\u003E\n好,了解了方法的参数后我们来分析一下getChildMeasureSpec()方法的具体实现步骤 \u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E得到父容器的specMode和specSize \u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E得到父容器在水平方向或垂直方向可用的最大空间值 \u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E确定子View的specMode和specSize \u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cp\u003E\n在这里有一个switch语句,该语句的判断条件就是父View的specMode;在此根据父View的specMode的不同来决定子View的specMode和specSize。 \u003C\u002Fp\u003E\u003Cp\u003E\n接下来我们来分析一下这个switch()语句\n\ncase 1 中父容器的specMode为EXACTLY,内部接 if
else-if语句,分别对应三种情况。 \u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003Eif (childDimension &= 0) \u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003Eelse if (childDimension == LayoutParams.MATCH_PARENT) \u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003Eelse if (childDimension == LayoutParams.WRAP_CONTENT) \u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cp\u003E\n有些朋友一看到 childDimension &= 0 就懵逼了,这是什么鬼?在这里我们有必要说明一下两个系统常量: \u003C\u002Fp\u003E\u003Cp\u003E\nLayoutParams.MATCH_PARENT=-1 \u003C\u002Fp\u003E\u003Cp\u003E\nLayoutParams.WRAP_CONTENT=-2 \u003C\u002Fp\u003E\u003Cp\u003E\n\n所以 childDimension &= 0 的意思是除了MATCH_PARENT和WRAP_CONTENT这两种情况外的所有情况,其实也就是说当传入父容器的模式既不是MATCH_PARENT也不是WRAP_CONTENT,而是一个具体数值的情况时。 \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Eif (childDimension &= 0)\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n所以上面这一句代码的意思表示子View的宽或高不是match_parent,也不是wrap_content而是一个具体的数值,比如100px。 \u003C\u002Fp\u003E\u003Cp\u003E\n\n此时子View的size就是childDimension,子View的mode也为MeasureSpec.EXACTLY,即:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-java\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EresultSize\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EchildDimension\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E;\u003C\u002Fspan\u003E \n\u003Cspan class=\&n\&\u003EresultMode\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EMeasureSpec\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&na\&\u003EEXACTLY\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E;\u003C\u002Fspan\u003E \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-java\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E \u003Cspan class=\&k\&\u003Eelse\u003C\u002Fspan\u003E \u003Cspan class=\&k\&\u003Eif\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EchildDimension\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E==\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ELayoutParams\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&na\&\u003EMATCH_PARENT\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E)\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E这一句代码表示子View的宽或高是LayoutParams.MATCH_PARENT。 \u003C\u002Fp\u003E\u003Cp\u003E\n\n此时子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size,子View的mode也为MeasureSpec.EXACTLY,即: \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003EresultSize = \nresultMode = MeasureSpec.EXACTLY;\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Eelse if (childDimension == LayoutParams.WRAP_CONTENT)\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n\n这一句表示子View的宽或高是LayoutParams.WRAP_CONTENT。 \u003C\u002Fp\u003E\u003Cp\u003E\n\n此时子View的size就是父容器在水平方向或垂直方向可用的最大空间值即size,子View的mode为MeasureSpec.AT_MOST,即: \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003EresultSize = \nresultMode = MeasureSpec.AT_MOST; \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n另外两种模式传入时父容器的specMode不同,同样对应三种情况以及不同的size和mode,就不再讲解了,大家对着源码看就知道了。 \u003C\u002Fp\u003E\u003Cp\u003E\n到这一步,我们已经可以清楚的知道\n\n子View的MeasureSpec由其父容器的MeasureSpec和该子View本身的布局参数LayoutParams共同决定。 \u003C\u002Fp\u003E\u003Cp\u003E\n\n经过测量得出的子View的MeasureSpec是系统给出的一个期望值(参考值),我们也可摒弃系统的这个测量流程,直接调用setMeasuredDimension( )设置子View的宽和高的测量值。 \u003C\u002Fp\u003E\u003Cp\u003E\n---------------------------------------------------------------------------------------------------------------------------\u003C\u002Fp\u003E\u003Ch2\u003EonMeasure()方法源码分析 \u003C\u002Fh2\u003E\u003Cp\u003E\n到这里终于把MeasureSpec搞懂了,那么接下来就可以开始看我们的主角了!! \u003C\u002Fp\u003E\u003Cp\u003E\n我们来看一下View的onMeasure()方法的源码\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Eprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
\n } \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\nonMeasure( )源码流程如下: \u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E(1) 在onMeasure调用setMeasuredDimension( )设置View的宽和高 \u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E(2)在setMeasuredDimension()中调用getDefaultSize()获取View的宽和高 \u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cul\u003E\u003Cli\u003E(3)在getDefaultSize()方法中会调用到getSuggestedMinimumWidth()或者getSuggestedMinimumHeight()获取到View宽和高的最小值 \u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cp\u003E\n这些方法的调用顺序如下图所示\n\n!\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\u002Fimg.blog.csdn.net\u002F11165%3Fwatermark\u002F2\u002Ftext\u002FaHR0cDovL2Jsb2cuY3Nkbi5uZXQvamltX19jaGFybGVz\u002Ffont\u002F5a6L5L2T\u002Ffontsize\u002F400\u002Ffill\u002FI0JBQkFCMA%3D%3D\u002Fdissolve\u002F70\u002Fgravity\u002FSouthEast\& class=\& external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E\u003Cspan class=\&invisible\&\u003Ehttp:\u002F\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&visible\&\u003Eimg.blog.csdn.net\u002F2C\u002Fspan\u003E\u003Cspan class=\&invisible\&\u003E?watermark\u002F2\u002Ftext\u002FaHR0cDovL2Jsb2cuY3Nkbi5uZXQvamltX19jaGFybGVz\u002Ffont\u002F5a6L5L2T\u002Ffontsize\u002F400\u002Ffill\u002FI0JBQkFCMA==\u002Fdissolve\u002F70\u002Fgravity\u002FSouthEast\u003C\u002Fspan\u003E\u003Cspan class=\&ellipsis\&\u003E\u003C\u002Fspan\u003E\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E)\u003C\u002Fp\u003E\u003Cp\u003E\n为了理解onMeasure到底做了什么,以及这几个方法分别做了什么,它们之间又有什么联系,我们从后面按顺序分别对这几个方法的源码进行分析,这样就可以更好的理解上一层方法是如何调用小一层方法的了。 \u003C\u002Fp\u003E\u003Cp\u003E首先是getSuggestedMinimumWidth()和getSuggestedMinimumHeight(),它们的源码如下 \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u002F\u002FReturns the suggested minimum width that the view should use \nprotected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
\n} \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u002F\u002FReturns the suggested minimum height that the view should use \nprotected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
\n} \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n这两个方法分别返回View的宽度和高度的最小值MinimumWidth、MinimumHeight \u003C\u002Fp\u003E\u003Cp\u003E\n在此需要注意该View是否有背景 \u003C\u002Fp\u003E\u003Cp\u003E\n(1) 若该View没有背景 \u003C\u002Fp\u003E\u003Cp\u003E那么该MinimumWidth为View本身的最小宽度即mMinWidth \u003C\u002Fp\u003E\u003Cp\u003E \n有两种方法可以设置该mMinWidth值: \n\n - 在XML布局文件中定义minWidth \n \n - 调用View的setMinimumWidth()方法为该值赋值\n\n(\u003C\u002Fp\u003E\u003Cp\u003E2) 若该View有背景 \u003C\u002Fp\u003E\u003Cp\u003E那么该MinimumWidth为View本身最小宽度mMinWidth和View背景的最小宽度的最大值\u003C\u002Fp\u003E\u003Cp\u003E\n\ngetSuggestedMinimumHeight()方法与getSuggestedMinimumWidth()类似,不再多说 \u003C\u002Fp\u003E\u003Cp\u003E\n---------------------------------------------------------------------------------------------------------------------------\u003C\u002Fp\u003E\u003Cp\u003E\n然后是getDefaultSize(),我们来看一下它的源码 \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Epublic static int getDefaultSize(int size, int measureSpec) {
int result =
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specS
\n} \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n该方法用于获取View的宽或者高的大小 \u003C\u002Fp\u003E\u003Cp\u003E\n该方法的第一个输入参数size就是通过调用getSuggestedMinimumWidth()方法获得的View的宽或高的最小值 \u003C\u002Fp\u003E\u003Cp\u003E\n从getDefaultSize()的源码里的switch语句可看出该方法的返回值有两种情况: \u003C\u002Fp\u003E\u003Cp\u003E\n\n(1) measureSpec的specMode为MeasureSpec.UNSPECIFIED 时 \n在此情况下该方法的返回值就是View的宽或者高最小值\n这种情况很少见,可忽略不计 \u003C\u002Fp\u003E\u003Cp\u003E\n\n(2) measureSpec的specMode为MeasureSpec.AT_MOST或MeasureSpec.EXACTLY 时: \n在此情况下getDefaultSize()的返回值就是该子View的measureSpec中的specSize \u003C\u002Fp\u003E\u003Cp\u003E\n除去第一种情况以外,可知,在measure阶段View的宽和高由其measureSpec中的specSize决定!! \u003C\u002Fp\u003E\u003Cp\u003E\n如果子View在XML布局文件中对于大小的设置采用wrap_content,那么不管父View的specMode是 MeasureSpec.AT_MOST还是MeasureSpec.EXACTLY 对于子View而言系统给它设置的specMode都是MeasureSpec.AT_MOST,并且其大小都是parentLeftSize即父View目前剩余的可用空间。这时wrap_content就失去了原本的意义,变成了match_parent一样了。\u003C\u002Fp\u003E\u003Cp\u003E\n\n所以自定义View在重写onMeasure()的过程中应该手动处理View的宽或高为wrap_content的情况 \u003C\u002Fp\u003E\u003Cp\u003E\n---------------------------------------------------------------------------------------------------------------------------\u003C\u002Fp\u003E\u003Cp\u003E最后是setMeasuredDimension(),同样的,我们来看一下它的源码 \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Eprotected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredW
mMeasuredHeight = measuredH
mPrivateFlags |= MEASURED_DIMENSION_SET;
\n}\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n经过调用前面的各种方法,终于得到了View的宽度和高度 \u003C\u002Fp\u003E\u003Cp\u003E\n在这里就可以通过setMeasuredDimension()方法来设置View的宽度和高度的测量值 \u003C\u002Fp\u003E\u003Cp\u003E\n--- \u003C\u002Fp\u003E\u003Ch2\u003E重写onMeasure()方法 \u003C\u002Fh2\u003E\u003Cp\u003E\n上面说到,当子View在XML布局文件设置的宽或高为wrap_content时,系统给子View设置的specMode都是MeasureSpec.AT_MOST,这时wrap_content就失去了原本的意义,变成了match_parent一样。所以此时我们需要重写onMeasure来处理这种情况。 \u003C\u002Fp\u003E\u003Cp\u003E\n那么我们该怎么对这两种情况进行处理呢? \u003C\u002Fp\u003E\u003Cp\u003E\n第一种情况: \n如果在xml布局中View的宽和高均用wrap_content,那么需要设置View的宽和高为mWidth和mHeight \u003C\u002Fp\u003E\u003Cp\u003E\n第二种情况: \n如果在xml布局中View的只是宽或高其中一个为wrap_content,那么就将该值设置为默认的宽或高,另外的一个值采用系统测量的specSize即可 \u003C\u002Fp\u003E\u003Cp\u003E\n具体的实现可以这样做: \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Eprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n\n
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);
\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n上面的代码主要分两步完成 \u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E调用super.onMeasure() \u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E处理View的大小为wrap_content时的情况 \u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cp\u003E\n我们看下面这张图,会更清晰一点,要进行处理的两种情况是最后一行左边两个mode为AT_MOST的 \u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\u002Fimg.blog.csdn.net\u002F49408%3Fwatermark\u002F2\u002Ftext\u002FaHR0cDovL2Jsb2cuY3Nkbi5uZXQvamltX19jaGFybGVz\u002Ffont\u002F5a6L5L2T\u002Ffontsize\u002F400\u002Ffill\u002FI0JBQkFCMA%3D%3D\u002Fdissolve\u002F70\u002Fgravity\u002FSouthEast\& class=\& external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E\u003Cspan class=\&invisible\&\u003Ehttp:\u002F\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&visible\&\u003Eimg.blog.csdn.net\u002F2C\u002Fspan\u003E\u003Cspan class=\&invisible\&\u003E?watermark\u002F2\u002Ftext\u002FaHR0cDovL2Jsb2cuY3Nkbi5uZXQvamltX19jaGFybGVz\u002Ffont\u002F5a6L5L2T\u002Ffontsize\u002F400\u002Ffill\u002FI0JBQkFCMA==\u002Fdissolve\u002F70\u002Fgravity\u002FSouthEast\u003C\u002Fspan\u003E\u003Cspan class=\&ellipsis\&\u003E\u003C\u002Fspan\u003E\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003E\n其实,Andorid系统的控件比如TextView等也在onMeasure()中对其大小为wrap_content这一情况作了特殊的处理。 \u003C\u002Fp\u003E\u003Cp\u003E\n请注意在第二步的代码中用的判断条件: \n\nwidthSpecMode==MeasureSpec.AT_MOST或者heightSpecMode==MeasureSpec.AT_MOST或者兼而有之; \u003C\u002Fp\u003E\u003Cp\u003E\n这里的关键是模式(mode)MeasureSpec.AT_MOST \u003C\u002Fp\u003E\u003Cp\u003E\n--- \u003C\u002Fp\u003E\u003Ch2\u003EViewGroup.measure \u003C\u002Fh2\u003E\u003Cp\u003E\n看完了View中的onMeasure方法,接下来我们继续看ViewGroup中的measure()方法 \u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Epublic abstract class ViewGroup extends View implements ViewParent,ViewManager{ \n}\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E我们可以看到ViewGroup是一个抽象类,它没有重写View的onMeasure( )但它提供了measureChildren( )测量所有的子View。在measureChildren()方法中调用measureChild( )测量每个子View,在该方法内又会调用到child.measure( )方法,这又回到了前面熟悉的流程。 \u003C\u002Fp\u003E\u003Cp\u003E\n\n即ViewGroup中测量子View的流程: \u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\u002Fimg.blog.csdn.net\u002F44179%3Fwatermark\u002F2\u002Ftext\u002FaHR0cDovL2Jsb2cuY3Nkbi5uZXQvamltX19jaGFybGVz\u002Ffont\u002F5a6L5L2T\u002Ffontsize\u002F400\u002Ffill\u002FI0JBQkFCMA%3D%3D\u002Fdissolve\u002F70\u002Fgravity\u002FSouthEast\& class=\& external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E\u003Cspan class=\&invisible\&\u003Ehttp:\u002F\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&visible\&\u003Eimg.blog.csdn.net\u002F2C\u002Fspan\u003E\u003Cspan class=\&invisible\&\u003E?watermark\u002F2\u002Ftext\u002FaHR0cDovL2Jsb2cuY3Nkbi5uZXQvamltX19jaGFybGVz\u002Ffont\u002F5a6L5L2T\u002Ffontsize\u002F400\u002Ffill\u002FI0JBQkFCMA==\u002Fdissolve\u002F70\u002Fgravity\u002FSouthEast\u003C\u002Fspan\u003E\u003Cspan class=\&ellipsis\&\u003E\u003C\u002Fspan\u003E\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003E\n既然ViewGroup继承自View而且它是一个抽象类,那么ViewGroup的子类就应该根据自身的要求和特点重写onMeasure( )方法。 \u003C\u002Fp\u003E\u003Cp\u003E\n\n那么这些ViewGroup的子类会怎么测量子View和确定自身的大小呢? \u003C\u002Fp\u003E\u003Cp\u003E\n如果ViewGroup知道了每个子View的大小,将它们累加起来是不是就知道了自身的大小呢?\u003C\u002Fp\u003E\u003Cp\u003E\n\n顺着这个思路,我们选择ViewGroup的子类LinearLayout来看,就从它的onMeasure( )开始看吧!!\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E @Override\n
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n
if (mOrientation == VERTICAL) {\n
measureVertical(widthMeasureSpec, heightMeasureSpec);\n
} else {\n
measureHorizontal(widthMeasureSpec, heightMeasureSpec);\n
}\n```\n\n我们可以看到,代码里分了水平线性和垂直线性这两种情况进行测量,类似的逻辑在其onLayout( )阶段也会看到。我们就选measureVertical( )继续往下看\n\n```\n
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {\n
mTotalLength = 0;\n
int maxWidth = 0;\n
int childState = 0;\n
int alternativeMaxWidth = 0;\n
int weightedMaxWidth = 0;\n
boolean allFillParent =\n
float totalWeight = 0;\n\n
final int count = getVirtualChildCount();\n\n
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);\n
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);\n\n
boolean matchWidth =\n
boolean skippedMeasure =\n\n
final int baselineChildIndex = mBaselineAlignedChildI
final boolean useLargestChild = mUseLargestC\n\n
int largestChildHeight = Integer.MIN_VALUE;\n\n
for (int i = 0; i & ++i) {\n
final View child = getVirtualChildAt(i);\n\n
if (child == null) {\n
mTotalLength += measureNullChild(i);\n
if (child.getVisibility() == View.GONE) {\n
i += getChildrenSkipCount(child, i);\n
if (hasDividerBeforeChildAt(i)) {\n
mTotalLength += mDividerH\n
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();\n\n
totalWeight += lp.\n\n
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight & 0) {\n
final int totalLength = mTotalL\n
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);\n
skippedMeasure =\n
} else {\n
int oldHeight = Integer.MIN_VALUE;\n\n
if (lp.height == 0 && lp.weight & 0) {\n\n
oldHeight = 0;\n
lp.height = LayoutParams.WRAP_CONTENT;\n
measureChildBeforeLayout(\n
child, i, widthMeasureSpec, 0, heightMeasureSpec,\n
totalWeight == 0 ? mTotalLength : 0);\n\n
if (oldHeight != Integer.MIN_VALUE) {\n
lp.height = oldH\n
final int childHeight = child.getMeasuredHeight();\n
final int totalLength = mTotalL\n
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +\n
lp.bottomMargin + getNextLocationOffset(child));\n\n
if (useLargestChild) {\n
largestChildHeight = Math.max(childHeight, largestChildHeight);\n
if ((baselineChildIndex &= 0) && (baselineChildIndex == i + 1)) {\n
mBaselineChildTop = mTotalL\n
if (i & baselineChildIndex && lp.weight & 0) {\n
throw new RuntimeException(\&Exception\&);\n
boolean matchWidthLocally =\n
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {\n
matchWidth =\n
matchWidthLocally =\n
final int margin = lp.leftMargin + lp.rightM\n
final int measuredWidth = child.getMeasuredWidth() +\n
maxWidth = Math.max(maxWidth, measuredWidth);\n
childState = combineMeasuredStates(childState, child.getMeasuredState());\n\n
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;\n
if (lp.weight & 0) {\n
weightedMaxWidth = Math.max(weightedMaxWidth,\n
matchWidthLocally ? margin : measuredWidth);\n
} else {\n
alternativeMaxWidth = Math.max(alternativeMaxWidth,\n
matchWidthLocally ? margin : measuredWidth);\n
i += getChildrenSkipCount(child, i);\n
if (mTotalLength & 0 && hasDividerBeforeChildAt(count)) {\n
mTotalLength += mDividerH\n
if (useLargestChild &&\n
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {\n
mTotalLength = 0;\n\n
for (int i = 0; i & ++i) {\n
final View child = getVirtualChildAt(i);\n\n
if (child == null) {\n
mTotalLength += measureNullChild(i);\n
if (child.getVisibility() == GONE) {\n
i += getChildrenSkipCount(child, i);\n
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)\n
child.getLayoutParams();\n
final int totalLength = mTotalL\n
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +\n
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));\n
mTotalLength += mPaddingTop + mPaddingB\n\n
int heightSize = mTotalL\n\n
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());\n\n
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);\n
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;\n\n
int delta = heightSize - mTotalL\n
if (skippedMeasure || delta != 0 && totalWeight & 0.0f) {\n
float weightSum = mWeightSum & 0.0f ? mWeightSum : totalW\n\n
mTotalLength = 0;\n\n
for (int i = 0; i & ++i) {\n
final View child = getVirtualChildAt(i);\n\n
if (child.getVisibility() == View.GONE) {\n
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();\n\n
float childExtra = lp.\n
if (childExtra & 0) {\n
int share = (int) (childExtra * delta \u002F weightSum);\n
weightSum -= childE\n
delta -=\n\n
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,\n
mPaddingLeft + mPaddingRight +\n
lp.leftMargin + lp.rightMargin, lp.width);\n\n\n
if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {\n
int childHeight = child.getMeasuredHeight() +\n
if (childHeight & 0) {\n
childHeight = 0;\n
child.measure(childWidthMeasureSpec,\n
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));\n
} else {\n
child.measure(childWidthMeasureSpec,\n
MeasureSpec.makeMeasureSpec(share & 0 ? share : 0,\n
MeasureSpec.EXACTLY));\n
childState = combineMeasuredStates(childState, child.getMeasuredState()\n
& (MEASURED_STATE_MASK&&MEASURED_HEIGHT_STATE_SHIFT));\n
final int margin =
lp.leftMargin + lp.rightM\n
final int measuredWidth = child.getMeasuredWidth() +\n
maxWidth = Math.max(maxWidth, measuredWidth);\n\n
boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&\n
lp.width == LayoutParams.MATCH_PARENT;\n\n
alternativeMaxWidth = Math.max(alternativeMaxWidth,\n
matchWidthLocally ? margin : measuredWidth);\n\n
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;\n\n
final int totalLength = mTotalL\n
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +\n
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));\n
mTotalLength += mPaddingTop + mPaddingB\n
} else {\n
alternativeMaxWidth = Math.max(alternativeMaxWidth,\n
weightedMaxWidth);\n\n
if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {\n
for (int i = 0; i & i++) {\n
final View child = getVirtualChildAt(i);\n\n
if (child == null || child.getVisibility() == View.GONE) {\n
final LinearLayout.LayoutParams lp =\n
(LinearLayout.LayoutParams) child.getLayoutParams();\n\n
float childExtra = lp.\n
if (childExtra & 0) {\n
child.measure(\n
MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),\n
MeasureSpec.EXACTLY),\n
MeasureSpec.makeMeasureSpec(largestChildHeight,\n
MeasureSpec.EXACTLY));\n
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {\n
maxWidth = alternativeMaxW\n
maxWidth += mPaddingLeft + mPaddingR\n
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());\n\n
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),\n
heightSizeAndState);\n\n
if (matchWidth) {\n
forceUniformWidth(count, heightMeasureSpec);\n
}\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\n代码有点多,但没关系,我们来分析一下其中的关键点,这段代码的主要操作其实就只有下面两点 \u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E遍历每个子View,并对每个子View调用measureChildBeforeLayout(),代码115-133行 \u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cp\u003E\n \n在measureChildBeforeLayout()方法内又会调用measureChildWithMargins()从而测量每个子View的大小。在该过程中mTotalLength保存了LinearLayout的高度,所以每当测量完一个子View该值都会发生变化。 \u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E设置LinearLayout的大小,代码241行\u003C\u002Fli\u003E\u003C\u002Ful\u003E&,&updated&:new Date(&T09:21:15.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:0,&collapsedCount&:0,&likeCount&:1,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&titleImage&:&&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&reviewers&:[],&topics&:[{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&Android&}],&adminClosedComment&:false,&titleImageSize&:{&width&:0,&height&:0},&href&:&\u002Fapi\u002Fposts\u002F&,&excerptTitle&:&&,&tipjarState&:&closed&,&annotationAction&:[],&sourceUrl&:&&,&pageCommentsCount&:0,&hasPublishingDraft&:false,&snapshotUrl&:&&,&publishedTime&:&T17:21:15+08:00&,&url&:&\u002Fp\u002F&,&lastestLikers&:[{&bio&:&IT&,&isFollowing&:false,&hash&:&315e6e775a615d5e65f76&,&uid&:361600,&isOrg&:false,&slug&:&kokahou&,&isFollowed&:false,&description&:&&,&name&:&琥華豊&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Fkokahou&,&avatar&:{&id&:&da8e974dc&,&template&:&https:\u002F\\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false}],&summary&:&我的个人网站 \u003Ca href=\&xuejianxin.github.io)\& data-editable=\&true\& data-title=\&Xuejianxin's Blog\&\u003EXuejianxin's Blog\u003C\u002Fa\u003EGoogle Blog \u003Ca href=\&http:\u002F\\u002F\& data-editable=\&true\& data-title=\&Xuejianxin's Blog\&\u003EXuejianxin's Blog\u003C\u002Fa\u003E---------------------------------------------------------------------------------------------------------------------------\nAndroid自定义View学习 \u003Ca href=\&http:\u002F\u002Fblog.csdn.net\u002Fjim__charles\u002Farticle\u002Fdetails\u002F\& data-editable=\&true\& data-title=\&Android自定义View学习教程01之常用工具源码分析 - jim__charles的博客 - 博客频道 - CSDN.NET\& class=\&\&\u003EAndroid自定义View学习教程01之常用…\u003C\u002Fa\u003E&,&reviewingCommentsCount&:0,&meta&:{&previous&:null,&next&:null},&annotationDetail&:null,&commentsCount&:0,&likesCount&:1,&FULLINFO&:true}},&User&:{&jim-charles-56&:{&isFollowed&:false,&name&:&Jim Charles&,&headline&:&一名Android开发者,现在大二。技能:Andrid开发、Java、mysql、JavaWeb&,&avatarUrl&:&https:\u002F\\u002F50\u002F8dfafac0_s.jpg&,&isFollowing&:false,&type&:&people&,&slug&:&jim-charles-56&,&bio&:&Android开发程序员&,&hash&:&0af515bc99b514ecb6eefa88&,&uid&:752600,&isOrg&:false,&description&:&一名Android开发者,现在大二。技能:Andrid开发、Java、mysql、JavaWeb&,&badge&:{&identity&:null,&bestAnswerer&:null},&profileUrl&:&https:\u002F\\u002Fpeople\u002Fjim-charles-56&,&avatar&:{&id&:&8dfafac0&,&template&:&https:\u002F\\u002F50\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false}},&Comment&:{},&favlists&:{}},&me&:{},&global&:{&experimentFeatures&:{&ge3&:&ge3_9&,&ge2&:&ge2_1&,&nwebStickySidebar&:&sticky&,&androidPassThroughPush&:&all&,&newMore&:&new&,&liveReviewBuyBar&:&live_review_buy_bar_2&,&liveStore&:&ls_a2_b2_c1_f2&,&searchHybridTabs&:&without-tabs&,&isOffice&:&false&,&homeUi2&:&default&,&answerRelatedReadings&:&qa_recommend_with_ads_and_article&,&remixOneKeyPlayButton&:&headerButton&,&asdfadsf&:&asdfad&,&qrcodeLogin&:&qrcode&,&newBuyBar&:&livenewbuy3&,&isShowUnicomFreeEntry&:&unicom_free_entry_off&,&newMobileColumnAppheader&:&new_header&,&zcmLighting&:&zcm&,&favAct&:&default&,&appStoreRateDialog&:&close&,&mobileQaPageProxyHeifetz&:&m_qa_page_nweb&,&iOSNewestVersion&:&4.2.0&,&default&:&None&,&wechatShareModal&:&wechat_share_modal_show&,&qaStickySidebar&:&sticky_sidebar&,&androidProfilePanel&:&panel_b&,&nwebWriteAnswer&:&default&}},&columns&:{&next&:{}},&columnPosts&:{},&columnSettings&:{&colomnAuthor&:[],&uploadAvatarDetails&:&&,&contributeRequests&:[],&contributeRequestsTotalCount&:0,&inviteAuthor&:&&},&postComments&:{},&postReviewComments&:{&comments&:[],&newComments&:[],&hasMore&:true},&favlistsByUser&:{},&favlistRelations&:{},&promotions&:{},&draft&:{&titleImage&:&&,&titleImageSize&:{},&isTitleImageFullScreen&:false,&canTitleImageFullScreen&:false,&title&:&&,&titleImageUploading&:false,&error&:&&,&content&:&&,&draftLoading&:false,&globalLoading&:false,&pendingVideo&:{&resource&:null,&error&:null}},&drafts&:{&draftsList&:[],&next&:{}},&config&:{&userNotBindPhoneTipString&:{}},&recommendPosts&:{&articleRecommendations&:[],&columnRecommendations&:[]},&env&:{&edition&:{},&isAppView&:false,&appViewConfig&:{&content_padding_top&:128,&content_padding_bottom&:56,&content_padding_left&:16,&content_padding_right&:16,&title_font_size&:22,&body_font_size&:16,&is_dark_theme&:false,&can_auto_load_image&:true,&app_info&:&OS=iOS&},&isApp&:false},&message&:{&newCount&:0},&pushNotification&:{&newCount&:0}}

我要回帖

更多关于 wrap 的文章

 

随机推荐