接着上一篇View绘制流程及源码解析(一)——performTraversals()源码分析,这一篇我们来具体看看三大流程的实现过程。
一. 从MeasureSpec说起
由于剧情的需要,我们不得不先说一下这个MeasureSpec。为什么要先说这个东西呢?再上一篇文章中,我们是否还记得三大流程正式开始的地方的代码:
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
可见,getRootMeasureSpec()是获取根布局的MeasureSpec,而最终的performMeasure()正式执行measure()的流程就是所传递就去的参数就是这个getRootMeasureSpec()所计算得到的值,因此我们先要搞清楚这个MeasureSpec是什么玩意。
那么什么是MeasureSpec呢?首先,他代表的是一个32位的int值。其次,它是View类中的一个内部类,所以我们再接下来的文章分析中,可能会用到它所代表的int值和它本身这两种情况,希望读者再阅读的时候可以加以区分。我们来看看它的源码中的注释(framewoks/base/core/java/android/view/View):
/**
* 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. There are three possible
* modes:
* <dl>
* <dt>UNSPECIFIED</dt>
* <dd>
* The parent has not imposed any constraint on the child. It can be whatever size
* it wants.
* </dd>
*
* <dt>EXACTLY</dt>
* <dd>
* 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.
* </dd>
*
* <dt>AT_MOST</dt>
* <dd>
* The child can be as large as it wants up to the specified size.
* </dd>
* </dl>
*
* MeasureSpecs are implemented as ints to reduce object allocation. This class
* is provided to pack and unpack the <size, mode> tuple into the int.
*/
public static class MeasureSpec {
上面的注释交代了四点:
(1)MeasureSpec封装了从父布局传递给子布局的的布局要求。
(2)每一个MeasureSpec代表了一个对宽度或着高度的要求。
(3)一个MeasureSpec由一个size和一个mode组成,有三种可能:
①UNSPECIFIED:父布局对于子布局/Veiw没有任何约束,子布局可以是任何它想要的尺寸。根据主席的说法,这中情况一般用于系统内部反复测量控件大小,我们再用的过程中很少见到。
②EXACTLY:
父布局精确设定了子布局/View的大小,无论子布局想要多大的都将得到父布局给予的精确边界。这种情况实际上就对应的是我们在xml文件或者LayoutParams中设置的xxdp/px或者MATH_PARENT这两种情况,也就是精确的给予了布局边界。
③AT_MOST:在确定的尺寸内,子布局/View可以尽可能的大。这个确定的尺寸,当然指就值得就是父布局的大小,毕竟子布局/View再大,也不能超出父布局的大小。这种情况对应的就是我们再xml文件或者LayoutParams中设置的WRAP_CONTENT,子View可以随着内容的增加而申请更大的尺寸,但是不能超过父布局的大小。
(4)MeasureSpec将SpecMode和SpecSize打包成一个int值以便减少对象分配(的开支)。这个类提供了打包和解包size,mode的各中方法。
前面我们提到过,MeasureSpec是一个32位的int值,通过上面一段我们又知道他是一个大小(Size)和模式(Mode)的组合值,它的存在是为了减少对象分配的开支,那么我们可以做出进一步解释:
①32位的int值中,高2位是SpecMode,代表测量模式;低30位是SpecMode,代表再某种模式下得出的测量大小。
②我们在上面(1)(2)中我们将"要求"两个字着重加黑,就是为了强调:这个MeasureSpec的值是"一个父传递给子测量要求","怎么测量的要求",不是父对子强加的布局参数什么的,至于这个要求是什么,怎么要求的,后面我们会做进一步说明。
③至于为什么要强调"传递"两个字,主要是因为,上面的"测量要求"不是父强加给子的,而是两个人一起商量着来的。具体的说,对于除了DecorView外的普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定的,当然从源码来看还与View的margin和padding有关;而对于DecorView来说,其MeasureSpec由它自身的LayoutParams决定(毕竟它已经是根布局了,上面再没人了)。
说了这么一大通,下面我们从源码的角度带大家分析一下根布局和自布局确定MeasureSpec的过程。
(一).根View/DecorView的MeasureSpec
前面开篇代码中的getRootMeasureSpec()看名字我们也知道他是获得根布局的MeasureSpec的方法
(framewoks/base/core/java/android/view/ViewRootImpl):
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
通过上述代码,我们可以看到RootView(DecorView)的MeasureSpec创建的全过程,分为三种情况:
- LayoutParams.MATH_PARENT:精确模式,强制让RootView的大小等于Window的大小。
- LayoutParams.WRAP_CONTENT:最大模式,限制了RootView的最大窗口(父布局/View的大小。
- DEFAULT:指定大小,也就是在xml或者LayoutParams中指定View的宽度和高度的px/dp数。
在这三种情况下,分别调用makeMeasureSpec()方法,将模式和大小值打包成一个int值返回。
(二).普通View的MeasureSpec
获取普通View(除DecorView以外的View,大多数情况下指的是子布局/View)的MeasureSpec值的代码在ViewGroup类中,相对复杂一点(framewoks/base/core/java/android/view/ViewGroup):
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//通过父View的MeasureSpec和子View的自己LayoutParams的计算,算出子View的MeasureSpec,然后父容器传递给
//子容器的然后让子View用这个MeasureSpec(一个测量要求,比如不能超过多大)去测量自己,如果子View是
//ViewGroup 那还会递归往下测量。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到,和开篇代码中root测量的代码一样,这里的Child在测量之前,先得getChildMeasureSpec()。还记得我们上面说的吗?对于除了DecorView外的普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定的,当然从源码来看还与View的margin和padding有关,这里我们可以看源码感受一下:
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
//这里的mWidth就是DecorView的宽度,lp是它的LayoutParams参数。
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
可以看到,getRootMeasureSpec中,只需要传递进去DecorView自身的宽度和它的LayoutParams宽度设置这两个参数;而getChildMeasureSpec中则需要考虑父布局的MeasureSpec(parentWidthMeasureSpec),父View的Padding,自身设置的Margin,已经用掉的空间大小(widthUsed),LayoutParams的宽度设置(lp.width)。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //父View的mode
int specSize = MeasureSpec.getSize(spec); //父View的size
//(父View的大小-自己的Padding+子View的Margin),得到值才是子View的大小,所以这里的size并不是子View的大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
//父View强加了一个精确的值给我们,父布局的模式是EXACTLY。这种情况对应于MATH_PARENT和确定的px/dp值这两种情况。
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//子View的宽度和高度值为一个确定的dp/px值。
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
//子布局的LayoutParams为MATCH_PARENT,那么最终得出的模式为EXACTLY。
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be bigger than us.
//子布局的LayoutParams设为WRAP_CONTENT,那么最终的布局模式为AT_MOST。
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
//父布局强加给我们一个最大值,即父布局的模式为AT_MOST.
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
//这个时候如果子布局/View想要一个精确的尺寸(xxdp/px),那么最终的结果为EXACTLY。
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
//如果子View的LayoutParams为MATH_PARENT,那么最终得到的结果为AT_MOST。
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
//如果子View的LayoutParams为WRAP_CONTENT,那么最终的结果模式为AT_MOST。
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
我们将上面的这段代码中子View的模式和父View模式的对应关系用一个表格表示一下:
这里的resultMode为最终得到的子VIew的SpecMode,resultSize为最终得到的子View的大小。而childeSize就是上面的childDimension,即精确对定义子View大小的时候我们在LayotParams中所设定的子View大小,parentSize则为父容器中目前剩余的可用大小。
二.measure()过程
(一).View的measure()过程
好了,清楚了MeasureSpec这个值以及我们如何得到父View及View的MeasureSpec之后呢,我们接下来就开始看三大流程的第一步:Measure(测量)过程(framewoks/base/core/java/android/view/View):
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if (cacheIndex < 0 || sIgnoreMeasureCache) {
......
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
可以看到,这个Measure()方法是final的,也就是说我们不能重写,但是由于具体的测量过程是在onMeasure()中完成的,所以有需要的话我们可以重写这个方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
......
顾名思义,这个setMeasuredDimension()方法是设置View宽/高的测量值,而它传入的两个参数getDefaultSize方法则是获取到的宽/高值。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
首先我们来看看它的第一个参数getSuggestedMinimumWidth(),只有一句代码,比较简单,他返回的是View的建议宽度/高度。这个建议值是由View的背景尺寸和它的mMinWidth属性决定的,mBackground就是背景,mMinWidth表示View的xml文件中android:minWidth属性所指定的值,如果这个属性没有设置,那么就默认为0。
从源码来看,这个方法中通过一个三目运算符,显示判断mBackground是否为null,也就是有没有。如果为ture也就是没有背景,那么就返回mMinWidth的值,这个值可以为0;如果View指定了背景,那么返回的是mMinWidth和mBackground最小宽度两个值中较大的那个。这里指贴了getSuggestedMinimumWidth的源码,getSuggestedMinimumHeight()的源码是一样的。
现在我们清楚了getDefaultSize()方法中第一个参数的意义,第二个参数measureSpec就是上面我们一直讲的宽度/高度的MeasureSpec值。现在我们来看看这个方法:
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;
}
可以看出这个方法的逻辑实际上比较简单,如果是UNSPECIFIED这种情况,返回值是上面getSuggestedMinimumWidth()得到的值。对于我们来说,只需要关注AT_MOST和EXACTLY这两种情况就行了。在这两种情况中,返回值也就是View的测量值都等于 MeasureSpec.getSize(measureSpec):
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
这个方法中做了什么呢?我们已经知道,这个measureSpec是一个封装了View的SpecMode和SpecSize大小的int值,其中高两位是模式,低三十位是大小。而这个MeasureSpec.getMode(measureSpec);
和MeasureSpec.getSize(measureSpec);
实际上就是分别将传递进去的measureSpec中封装的SpecMode和SpecSize剥离出来。
对于DecorView/View,measureSpec就是我们在开篇最上面的代码中计算出来的getRootMeasureSpec(mWidth, lp.width)。所以这里的specSize(对于DecorView)也就是我们最上边代码中的mWidth,也就是DecorView的Window的宽度。
而对于其他的一些View的派生类,如TextView、Button、ImageView等,它们的onMeasure方法系统了都做了重写,不会这么简单直接拿 MeasureSpec 的size来当大小,而去会先去测量字符或者图片的高度等,然后拿到View本身content这个高度(字符高度等),如果MeasureSpec是AT_MOST,而且View本身content的高度不超出MeasureSpec的size,那么可以直接用View本身content的高度(字符高度等),而不是像View.java 直接用MeasureSpec的size做为View的大小。
还有一点需要说明的是,上面我们强调View测量值的原因是,View最终值是在Layout阶段确定的,虽然几乎是所有情况下View测量大小和最终大小是一样的。
(二).ViewGroup的measure()过程
对于ViewGroup来说,出了完成自己的测量过程外,还会去遍历调用所有子View/ViewGroup的measure()方法,各个子元素再去递归执行这个方法。和View不同的是,ViewGroup是一个抽象类,他并没有重写真正实现测量自身的onMeasure()方法,而是将该方法下发到了各个子类去实现。为什么ViewGroup不像View一样对其onMeasure()方法做同意的实现呢?主要是因为不同的ViewGroup子类有不同的布局特性,比如LinearLayout和RelativeLayout这两者的布局特性显然不同,因此ViewGroup无法做统一的实现。
虽然ViewGroup测量自身的过程下放到了各个子类中去实现,但是在该类中我们任然可以看到该类递归调用子类测量方法的逻辑:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
遍历子Veiw,只要不是GONE的View都会调用。
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measureChild()方法中做的事情就是,取出子View的LayoutParams值,然后通过getChildMeasureSpec()方法(这个方法上面讲过)得出子元素的MeasureSpec,然后直接调用子View的measure(),之后所做的事情就和"(一).View的measure()过程"中的过程一样了。
上面就是子View的测量逻辑,下面我们需要着重看一下ViewGroup()自身的测量过程,这里我们选择LinearLayout来做分析(待会实例分析会用到)(framewroks/base/core/java/android/widget/linearlayout):
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
我们只分析竖直方向的测量过程,水平方向的过程是一样的:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0; //所有子View高度和
int maxWidth = 0; //所有子View中最大宽度值
......
final int count = getVirtualChildCount(); //竖直方向的子View总数
final int widthMode = MeasureSpec.getMode(widthMeasureSpec); //获取父布局的宽/高的模式
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false; //是否跳过测量
......
int largestChildHeight = Integer.MIN_VALUE;
// See how tall everyone is. Also remember max width.获取每一个子View的高度,同时记录最大的宽度
for (int i = 0; i < count; ++i) { //循环遍历每一个子View
final View child = getVirtualChildAt(i); //一个一个的获取子View
......
if (child.getVisibility() == View.GONE) { //如果这个子View的可见性为GONE
i += getChildrenSkipCount(child, i); //将这个子View连同他的序号一并计入getChildrenSkipCount()这个方法中
continue;
}
if (hasDividerBeforeChildAt(i)) { //如果这个子View带有divider分割线
mTotalLength += mDividerHeight; //mTotalLength累加分割线的高度。
}//mDividerHeight对应android:divider="@drawable/spacer_medium"这个属性的高度,通常添加的是一个drawable图片
//获取子View的LayoutParams属性
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight; //totalWeight累加LayoutParams中的weight属性值
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
//如果这个子View高度的heightMode为EXACTLY,并且高度为0,并且比重大于0。对照上文的表格可知,当父mode为EXACTLY
//时,此时子View的高度为一个确定的值(这里为0),那么最终的高度就为childSize也就是0.显然这个时候没有可见的View。
final int totalLength = mTotalLength;
//注意这里的totalLength/mTotalLength表示的是之前测量过的子View的高度和(以及如果本次测量的View有divider分
//割线的话就加上本次测量View的分割线图片高度)
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
//此时总高度(本次+测过的)就是totalLength和(totalLength+本次测量View的上下Margin)两个值中较大的那个.
skippedMeasure = true; //跳过测量的标志位(毕竟这个高度是0,也就是没有可见的View,就没有测量的意义了)
} else {
int oldHeight = Integer.MIN_VALUE; //oldHeight设为负无穷
if (lp.height == 0 && lp.weight > 0) {
//else代表着此时父View的模式heightMode为UNSPECIFIED或者AT_MOST,并且这个子View想延伸以填充剩余的可用
//空间(虽然它的高度为0,但是它的比重不为0,说明他有填充剩余可用空间的“欲望”),此时我们将它的模式转变成
//WRAP_CONTENT以便它最终的高度不为0(同样对照上文的表格克制此时子View的大小为父View剩余空间的大小,相当于
//match_parent,当然这里暂时不考虑UNSPECIFIED这种情况。)
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
}
//最终决定子View大小的方法
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec, ①
totalWeight == 0 ? mTotalLength : 0);
if (oldHeight != Integer.MIN_VALUE) {//如果oldHeight不为负无穷,说明经历了上段代码中else的情况
lp.height = oldHeight; //此时height=oldHeight=0
}
final int childHeight = child.getMeasuredHeight(); //获取当前子View的测量高度
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
//mTotalLength + childHeight +lp.topMargin +lp.bottomMargin + getNextLocationOffset(child)
//表示之前测量过的子View的总高度+本次测量的子View的高度+本次测量的子Veiw的上下Margin,最后一个方法返
//回值为0,可以直接忽略。
//那么此时总高度(之前的子View+本次测量的子View)就等于之前测量的子View的总高度和上面一串串值,两者中的
//较大者,之所以这么干是因为margin+height的值可能是负的,不能因为margin的影响就缩小了总高度的测量和,
//这样做显然得出的不是真实的高度.
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
//这里的largestChildHeight表示高度最大的子Veiw
}
}
......
final int margin = lp.leftMargin + lp.rightMargin; //左右Margin
final int measuredWidth = child.getMeasuredWidth() + margin; //本次测量的子View的宽度+左右Magin
maxWidth = Math.max(maxWidth, measuredWidth); //获取总的子View中最大的Veiw宽度
......
}
上面这段代码就是循环遍历测量LinearLayout中所有子View的代码,主要获取两个值:每个子View的高度和所有子View中最大的宽度。其中高度和宽度均是加上上下/左右Margin之后的结果。注释已经写的很清楚了,这里不在鳌述。
当LinearLayout测量完了所有子View之后,就需要开始测量自身了:
......
mTotalLength += mPaddingTop + mPaddingBottom; //上面的for循环已经计算完了所有的子Veiw的高度,这里开始计算
//LinearLayout自己的高度,也就是在所有子View高度之和的基础之上加上上下Padding
int heightSize = mTotalLength;
// 将高度值再做一遍比较,即之前的高度值和getSuggestedMinimumHeight()较大的那个,getSuggestedMinimumHeight()前面讲过
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); ②
heightSize = heightSizeAndState & MEASURED_SIZE_MASK; //最终得出的LinearLayout总高度
......(省略针对不同情况缩放子View的代码)
maxWidth += mPaddingLeft + mPaddingRight; //LinearLayout总宽度
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); //LinearLayout总宽度
//所有的子View测量之后,经过一系类的计算之后通过setMeasuredDimension()设置自己的宽高,对于LinearLayout,可能是高度的
//累加,对于FrameLayout 可能用最大的字View的大小,
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}
当子元素测量完之后,LinearLayout就会根据子元素的测量情况来测量自己的大小。针对竖直方向的LinearLayout,它的宽度测量实际上就是View的测量过程(最大的子View宽度+左右margin+左右padding),具体到代码中为:
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
maxWidth += mPaddingLeft + mPaddingRight;
而LinearLayout的高度测量稍微复杂一点,具体来说是指,如果它的布局中高度采用的是math_parent或者具体的数值,那么它的测量过程和View一致,即高度为sizeSpec;如果它的布局中高度采用的是wrap_content,那么它的高度是所有子元素占用的高度总和,但是仍然不能超过它父容器的剩余空间,当然它的最终高度还是要考虑再竖直方向得到padding.(这段结论取自《Android开发艺术探索》,笔者在分析中并没有找到支持该结论的相关源码,但是相信主席一回,如果有人找到了欢迎指教)
最终决定它的高度的是上面②处的resolveSizeAndState()方法(framewoks/base/core/java/android/view/View):
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
这个方法主要的作用是在父布局的MeasureSpec约束下,协调所需要的大小和状态。其中size是当前LinearLayout所需要的宽度(最大宽度),measureSpec是父布局的MeasureSpec,childMeasuredState为子View的测量状态。上面的代码已经很清楚了,这里不再加以分析。
这里还要说明的一点就是上段代码中①处的measureChildBeforeLayout()方法:
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到,改方法最终调用了ViewGroup类中的measureChildWithMargins()方法,最终调用了View类中的measure()方法。
(三)举例说明
进过上面的过程我们的measure过程的源码就分析完了,下面我们通过一个例子来具体说明下这个过程。首先呢,我们要回顾一下浅谈Activity从建立到显示(setContentView源码浅析)中的一些内容:这片文章中,我们
为了说明开发中我们自己写的xml文件在DecorView的层次关系,用到了一个系统定义的xml文件:R.layout.screen_simple,这个布局是我们的theme设置为NoTitleBar时的布局(SDK/platforms/android-23/data/res/layout/screen_simple.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
这个时候我们定义了一个activity_main.xml文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:paddingBottom="50dp"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hellow word"
android:textSize="20sp"/>
<View
android:id="@+id/selfView"
android:layout_width="match_parent"
android:layout_height="100dp" />
</LinearLayout>
并且画出了两者的层级关系图:
OK,相信通过上一篇文章的分析你已经清楚上面这张途中各部分之间的关系了,下面我们对上面布局中的一些元素做一下必要的说明:
①上面我们说过,这个R.layout.screen_simple是我们将系统的theme设置为NoTitleBar时的布局。NoTitleBar也就意味着没有titlebar或者actionbar的干扰,整个content区域只有我们的activity_main.xml,这样就方便了我们之后对于测量流程的分析。
②图中id为statusBarBackgroud的Veiw,顾名思义就是状态栏背景。为了抓住主要矛盾,在之后的分析中我们会跳过状态栏的测量分析。
③screen_simple下属的ViewStub,我们对这个东西做一下说明:ViewStub是View的子类;它不可见(可见性为GONE),宽高均为0;它用来延迟加载布局资源(惰性加载),常用于布局优化。显然,ViewStub在这里是用来惰性加载titlebar/actionbar的,关于什么是惰性加载,读者可以自行搜索;另外,由于它的可见性为GONE,并且宽高都是0,因此再measure()的过程中系统也会自动跳过。
OK,下面我们正式开始测量流程的分析。首先,我们要回到三大流程开始的地方,也就是ViewRootImpl类中的performTraversals()方法,不清楚的同学可以看View绘制流程及源码解析(一)——performTraversals()源码分析这片文章。再这片文章的"第六段代码"中,我们看到了measure()过程的开始方法——performMeasure():
......
//mStopped的注释:Set to true if the owner of this window is in the stopped state.如果此窗口的所有者
//(Activity)处于停止状态,则为ture.
if (!mStopped || mReportNextDraw) { //mReportNextDraw Window上报下一次绘制.
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
//mWidth != host.getMeasuredWidth() 表示frame的宽不等于初始DecorView宽.
//getMeasuredWidth()方法可以获取View测量后的宽高,host上面说过为DecorView根布局.
//获得view宽高的测量规格,lp.width和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//开始执行测量操作 ①
第一步,DecorVeiw的测量
通过上篇文章之前的分析可知,这里的getRootMeasureSpec()就是获取DecorView的MeasureSpec,也就是说这里进行的是顶层View——DecorView的测量。在performMeasure()方法中,我们调用了DecorView的measure()方法:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); //mView就是DecorView
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
而DecorView是一个FrameLayout,因此我们需要区看FrameLayout类的onMeaure()方法,这个方法相对LinearLayout的简单多了(早知道在上面我就直接分析FrameLayout了,mdzz):
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
.......
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 循环遍历的子View,只要不是GONE的都会参与测量
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
......
}
}
......
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
......
}
需要说明的一点是,由于DecorView是最顶层的View,因此它的宽高MeasureSpec是由它自己的宽度和LayoutParams决定的getRootMeasureSpec(mWidth, lp.width);
和getRootMeasureSpec(mHeight, lp.height);
。再者,由于DecorView是最顶层View的缘故,通常情况下它可以代表整个屏幕,因此mWidth和mHeight既是DecorView的宽高也是整个屏幕的宽高。lp是WindowManager.LayoutParams,它的lp.width和lp.height的默认值是MATCH_PARENT,所以对于DecorView通过getRootMeasureSpec 生成的测量规格MeasureSpec 的mode是MATCH_PARENT,size是屏幕的高宽,这里我们假设有一个1920x1080像素的手机那么size就对应这两个值。再根据文章最开头第一大点中的第(一)小点"根布局的MeasureSpec获取"可知,当根布局的LayoutParams为Match_parent时,它的MeasureSpec.mode为EXACTLY,用图像表示为:
第二步,DecorView到screen_simple
在FrmeLayout的onMeasure()方法中,for循环遍历的子View只有两个:screen_simple,xml和stautsbarbackdroud,前面我们已经说过会跳过状态栏的分析,所以这里只看screen_simple,xml对应的View。measureChildWithMargins()中第一个参数child代表的就是screen_simple.xml对应的View,可以看到最终调用了该View的measure()方法,并传递进去了DecorView的宽高MeasureSpec。
我们看看此时screen_simple对应的View的MeasureSpec的两个参数。由于该View是系统的View,它的LayoutParams默认都是match_parent,又因为它的父布局(DecorVeiw)的mode为EXACTLY,根据我们文章开头LayoutParams和MeasureSpec的对应关系可知,screen_simple的size为父布局剩余的空间大小,mode为EXACTLY;又因为DecorView是FrameLayout,statusbar是叠在screen_simple上面的,因此相当于状态栏不占用底下一层的DecorView的空间——综上,screen_simple的size等于DecorView的size为1920x1080,mode为EXACTLY。用图表示:
算出screen_simple.xml对应的View的MeasureSpec之后呢,就开始计算调用measure()方法计算它的大小。由上面层次图及xml文件可知screen_simple.xml对应的View是一个方向为vertical的LinearLayout,因此child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
最终会调用LinearLayout的onMeasure()方法,(这个过程在上文是有详细的讲解的,如果不记得的话可以翻上去看一下)最终会调用LinearLayout的measureChildBeforeLayout()并在该方法中又一次调用measureChildWithMargins(child, widthMeasureSpec, totalWidth,heightMeasureSpec, totalHeight);
方法,开始计算screen_simple.xml当中各个子元素的大小(子元素测量完之后再测量自身的大小)。
这里还要注意的一点是screen_simple.xml对应的View有一个padding值:mPaddingTop=100px,这个可能和状态栏的高度有关,我们测量的最后会发现id/statusBarBackground的View的高度刚好等于100px。
第三步,从screen_simple到R.id.content
这个时候我们进入了screen_simple.xml对应的View中,该View有两个子元素:R.id.content和VeiwStub。ViewStub上面我们已经说过,它的可见性为GONE,且宽高均为0,所以测量的话会系统会直接跳过。那么我们就只剩下R.id.content了,通过上面的结构层次图,我们知道这个R.id.content实际上就是我们写的activity_main.xml文件。
这里一样,我们首先需要根据screen_simple.xml对应View的MeasureSpec(size:1980x1080,mode:EXACTLY)和R.id.content的LayoutParams(match_parent x match_parent)来计算出它的MeasureSpec,当然这里的计算也需要遵循ViewGroup的measureChildWithMargins()方法,这个方法我们之前讲过,这里我们再贴一遍源码:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
**resultSize = size;**
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
......
}
这里我们只看高度,之前提到过,screen_simple.xml对应的View有一个padding值:mPaddingBottom=100px,因此getChildMeasureSpec(int spec, int padding, int childDimension)
中第二个参数的值为100px,第三个参数值为match_parent。由int size = Math.max(0, specSize - padding);
可知这里的size变量为(1920-100)x1080 = 1820x1080,又父布局screen_simple.xml的specMode为EXACTLY,childDimension == LayoutParams.MATCH_PARENT,因此最终的结果resultSize = size = 1820x1080;resultMode = MeasureSpec.EXACTLY。用图表示为:
第四步,activity_main.xml测量规格MeasureSpec分析
R.id.content是一个FrameLayout,下面就一个activity_main.xml子元素,这个时候我们同样先要确定activity_main.xml这个LinearLayout(id/linearLayout)的MeasureSpec值,这里我们再贴一下布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:paddingBottom="50dp"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hellow word" />
<View
android:id="@+id/selfView"
android:layout_width="match_parent"
android:layout_height="100dp" />
</LinearLayout>
注意id/linearLayout的android:layout_width
,android:layout_height
,这几个参数的值。父布局screen_simple.xml的MeasureSpec.heightMode = EXACTLY,id/linearLayout的android:layout_height为wrap_content,对照上文的LayoutParams与MeasureSpec可知,id/linearLayout的MeasureSpec.heightMode为AT_MOST;同理,它的MeasureSpec.weightMode为EXACTLY;
下面我们来看它的specSize,注意:android:layout_marginTop="50dp"
,这里我们需要说明一下Android设备中px/dp的换算——dp x 基准比例 = px; 基准比例 = 系统 DPI / 160 ;一般在1920*1080 分辨率的手机上 默认就使用 480 的 dpi ,不管的你的尺寸是多大都是这样,除非厂家手动修改了配置文件,因此这里可以得到——基准比例=480 / 160 = 3;px = dp x 基准比例 = 50 x 3 = 150px。(关于更多这方面的知识推荐一片文章[Android] Android开发中dip,dpi,density,px等详解感兴趣的渎职可自行参阅)。也就是说,id/linearLayout在高度上少了150px,根据上面getChildMeasureSpec()方法中第二个参数:padding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed可知padding = 150px,又int size = Math.max(0, specSize - padding) = 1670 x 1080。
所以我们现在可以得出id/linearLayout的MeasureSpec,用图表示:
第五步,TextView和View的测量分析
1.TextView
接上面,先看TextView:
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hellow word" />
由它的父布局id/linearLayout的MeasureSpec.mode可知,TextView的MeasureSpec.heightMode为AT_MOST;MeasureSpec.weightMode为EXACTLY;至于它的大小,由于id/linearLayout中有android:paddingBottom="50dp"
,它的子元素的生存空间又被压缩了50dp也就是150px,所以TextView的Measure.specSize为(1670-150)x1080=1520x1080(注意这个是对TextView的宽高约大小束,可不是正真的大小,不要忘了MeasureSpecd的正真含义)。算出id/textView 的MeasureSpec 后,接下来我们来计算textView的正真大小:textView.measure(childWidthMeasureSpec, childHeightMeasureSpec);跳转到TextView.onMeasure()方法中(frameworks/base/core/java/android/widget/TextView):
if (heightMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
height = heightSize;
mDesiredHeightAtMeasure = -1;
} else {
int desired = getDesiredHeight(); //desired = 107px
height = desired;
mDesiredHeightAtMeasure = desired;
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desired, heightSize); //heightSize = 1520px
}
}
TextView字符的高度(也就是TextView的content高度[wrap_content])测出来等于107px,107px 并没有超过1980px(允许的最大高度),所以实际测量出来TextView的高度是107px。(这个笔者并没有亲自测,直接拿来主义,但是差距不会太大)
最终算出id/text 的mMeasureWidth=1080,mMeasureHeight=107px。
2.View
接下来我们来看这个id/selfView的子View:
<View
android:id="@+id/selfView"
android:layout_width="match_parent"
android:layout_height="100dp" />
由于这里指明了它的高度为100dp也就是300px,加上它的父布局id/linearLayout的MeasureSpec.heightMode为AT_MOST,因此它的MeasureSpec.heightMode为EXACTLY,MeasureSpec.weightMode也为EXACTLY,heightSpecSize为自己的尺寸300px,所以它的specSize为300 x 1080,然后计算View的大小:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
最终算出id/selfView的mMeasureWidth=1080px,mMeasureHeight=300px。
第六步,activity_main.xml自身大小测量分析
id/linearLayout 的子View的高度都计算完毕了,接下来id/linearLayout就通过所有子View的测量结果计算自己的高宽,之前我们着重分析过LinearLayout的测量过程,这里我们再贴一下关键代码:
mTotalLength += mPaddingTop + mPaddingBottom;
简单理解就是子View的高度的累积+自己的Padding,也就是107px(TextView) + 300px(View) + 150px(paddingBottom="50dp") = 557px最终算出id/linearLayout的mMeasureWidth=1080px,mMeasureHeight=557px。
第七步,从activity到R.id.content,计算R.id.content自身的大小
第八步,从R.id.content到screen_simple,计算screen_simple自身的大小
第九步,从screen_simple到DecotView,计算DeorView自身的大小
后面三步和第六步同理,逻辑都是计算完了子元素的大小加上自身的padding,计算出自身的大小。
站在巨人的肩膀上摘苹果:
①Android View的绘制流程
②《Android开发艺术探索》