文章目录
- 场景
- MeasureSpec
- SpecMode
- 与 LayoutParams 关系
- 总结
场景
当我们需要了解 View 的原理,我们肯定会碰到这么一个类 MeasureSpec,那么它是干嘛的呢,它有什么作用,在理解 View 原理时,可以跳过它么,它充当什么角色呢?
MeasureSpec
- 它是 View类中 的一个静态内部类
- MeasureSpec 可以理解成测量规范
- MeasureSpec 通过将 SpecMode 和 SpecSize
打包成 int 值,来减少本地对象。这个类还提供了打包和解包方法 - measureSpec 是一个32位 int 值,高2位代表 mode,低30位代表 size。mode 是测量模式,size 是测量模式下的规格大小。
看下源码中的重要方法(解包,打包):
public static class MeasureSpec {
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
}
SpecMode
- 三种模式,分别是:UNSPECIFIED , EXACTLY , AT_MOST
1、UNSPECIFIED
View 要多大有多大,不受父容器限制。一般用于系统内部
2、EXACTLY
View 的大小等于精确的数值
3、AT_MOST
父容器指定了一个可用的大小SpecSize,View 的大小不能大于这个值。
与 LayoutParams 关系
- View 的 measureSpec 值,由父容器和 View 的 LayoutParams 一起决定的
- DecorView 的 measureSpec 值 由窗体大小和 DecorView 的 LayoutParams 决定
- 普通 View 的 measureSpec 值 由父容器 MeasureSpec 和 View 的 LayoutParams 决定
一、看下 DecorView 怎么获取 measureSpec :
1、其中 desiredWindowWidth,desiredWindowHeight 是窗口宽和高的大小。lp.width ,lp.height 是 LayoutParams 的宽高参数,下面是
ViewRootImpl#measureHierarchy()源码:
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
...
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
return windowSizeMayChange;
}
2、获取 measureSpec
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;
}
根据以上代码可以看出,DecorView 根据它的 LayoutParams 中的宽高的参数来划分的,还有窗口大小,最终返回 measureSpec.
二、普通 view 的怎么获取 measureSpec:
1、 看下 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);
}
2、从上面中我们看到调用了getChildMeasureSpec 方法,所以,我们在看下
ViewGroup#getChildMeasureSpec 方法源码:
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 = 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;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
从中可以发现的,普通 View 的 measureSpec 由父容器和 View 的 LayoutParams 共同决定的。另外在1展示地代码中更有这么一句:child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
从这句代码中,我们也知道了, View 的测量是由 View 的 measureSpec 决定的。
总结
- MeasureSpec 是测量规范
- measureSpec 代表一个 32位 int值,高2位是 mode(测量模式),低30位是 size(测量模式下的大小)
- DecorView 的 measureSpec 由窗口大小和 DecorView 的 LayoutParams 共同决定
- 普遍 View 的 measureSpec 由父容器 MeasureSpec 和 View 的 LayoutParams 共同决定
- View 的测量是由 View 的 measureSpec 决定的
如果对你有一点点帮助,那是值得高兴的事情。:)
我的csdn:http://blog.csdn.net/shenshizhong
我的简书:http://www.jianshu.com/u/345daf0211ad