上一篇的结尾中,我们发现了View的绘制发生在ViewRootImpl
的performTraversals()
中.而且在其中先后调用了performMeasure(),performLayout(),performDraw()
.
如此一来,我们又有了新的猎物了.就像美食一样,好吃的东西一定要仔细地品尝.在上主菜之前,我们先来点开胃菜.我们先来了解一下Android是怎样绘制View的.(官方的文档How Android Draws Views)
开胃菜(关于View绘制时需要知道的常识)
当Activity
接受焦点时,就会被要求绘制其布局.虽然Android Framework
会处理这个过程,但Activity
必须提供整个布局层级的根节点,因为需要知道从哪开始绘制.
Activity
的整个布局被转换成了一棵树,绘制整个布局就相当于了遍历整颗树并把每个节点的View绘制出来.相应地,ViewGroup
负责要求它的每一个child
进行绘制,而View
则负责绘制自己.由于树的遍历是有序的,所以父View绘制之前会先绘制其子View,而兄弟节点会按照在树中出现的顺序进行绘制.
绘制布局需要进行两个传递过程(pass process)
:分别是测量时的传递(measure pass)
和布局时的传递(layout pass)
.这里所说的传递指的是在view tree
的各个节点之间的传递.
-
measure pass
在measure(int,int)
中实现,而且它是一个从上到下的传递.在view tree
中每个View节点都将它的尺寸规格向下传递给它的孩子,在整个传递过程结束时,每个节点都应该拥有了自己的测量值(尺寸大小). -
layout pass
在layout(int,int,int,int)
中发生,它同样也是一个从上到下的传递.在传递过程中每个parent
都需要根据在measure pass
时得到的测量值在布局中放置它的所有children
.
下面贴上一张普通的view tree
的图.
在measure()
函数中,官方定下了一些规则,在函数执行完毕返回前必须要满足下面的条件:
- View(以及其后代节点)的
getMeasuredWidth()(即mMeasuredWidth的值)
和getMeasuredHeight()(即mMeasuredHeight的值)
的值必须已经设置.从函数名已经知道函数获取的是已经测量的宽高值,measure()
函数结束就表明测量结束了,这一条规则理所当然. - View测量后的宽高必须符合其父View所规定的大小.这一条规则可以保证当
measure pass
结束时,所有的parents
能接受其所有children
的测量值.这也很好理解,子View的大小总不能比其父布局还大吧,否则就没有意义了.
一个为parent
的View可能会不止一次地对其children
调用measure()
.因为如果parent
使用未指定的尺寸测量它的每一个child
得到各个child
想要的大小,但如果所有children
的(未加限制的)测量值的总和太大或太小,那就需要parent
再次调用measure()
重新测量,但这次的测量设置了相应的规则.(举个比喻,就像孩子们在分配糖果时,大家都对所分配的糖果不满意时,父母就会干涉并重新分配)
我要吃神户牛柳(深入measure
过程)
吃过开胃菜后,再来品尝我们的主菜就会更加的美味.美味的食物通常都有独特的吃法,比如使用特定的餐具.我们的第一道菜(measure
)就是神户牛柳,我们需要准备刀叉来用餐.那先准备一下我们的餐具吧.(与measure
过程密切相关的两个类).
刀 (ViewGroup.LayoutParams)
先来说明一下ViewGroup.LayoutParams
是干什么用的.View通过ViewGroup.LayoutParams
来告诉它的parent
它在布局中想被放在什么位置和想占多大.而基本的ViewGroup.LayoutParams
只能表达View想占多宽和多高,可以通过下面的其中一种方式表达:
- 一个确切的数值大小
-
MATCH_PARENT
,表达View想要和它的parent
一样大(去掉View的内边距) -
WRAP_CONTENT
,表达View只想要能将它的内容包裹的大小(加上View的内边距)
ViewGroup.LayoutParams
只能表达View大小,但ViewGroup
的子类的LayoutParams
能表达View的位置.
叉 (MeasureSpec)
看过我的自定义View#02文章的同学可能会对MeasureSpec
有所了解.MeasureSpec
被parent
用来限制child
的大小,在measure()
的过程中,它作为参数,从view tree
的根节点往下传递到它的子节点和其后代.它有下列3种模式:
-
UNSPECIFIED
, 这种模式表明parent
对它的child
的大小没有限制,child
可以告诉parent
它自己所希望的尺寸. -
EXACTLY
, 这种模式表明parent
给child
设置了一个确切的值,child
必须使用这个值,并且需要保证child
的后代节点都要符合这个值的设置 -
AT_MOST
, 这种模式表明parent
给child
设置了一个最大值,child
可以是它想要的任何值,但child
以及它的后代节点的尺寸大小都必须保证在这个最大值内.
既然MeasureSpec
有相应的模式来限制View的尺寸,那用什么来表示限制尺寸的大小呢.MeasureSpec
采用了一个32位的int值来代表模式和大小,高2位表示模式,低30位表示大小.
有了我们的餐具后,我们可以终于可以开动了.我们先从ViewRootImpl.performTraversals()
中调用performMeasure()
的地方开始,下面是该部分的代码:
.......
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
// 标注 1
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);
// Ask host how big it wants to be
// 标注 2
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
// 标注 3
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
// 标注 4
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(TAG,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
.......
上面的代码我做了4个标注,我们一个一个来看,先看标注1
的代码.
// 标注 1
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
其中这里的mHeight
和mWidth
分别是窗口(Activity)的宽高,lp
为窗口(Window)的布局参数.childWidthMeasureSpec
和childHeightMeasureSpec
这两个变量其实从标注2
的代码就很容易的看出它们是作为参数传进performMeasure(int,int)
的.那我们就看看它们代表的是什么意思.
由于它们是从getRootMeasureSpec(int,int)
获取的,从函数名可以看出这个函数是用来获取Root
节点的MeasureSpec
的(就是根节点在测量时给它的孩子节点所定下的尺寸大小的限制).但我们还是要看看这个函数的代码:
/**
* Figures out the measure spec for the root view in a window based on it's
* layout params.
*
* @param windowSize
* The available width or height of the window
*
* @param rootDimension
* The layout params for one dimension (width or height) of the
* window.
*
* @return The measure spec to use to measure the root view.
*/
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.
// 如果布局参数要求MATCH_PARENT,那么就设置为窗口的大小,模式为EXACTLY,因为窗口(Activity)的大小固定
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
//如果布局参数为WRAP_CONTENT,就设置为AT_MOST模式,最大值为窗口大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
// 如果布局的参数为一个确切的值,那我们就让root view为该值,模式为EXACTLY
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
getRootMeasureSpec(int,int)
的两个参数分别代表窗口的大小(windowSize
)和窗口的布局参数的大小(rootDimension
).官方的注释(我也做了相应的注释)已经写得很清楚了,这个函数通过window的布局参数来决定root view
的MeasureSpec
.
经过标注1
的代码,我们获取到了root tree
的根节点的MeasureSpec
,这样就可以从树的根节点开始进行测量传递的过程了(在开胃菜中提到的measure pass
).在对标注2
这个最主要的代码部分进行分析前,我们先来分析后面的标注3
和标注4
的代码.(好东西肯定要留到最后,反正我是这样想的)
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
// 标注 3
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
// 标注 4
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(TAG,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
标注3
首先获取到了测量后root view
的宽高值,然后分别判断lp.horizontalWeight
和lp.verticalWeight
的值是否大于0(即我们的布局参数在横向或纵向的weight
被设置时,可以理解为在xml布局文件里设置了layout_weight
属性).若设置了其中一个方向上的weight
,那么标注4
的代码都会执行,我们需要再调用一次performMeasure()
进行测量(measure pass
),但这次采用的是新的参数,把布局参数的weight
考虑进去.
可能有同学会问,为什么第一次调用
performMeasure()
前不把weight
考虑进去,测量完一次后才考虑这不让前面的工作都白费了吗?我也有相同的疑问,目前我还没找到一个准确的答案,但google这样写一定有它的道理,我在这里分享一下我的想法吧(不一定是正确的,如果错了希望大家能指正).我是这样想的:第一次调用
performMeasure()
的时候并不知道weight
是否设置了,因为我们通常设置layout_weight
属性都是在子View中设置的,在子View测量完毕前,父布局并不知道它的所有子View的weight
属性,而父布局的测量发生在子View测量结束后,所以我们可能需要进行两次的测量传递过程(measure pass
).
好的,清楚了我们标注3,标注4
的代码后,我们可以迎接我们的主角performMeasure()
了,下面就是performMeasure()
的代码.
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
原来代码中调用的是mView.measure(int,int)
,即调用了root view
的measure()
,既然如此,我们来看measure()
的代码:
/**
* <p>
* This is called to find out how big a view should be. The parent
* supplies constraint information in the width and height parameters.
* </p>
*
* <p>
* The actual measurement work of a view is performed in
* {@link #onMeasure(int, int)}, called by this method. Therefore, only
* {@link #onMeasure(int, int)} can and must be overridden by subclasses.
* </p>
*
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the
* parent
* @param heightMeasureSpec Vertical space requirements as imposed by the
* parent
*
* @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
注释中也说了,真正的测量工作是发生在onMeasure(int,int)
函数中的,并且说明了View的子类可以并必须重写onMeasure()
来测量我们的View.这里的必须并不代表我们在自定义VIew的时候一定要重写onMeasure()
,因为onMeasure()
已经在View中实现了,在不重写的情况下会调用默认的实现.
既然注释中给我们指明了方向,那我们就来看看这个onMeasure()
.
神户牛的精华(onMeasure
)
onMeasure()
可以说是整个measure pass
的核心部分,就像是神户牛的精华一样.那现在我们就来感受一下这神户牛的精华所带来的美味.
由于在view tree
上不可能每个节点都是View节点(这里是叶子节点的意思),就像在"开胃菜"
中给大家展现的图一样,在view tree
中也会有ViewGroup
节点,像FrameLayout,LinearLayout,RelativeLayout...
,这些ViewGroup
节点都相应的实现了自己的onMeasure()
.那么这就说明了ViewGroup.onMeasure()
与View.onMeasure()
并不一样.既然测量传递的过程(measure pass
)是从根节点开始的,那我们也从ViewGroup
的onMeasure()
开始.这里我们使用的是FrameLayout
的代码(其他的ViewGroup
大家可以自己试着去分析).
提示:下面的代码可以先跳过,因为在后面会再提到.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 第1部分
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
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) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// 第2部分
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
//第3部分
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
Part 1
虽然代码不算很长,但为了方便,我们还是将它分为3个部分来分析吧(上面代码注释中所划分的).先来第1部分
的代码:
// 第1部分
int count = getChildCount();
// 编号1. 用于判断是否需要对布局参数为MATCH_PARENT的子View进行重新测量
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
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) {
// 编号2. 对每个子View进行测量
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 编号3. 得到所有子View中最大的宽度(加上子View的外边距)
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
// 编号4. 得到所有子View中最大的高度(加上子View的外边距)
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
// 编号5. 得到子View的MeasureState
childState = combineMeasuredStates(childState, child.getMeasuredState());
// 编号6. 将布局参数为MATCH_PARENT的子View加入到`mMatchParentChildren`集合中
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
为了后面的分析方便,我在上面的代码注释中都给相应的语句进行了编号,我们先来分析简单的.
编号1
的measureMatchParentChildren
是用于判断FrameLayout是否需要对布局参数为MATCH_PARENT
的子View进行第二次测量.这里我们等分析过编号2
的代码后再对这个变量进行解析(这里可以先留个疑问).
而编号3,编号4
的代码就是为了得到FrameLayout
所有孩子中测量后的最大宽高(加上边距),因为FrameLayout需要按照它的孩子中尺寸最大的宽高进行测量.
编号5
的代码就是为了得到子View的MeasuredState
,这个对我们来说是个新的概念.既然这样,我们就来看看child.getMeasuredState()
这个在View
类下的函数是个怎样的函数.
/**
* Return only the state bits of {@link #getMeasuredWidthAndState()}
* and {@link #getMeasuredHeightAndState()}, combined into one integer.
* The width component is in the regular bits {@link #MEASURED_STATE_MASK}
* and the height component is at the shifted bits
* {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}.
*/
// 将宽高的状态位结合成在一个32位的int值并返回
// 宽度的状态位在常规的位置
// 高度的状态位在偏移后的位置
public final int getMeasuredState() {
return (mMeasuredWidth&MEASURED_STATE_MASK)
| ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}
// 用于使高度的状态位偏移的位数
public static final int MEASURED_HEIGHT_STATE_SHIFT = 16;
我把用到的变量也贴在了上面的代码中.首先我们来了解一下什么是"宽高的状态位".我们知道mMeasuredHeight
或mMeasuredWidth
都是32位的int值,但这个值并不是一个表示宽高的实际大小的值,而是一个由宽高的状态和实际大小所组合的值.这里的高8位就表示状态(STATE
),而低24位表示的是实际的尺寸大小(SIZE
),这个信息可以从它们相应的掩码看出.
// 用于得出宽高的状态位的掩码
public static final int MEASURED_STATE_MASK = 0xff000000;
// 用于得出宽高的尺寸位的掩码
public static final int MEASURED_SIZE_MASK = 0x00ffffff;
这就解析了为什么我们的getMeasuredHeight()
函数返回的是mMeasuredHeight & MEASURED_SIZE_MASK
.而getMeasuredHeightAndState()
返回的是mMeasuredHeight
.相应的关于宽度的函数也是一个道理.
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
public final int getMeasuredHeightAndState() {
return mMeasuredHeight;
}
现在我们再来看getMeasuredState()
是怎样将宽高的状态位组合在一个int值中的.首先mMeasuredWidth & MEASURED_STATE_MASK
得到了宽度的状态位,保存在高8位.然后通过(mMeasuredHeight >> MEASURED_HEIGHT_STATE_SHIFT)
和(MEASURED_STATE_MASK >> MEASURED_HEIGHT_STATE_SHIFT)
将高度和状态掩码都右移了16位,现在高度的状态位在第8到第15位上,而MEASURED_STATE_MASK
变成了0x0000ff00
,接着将两个移位后的数进行按位相与(&
)得到了高度的状态位,保存在8-15位上.最后将处理后宽度和高度按位相或(|
)得到一个保存了宽度和高度的状态位的int值.如下图.
/**
* Merge two states as returned by {@link #getMeasuredState()}.
* @param curState The current state as returned from a view or the result
* of combining multiple views.
* @param newState The new view state to combine.
* @return Returns a new integer reflecting the combination of the two
* states.
*/
public static int combineMeasuredStates(int curState, int newState) {
return curState | newState;
}
回到编号5
的代码,就是为了将所有子View的state
都结合在一起,这个有什么作用现在也不好讲.先继续看吧.
编号2
的代码是将FrameLayout
中所有visibility
属性不为GONE
的子View都进行测量(即在布局中占据位置的View),使用的是measureChildWithMargins()
.下面我们来看这个函数的代码.
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding
* and margins. The child must have MarginLayoutParams The heavy lifting is
* done in getChildMeasureSpec.
*
* @param child The child to measure (需要测量的子View)
* @param parentWidthMeasureSpec The width requirements for this view
* (parent对子View宽度的要求(MeasureSpec))
* @param widthUsed Extra space that has been used up by the parent
* horizontally (possibly by other children of the parent)
* (被parent或其他兄弟节点在布局的水平方向上使用了的尺寸大小)
* @param parentHeightMeasureSpec The height requirements for this view
* (parent对子View高度的要求(MeasureSpec))
* @param heightUsed Extra space that has been used up by the parent
* vertically (possibly by other children of the parent)
* (被parent或其他兄弟节点在布局的垂直方向上使用了的尺寸大小)
*/
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);
}
像代码中的注释所写的一样,这个函数就是用来告诉child
需要进行测量.测量过程中需要遵循parent
的MeasureSpec
,还需要考虑将padding
和margin
的值.通过了getChildMeasureSpec()
得到了子View的MeasureSpec
后就可以调用子View的measure()
进行测量了.所以我们要来看看getChildMeasureSpec(int,int,int)
这个函数,先来看看函数原型的文档,因为我们要先搞清楚各个参数所代表的意义.
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view
* (对View的尺寸限制MeasureSpec)
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* (可以理解为父布局的padding值+View的margin值,即父布局中未使用的尺寸大小)
* @param childDimension How big the child wants to be in the current
* dimension
* (View希望在布局中的大小,即子View布局参数的宽高)
* @return a MeasureSpec integer for the child
*
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension)
这个函数是为了获得当前View的MeasureSpec
以便于进行测量和传递给子View的.函数中主要是根据父布局的MeasureSpec
来创建View自己的MeasureView
.下面是相应的代码.
代码有点长,希望能完整地看一遍.但如果不想看也不要紧,就粗略地扫一眼吧.因为后面有图片进行总结,正所谓一图胜千言啊!
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 分别获取父布局`MeasureSpec`中的模式和尺寸
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// 获取父布局实际提供给View的尺寸大小(去除边距)
// 即父布局最大的可用的大小
// 与0相比取最大值,以免尺寸大小为负值
int size = Math.max(0, specSize - padding);
// 当前View最终的尺寸大小和模式
int resultSize = 0;
int resultMode = 0;
// 根据父布局的模式来决定View的模式和尺寸
switch (specMode) {
// Parent has imposed an exact size on us
// 表示父布局的大小为确切的值
case MeasureSpec.EXACTLY:
// 由于`MATCH_PARENT`=-1,`WRAP_CONTENT`=-2,
// 所以childDimension >= 0 表示View的宽高布局参数为具体的值
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 表示View的布局参数为`MATCH_PARENT`,即View希望大小是父布局的最大的可以大小
// 模式与父布局一样为EXACTLY
// 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.
// 表示View的布局参数为`WRAP_CONTENT`,那么说明View的大小不明确,需要由它的内容决定
// 所以测量值的尺寸为父布局的最大的可以大小,模式为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
// 表示父布局的大小不确定,需要由父布局的内容决定
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
// 表明View的布局大小为确切的值
// 所以View的测量大小为布局参数的值,模式为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的布局参数为MATCH_PARENT
// 所以View的测量大小为父布局的可用大小,模式与父布局一样为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的布局参数为WRAP_CONTENT,即View的测量尺寸大小不确定,由其内容决定
// 所以View的测量大小为父布局最大的可以大小,模式为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
// 表示父布局对View的大小没有限制,通常用在ListView等可滚动的控件中
// 这种情况下父布局会满足View的所有要求
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;
}
// 根据最终的View的模式和尺寸生成View的MeasureSpec
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
虽然上面的代码不短,但代码逻辑并不复杂,而且我已经在代码上做了比较明白的注释了,如果还是不理解,那就请看图.
函数的功能就是为了给View生成一个
MeasureSpec
类型的int,而这个值是由模式和大小合成的,而且它们的值由父布局MeasureSpec
的模式和View的布局大小共同决定.上图就是一个决定View的Mode
和Size
的过程.这里有一点需要注意的,就是当父布局的
MeasureSpec
的模式为UNSPECIFIED
时,若View的布局大小不为一个具体的值那么resultSize
的大小就为0.这里决定resultSize
的值是下面的一条语句.
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
而这里的View.sUseZeroUnspecifiedMeasureSpec
可以在View中找到相应的信息.
/**
* Always return a size of 0 for MeasureSpec values with a mode of UNSPECIFIED
*/
static boolean sUseZeroUnspecifiedMeasureSpec = false;
public View(Context context) {
.....
// In M and newer, our widgets can pass a "hint" value in the size
// for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers
// know what the expected parent size is going to be, so e.g. list items can size
// themselves at 1/3 the size of their container. It breaks older apps though,
// specifically apps that use some popular open source libraries.
sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < M;
......
}
就是说若当前的Android版本小于M
的话那sUseZeroUnspecifiedMeasureSpec
的值就为true
.所以在旧版本的Android中,resultSize
的值都为0.
现在我们回到编号1
的地方就可能对那句代码有所理解了.
// 编号1. 用于判断是否需要对布局参数为MATCH_PARENT的子View进行重新测量
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
我们现在对应着上面的View的MeasureSpec
生成图来分析代码.将流程反过来看,即从有编号的框开始看,我们可以看到在(1),(2),(4),(7)
4种情况下resultMode
的值为EXACTLY
.而它们的条件有3种是childDimension >= 0
即FrameLayout的布局参数layout_width
或layout_height
为实际的具体值.另一种情况就是FramLayout的布局参数为MATCH_PARENT
且它的父布局为EXACTLY
. 综合的来讲可以这么理解吧就是当FrameLayout的布局宽高不同时设置为具体的值,或不同时为MATCH_PARENT
那么measureMatchParentChildren
这个值就为true
.
再简单的讲就是如果FrameLayout的宽高只要有一个设置为WRAP_CONTENT
,那么该值就为true
.那么就需要对布局参数为MATCH_PARENT
的子View进行重新测量.因为WRAP_CONTENT
的情况下父布局的测量值受子View的影响.
能看到这里的同学真是不简单啊,没想到第1部分
讲了这么久,(有的同学就可能会说:这分"神户牛柳"的量也太多了吧,吃得有点撑啊!)这里篇幅确实有点长,但如果仔细看下来的话还是能学到不少的东西.大家可以先休息一下,待会再来继续阅读.我也在下面做了分割线帮大家标记位置.
Part 2
我们来继续我们onMeasure()
的第2部分
代码的分析吧.相信我,当你看完第1部分
的分析后,后面就会很有感觉.
// 第2部分
// 前面我们的maxWidth和maxHeight只是计算了子View的外边距
// 但没有计算FrameLayout的内边距,所以在这里加上
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
// 保证我们的`maxWidth`和`maxHeight`不会太小(至少要等于最小的建议值)
// 这里的最少建议值与背景有关
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
// 上面保证了背景的宽高值,下面保证前景对的宽高值
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
// 标注
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
第2部分
的代码是先是确定了maxWidth
和maxHeight
的值,给大家看一眼getSuggestedMinimumHeight()
的代码吧,因为后面这个函数还会用到,我相信大家很容易就能理解.
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
我们主要还是看标注
的那句代码,setMeasuredDimension()
就是将测量好的宽高值存储下来供后面的布局过程使用.来看看代码吧.
/**
* <p>This method must be called by {@link #onMeasure(int, int)} to store the
* measured width and measured height. Failing to do so will trigger an
* exception at measurement time.</p>
*
* @param measuredWidth The measured width of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
* @param measuredHeight The measured height of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
/**
* Sets the measured dimension without extra processing for things like optical bounds.
* Useful for reapplying consistent values that have already been cooked with adjustments
* for optical bounds, etc. such as those from the measurement cache.
*
* @param measuredWidth The measured width of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
* @param measuredHeight The measured height of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
上面就是相关的代码,也比较简单,setMeasuredDimension()
中最后调用了setMeasuredDimensionRaw()
来设置mMeasuredWidth
与mMeasuredHeight
的值.其中关于Optical Bounds
有兴趣的同学可以到Internet上搜索一下,或看看下图.这里我们不作讨论,可以跳过.
了解了
setMeasuredDimension()
后,那我们再来看看调用处给它传进的两个参数resolveSizeAndState(maxWidth,widthMeasureSpec,childState)
和resolveSizeAndState(maxWidth,widthMeasureSpec,childState)
.既然调用了resolveSizeAndState()
,那就看看它的代码吧.
/**
* Utility to reconcile a desired size and state, with constraints imposed
* by a MeasureSpec. Will take the desired size, unless a different size
* is imposed by the constraints. The returned value is a compound integer,
* with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
* optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
* resulting size is smaller than the size the view wants to be.
*
* @param size How big the view wants to be.
* @param measureSpec Constraints imposed by the parent.
* @param childMeasuredState Size information bit mask for the view's
* children.
* @return Size information bit mask as defined by
* {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/
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) {
// 防止View超出了限制的大小所做的处理
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);
}
在第1部分
我们已经读过了不少的类似的代码了,在这里我就不啰嗦了.主要还是讲讲resolveSizeAndState(maxHeight,heightMeasureSpec,childState<<MEASURED_HEIGHT_STATE_SHIFT)
这句代码吧,为什么这里需要进行左移?如果前面有认真看的话就很容易理解,因为我们的childState
是存有宽高的状态的组合值,我们的高度的状态值存在第8-15位,所以这里需要将它左移16位(将状态位放置在常规的位置).
Part 3
来到第3部分
了,这部分比较简单,我们先来看看代码.
//第3部分
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
// 宽度为总宽度减去父布局的Padding和View的Margin
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
// 第1部分已经讨论过
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
这里的代码就是当FrameLayout有多于1个的子View的布局宽高为MATCH_PARENT
时(并且满足第1部分
中的measureMatchParentChildren
为true
),即当FrameLayout的宽高设置存在WRAP_CONTENT
时,对子View进行重新的测量.
View的onMeasure()
到这里我们FrameLayout
的onMeasure()
已经分析完毕了,既然我们已经分析过了ViewGroup
的onMeasure()
,那View
的onMeasure()
我觉得也免不了,废话不说赶紧上菜.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
代码还是很简单的,就一句.虽然嵌套很多,但只有一个函数我们没有见过,就是getDefaultSize()
,那就看看它是何方神圣.
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
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的MeasureSpec
的Size
的一种默认的方法.代码非常的简单,我就不再多说了.因为今天看这种代码看得真的不少,我相信很多同学都快要看吐了.
不过到这里我很开心,因为我们这次的任务完成了,measure
的过程我们已经分析完了.
最后的甜点(总结)
牛柳吃完了,不知道大家能不能消化,所以最后给大家上个甜点吧.最后还是用图说话,来总结一下measure
的整个流程.
网友评论