我们都知道android的所有View都是在自己的onMeasure()中确定自己的尺寸(即width和height),接下来我们就看一看是如何实现。
1. 什么是MeasureSpec?
android官方文档是这样描述的: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. [1]
也就是说:MeasureSpec 封装了父View对子View 的布局要求(其实就是对子View的长和宽的要求),并且同时MeasureSpec 由尺寸(size)和模式( mode)组成。
源码分析
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: 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.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
MeasureSpec代表一个32位的int值, 其中高2位代表SpecMode,而低30位代表SpecSize
UNSPECIFIED 父View对子View的并没确切的布局限制,这种情况一般用于系统内部。[2]
EXACTLY 父View对子View有精确的布局要求,这个时候这个时候View的大小就是MeasureSpec所指定的值,它对应于LayoutParams中具体的数值(比如666dp)和match_parent[2]
AT_MOST 父View指定了一个最大可用的大小,子View不能超过这个限制。它对应于LayoutParams中的wrap_content[2]
2. MeasureSpec 和LayoutParams的关系(子View的MeasureSpec的产生)
从上面我可以看出父View通过MeasureSpec对子View进行大小的要求,这也只是要求,而并不是完完全全的按照父View的MeasureSpec来设置。那子View的MeasureSpec如何确定呢?除了父View的MeasureSpec对子View的要求,还有我们自己对子View设置的LayoutParam,这两者共同作用,共同决定子View的MeasureSpec。
(MeasureSpec)
为了证实上面的说法,我们可以看看ViewGroup如何得到子View的MeasureSpec(注:这是方法之一)
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding.
* The heavy lifting is done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this 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);
}
很容易从上面的方法看出,子View的大小是由父View的MeasureSpec 和 子View的MeasureSpec共同作用的结果。
而其中getChildMeasureSpec()则是产生子ViewMeasureSpec的规则。
源码:
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;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
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.
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.
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 = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
上述代码很容易看出规则,我们总结一张表:
[3]
总结(由于UNSPECIFIED是用作系统的,所以用户可以不加考虑):
1) 只要子View的LayoutParam是精确值,不管父View是什么mode,得到的子View 的 MeasureSpec的 mode 都是 Exactly
2) 只要子View的LayoutParams是wrap_content,则得到的子View的MeasureSpec的mode是AT_MOST且大小不能超过parentSize。因此我们在子View的onMeasure()方法中要注意我们大小的设定,如果不自我调整的话,则默认为最大值(即parentSize)
3) 对于子View的LayoutParams是match_parent,则情况会有不同,子View的在onMeasure()方法中根据自己的View特性,稍加注意
4)对于得到的子View的MeasureSpec的mode是EXACTLY,除非你的子View很特别,否则子View的最终大小都要是得到的MeasureSpec中的size
3. 子View的大小确定
(注意:我们这次没有说是子View的MeasureSpec)
在子View的onMeasure()方法中,最终确定我们子View的长和宽。
可以根据子View的MeaSpe,子View的LayoutParams和子View本身的特性,共同决定我们子View的大小(即width和height)
4. 理解流程图
至此,感谢文章中参考的那些人们,如果有遗漏,请留言。如果有错误的地方多多指点
参考:
[1] https://developer.android.com/reference/android/view/View.MeasureSpec.html
[2] 任玉刚 《Android开发艺术探索》第4章 “View的工作原理” p178
[3] 任玉刚 《Android开发艺术探索》第4章 “View的工作原理” p182