一 什么是MeasureSpec
我们在上篇文章中已经了解到了UI绘制最终会走到ViewRootImpl的performTraversals()这个方法:
private void performTraversals() {
//mWidth:屏幕的宽度 lp.width:DecorView的layoutParam里的宽度模式(match_parent wrap_parent)
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//测量,布局,绘制三个方法入口
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}
可以看到,在执行测量的时候,首先会生成childWidthMeasureSpec ,childHeightMeasureSpec 这两个参数,这其实就是DecorView的测量规则,这里解释一下,所谓测量规则,其实就是父View对子View的一种限制,在测量子View的宽度和高度的时候,不能只看我们设置它的本身的大小,还要综合父View给的测量规则综合的得到子View在屏幕上的具体大小;我们知道android的界面顶层的View其实就是DecorView,而我们在加载界面的时候,自然要先得到它的测量规则才能继续测量
我们来看一下getRootMeasureSpec()
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的宽高模式来得到具体的MeasureSpec,最终返回一个int类型的值;其实MeasureSpec类似一个工具类,会根据传入的具体参数得到一个int数据类型,里面封装了测量模式和具体的size,接下来我们具体来看一下MeasureSpec这个类。
二 MeasureSpec的源码解析
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
// 0x3的二进制: 11
//0x3 << MODE_SHIFT: 11 000000 00000000 00000000 00000000
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
// UNSPECIFIED:00 000000 00000000 00000000 00000000
public static final int UNSPECIFIED = 0 << MODE_SHIFT; //1
// EXACTLY:01 000000 00000000 00000000 00000000
public static final int EXACTLY = 1 << MODE_SHIFT;//2
// AT_MOST:10 000000 00000000 00000000 00000000
public static final int AT_MOST = 2 << MODE_SHIFT;//3
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
/*
* 假设传入的size是1080
* 1080的二进制:00000000 000000000 00000100 00111000
*
* MODE_MASK: 11 000000 00000000 00000000 00000000
* ~MODE_MASK:00 111111 11111111 11111111 11111111
*
*
* size & ~MODE_MASK:
* 00000000 00000000 00000100 00111000
* &
* 00111111 11111111 11111111 11111111
* --------------------------------------
* 00000000 00000000 00000100 00111000 //其实跟size值一样
*
*
*
* mode & MODE_MASK:
* 假设 mode 是 EXACTLY
* EXACTLY: 01000000 00000000 00000000 00000000
* &
* MODE_MASK: 11000000 00000000 00000000 00000000
*---------------------------------------------------------
* mode & MODE_MASK 01000000 00000000 00000000 00000000
*
*
*
* */
return (size & ~MODE_MASK) | (mode & MODE_MASK);
/*
* (size & ~MODE_MASK) | (mode & MODE_MASK)
*
* size & ~MODE_MASK : 00000000 00000000 00000100 00111000
* |
* mode & MODE_MASK : 01000000 00000000 00000000 00000000
* --------------------------------------------------------------
* : 01000000 00000000 00000100 00111000
* */
}
}
MeasureSpec定义了三个变量,代表的是测量模式,就是父View对子View的一个限制
- UNSPECIFIED:
不确定的,这个在Android开发中很少用
- EXACTLY :
精确值,子View的大小可以是具体的数值,也可以是match_parent,因为在这个精确模式下,父view的大小是确定的,所以子view直接是等于父view的代销
- AT_MOST :
子view可以是任意值,但是前提是不能超过父view的大小,对应的子view的大小模式是wrap_parent,所以这个模式也叫最大值模式
可以看到,这三个值都通过了位运算,将对应的二进制位左移到了最左端(int数据类型是32位,也就是32和31位);接着我们来看一下makeMeasureSpec()这个方法,这个方法本质上就是将我们上面说的测量模式和传入的父view大小封装到一个int数据类型的32位二进制中,具体的运算上面写得很清楚了。
OK,现在通过了makeMeasureSpec()这个方法,我们将传入的DecorView的大小和模式封装成一个32位的int数据类型,那么我们又要如何获取size或者测量模式呢,MeasureSpec也提供了两个方法给我们,分别是getMode()和 getSize()
public static int getMode(int measureSpec) {
//noinspection ResourceType
/*
*
*
* measureSpec : 01000000 00000000 00000100 00111000
* &
* MODE_MASK : 11000000 00000000 00000000 00000000
* ---------------------------------------------------------
* Mode : 01000000 00000000 00000000 00000000
* EXACTLY : 01000000 00000000 00000000 00000000
* */
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
/*
*
*
* measureSpec : 01000000 00000000 00000100 00111000
* &
* ~MODE_MASK : 00111111 11111111 11111111 11111111
* ---------------------------------------------------------
* Size : 00000000 00000000 00000100 00111000 = 1080
* 1080 : 00000000 00000000 00000100 00111000
* */
return (measureSpec & ~MODE_MASK);
}
很显然,将测量规则和MODE_MASK进行与运算,就得到了测量模式,跟~MODE_MASK进行与运算就得到了大小
三 得到MeasureSpec如何使用
根据源码,得到宽和高的MeasureSpec之后,会将此参数传入到performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);这个方法中:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
在performMeasure方法中最终会调用mView的measure,这个mView其实就是DecorView,但是DecorView并没有measure方法,追踪代码可以知道,DecorView其实是继承FrameLayout,而FrameLayout最终还是继承了View,所以这个measure方法其实还是调用的是VIew 的Measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// Suppress sign extension for the low bytes
//将宽和高的MeasureSpec这两个32位的int数据类型存入到一个long的数据类型里
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
//创建一个长度为2的SparseArray,用来存储测量的值
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
//如果SparseArray没有测量的值就调用onMeasure方法进行测量
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
//如果SparseArray有测量的值就直接赋值
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;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
measure方法里也简单,只要做了三件事情
1.对宽和高的测量规则进行封装,作为key储存到SparryArray里去
2.创建SparryArray
3.对SparryArray进行判断,有值则直接将里面的值赋值,没有则调用onMeasure()方法进行测量
四 最终开始测量DecorView(onMeasure)
DecorView的onMeasure()方法只是做了一些边界值得判断,最终还是会调用它的父类(FrameLayout)的onMeasure()方法,我们主要来看一下FrameLayout的
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取DecorView的所有子View的数量
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;
//这里遍历所有的子View,并且测量,
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//这里是只要得到子View的 Margins值,然后把Margins值传入子VIew中去测量
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);
}
}
}
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
}
View.measureChildWithMargins()
```java
```java
```java
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//这里获取子View的MarginLayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//得到子View的测量模式,其中子View的size包括了padding和Margin以级自身的大小
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);
}
View.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);
}
这个方法就不用多说了,很简单,就是通过DecorView的MeasureSpec来确定这个子View的具体的size,同时根据子View的自身尺寸模式(match_parent,wrap_parent)来确定子View自己的MeasureSpec,接着就是调用makeMeasureSpec封装成MeasureSpec了。如果这个子View还有是ViewGrounp类型的话,会继续调用这个子View 的Measure()方法,这样过程其实还是和FrameLayout一样的,只是不同的ViewGrounp它尺寸和布局的算法不同而已,流程都是一样的。
Ok,假设我们通过for循环把所有的子View都测量完毕了,此时就会调用setMeasuredDimension()方法把最终测量的值赋值,此时View树就测量完毕了,performMeasure这行完毕,接下来就是布局和绘画了。
我专门写了关于测量布局绘画的源码分析,大家可以点击这里继续学习布局和绘画的源码