【参考链接】
我们在写布局文件的时候,是在设置LayoutParams,共有3种模式:
match_parent、wrap_content、dimens
这3种模式表达了我们希望给自己的View设置的宽度高度
但是,系统在进行测量measure()的时候,传进来的数据类型并不是LayoutParams,而是MeasureSpec。
1. 这个MeasureSpec是什么
MeasureSpec是一个32位的int值,其中高2位代表Mode,低30位代表Size。Mode主要有3类:EXACTLY,AT_MOST、UNSPECIFIED。可以通过如下方式解析一个MeasureSpec。
int
widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize =MeasureSpec.getSize(widthMeasureSpec);
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
int heightSize=MeasureSpec.getSize(heightMeasureSpec);
//
//可以通过如下方式来创建一个MeasureSpec
// int spec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
2. MeasureSpec是怎么来的
对于DecorView
MeasureSpec的mode是EXACTLY,size是窗口宽高
\frameworks\base\core\java\android\view\ViewRoot.java
private int
getRootMeasureSpec(int
windowSize, int
rootDimension) {
int
measureSpec;
switch (rootDimension) {
case
ViewGroup.LayoutParams.MATCH_PARENT:
// Window can'tresize. Force root view to be windowSize.
measureSpec =MeasureSpec.makeMeasureSpec(windowSize,
MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window canresize. Set max size for root view.
measureSpec =MeasureSpec.makeMeasureSpec(windowSize,
MeasureSpec.AT_MOST);
break;
default:
// Window wants tobe an exact size. Force root view to be that size.
measureSpec =MeasureSpec.makeMeasureSpec(rootDimension,
MeasureSpec.EXACTLY);
break;
}
return
measureSpec;
}
对于子View
系统把我们在布局文件中设置的LayoutParams和父View的MeasureSpec进行计算,生成新的MeasureSpec,传递给自己的measure()方法
\frameworks\base\core\java\android\view\ViewGroup.java
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 onus
case
MeasureSpec.EXACTLY:
if
(childDimension >=
0) {
resultSize = childDimension;
resultMode =MeasureSpec.EXACTLY;
}
else if
(childDimension ==LayoutParams.MATCH_PARENT) {
// Child wants tobe our size. So be it.
resultSize = size;
resultMode =MeasureSpec.EXACTLY;
}
else if
(childDimension ==LayoutParams.WRAP_CONTENT) {
// Child wants todetermine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode =MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size onus
case
MeasureSpec.AT_MOST:
if
(childDimension >=
0) {
// Child wants aspecific size... so be it
resultSize =childDimension;
resultMode =MeasureSpec.EXACTLY;
}
else if
(childDimension ==LayoutParams.MATCH_PARENT) {
// Child wants tobe 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 ownsize. It can't be
// bigger than us.
resultSize = size;
resultMode =MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we wantto be
case
MeasureSpec.UNSPECIFIED:
if
(childDimension >=
0) {
// Child wants aspecific size... let him have it
resultSize =childDimension;
resultMode =MeasureSpec.EXACTLY;
}
else if
(childDimension == LayoutParams.MATCH_PARENT){
// Child wants tobe our size... find out how big it should
// be
resultSize =
0;
resultMode =MeasureSpec.UNSPECIFIED;
}
else if
(childDimension ==LayoutParams.WRAP_CONTENT) {
// Child wants todetermine its own size.... find out how
// big it should be
resultSize =
0;
resultMode =MeasureSpec.UNSPECIFIED;
}
break;
}
return
MeasureSpec.makeMeasureSpec(resultSize,
resultMode);
}
转换关系可以用下表表示
|
|
match_parent |
wrap_content |
childDimen |
|
AT_MOST parentSize |
AT_MOST parentSize |
AT_MOST parentSize |
EXACTLY childDimen |
|
EXACTLY parentSize |
EXACTLY parentSize |
AT_MOST parentSize |
EXACTLY childDimen |
3. 所以系统并没有直接采用我们的LayoutParams,而是转换成了MeasureSpec,这个转换造成了什么影响呢?
从上面DecorView可以知道,最上面的MeasureSpec的mode是EXACTLY,所以,在MeasureSpec向下传递的过程中
如果下面的View的LayoutParams是match_parent,那么其MeasureSpec的mode就会保持为EXACTLY,并且size为parentSize。
|
|
match_parent |
wrap_content |
childDimen |
|
AT_MOST parentSize |
AT_MOST parentSize |
AT_MOST parentSize |
EXACTLY childDimen |
|
EXACTLY parentSize |
EXACTLY parentSize |
AT_MOST parentSize |
EXACTLY childDimen |
但是,如果某个View的LayoutParams是wrap_content,则其MeasureSpec的mode就会变成AT_MOST。并且即使下下面的View的LayoutParams是match_parent,
其MeasureSpec也只能是AT_MOST了。
|
|
match_parent |
wrap_content |
childDimen |
|
AT_MOST parentSize |
AT_MOST parentSize |
AT_MOST parentSize |
EXACTLY childDimen |
|
EXACTLY parentSize |
EXACTLY parentSize |
AT_MOST parentSize |
EXACTLY childDimen |
|
|
match_parent |
wrap_content |
childDimen |
|
AT_MOST parentSize |
AT_MOST parentSize |
AT_MOST parentSize |
EXACTLY childDimen |
|
EXACTLY parentSize |
EXACTLY parentSize |
AT_MOST parentSize |
EXACTLY childDimen |
所以,虽然LayoutParams表达了我们的期望,但是系统并没有完全满足我们的期望,还是使用转换成的MeasureSpec来测量宽高。
4. 那么我们要如何使用这个MeasureSpec呢?
从上表可知,
如果LayoutParams是match_parent,则MeasureSpec的size就是parentSize。如果LayoutParams是childDimen,则MeasureSpec的size就是ChildDimen。这两个值是符合跟父View一样大小/指定大小的语义的。
并且此时MeasureSpec的mode都是EXACTLY,所以当MeasureSpec的mode是EXACTLY时,我们可以直接使用MeasureSpec的size作为measuredDimension。
而当LayoutParams是wrap_content时,MeasureSpec的mode是AT_MOST,并且value是parentSize。这个时候我们可以根据自己的测量/布局想法,计算出长宽,设置成measuredDimension(实际是取跟parentSize比较的较小值)。
总结一下就是,在onMeasure()中
如果MeasureSpec的mode是EXACTLY,则可以直接使用MeasureSpec的size作为measuredDimension,否则再根据自己的需要计算长宽。
resolveSize()
按照上面的思路,在自定义View的时候,我们只需要计算AT_MOST时的长宽
所以系统为我们提供了resolveSize()方法,将AT_MOST时的长宽传进去就可以得到所有情况时的长宽,这可以方便我们少些很多代码。
protected void
onMeasure(int
widthMeasureSpec, int
heightMeasureSpec) {
//
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
//假设AT_MOST时的长宽想要为300,300//实际需要根据自己的测量/布局规则来计算
setMeasuredDimension(
resolveSize(300,
widthMeasureSpec),
resolveSize(300,
heightMeasureSpec)
);
}
\frameworks\base\core\java\android\view\View.java
public static int
resolveSize(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://将计算大小和parentSize进行比较,取较小值
result =Math.min(size,
specSize);
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return
result;
}
measureChildren()
在自定义ViewGroup的时候,重写onMeasure()时,还需要先对子View进行measure。系统也为ViewGroup提供了measureChildren()等方法。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec)
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)
此外,并不是在performTraversal()中measure()就只执行一次
都是看各自的实现的,比如说LinearLayout的measure()就会进行两次。