一. 前言
1. 作用
Measure过程用来测量View的宽度和高度。
2. 注意
① 在某些情况下,需要多次测量Measure才能确定View的最终宽高
② 因此,在上述情况下,Measure过程后得到的宽高不准确。
③ 因此,建议在Layout过程中onLayout去获取最终的宽高。
二. 储备知识
1. ViewGroup.LayoutParms类
① 作用
布局参数类,指定View的宽度width和高度height等布局参数。
② 具体使用
参数 |
解释 |
match_parent |
强制性使子视图的大小扩展至与父视图大小相等,不含padding |
wrap_content |
自适应大小,强制性使视图扩展以便显示其全部内容,含padding |
具体的值 |
dp/px |
2. MeasureSpecs类
① 简介
测量规格类,测量View大小的依据。每个MeasureSpecs代表了一组宽高的测量规格。
宽测量规格 widthMeasureSpecs 和 高测量规格 heightMeasureSpecs。
② 组成
测量规格MeasureSpecs = 测量模式Mode + 测量大小Size
③ Mode的类型
模式 |
具体描述 |
UNSPECIFIED |
父视图不约束子视图View,如ListView,ScrollView等,一般不常用 |
EXACTLY |
父视图为子视图指定一个确切的尺寸,子视图必须在指定的尺寸内。如使用具体数值或者match_parent。 |
AT_MOST |
父视图为子视图指定一个最大尺寸,子视图必须确保自身以及所有子视图可以适应在该尺寸内。 |
④ 实际使用
1 2 3 4 5 6 7 8 9 10 11 12
|
int specMode = MeasureSpec.getMode(measureSpec)
int specSize = MeasureSpec.getSize(measureSpec)
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
|
三. MeasureSpec值的测量
子View的MeasureSpec
值根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来的。具体计算逻辑封装在getChildMeasureSpec
里。
即:子view
的大小由父view
的MeasureSpec
值 和 子view
的LayoutParams
属性 共同决定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
|
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
* @param spec 父view的详细测量值(MeasureSpec) * @param padding view当前尺寸的的内边距和外边距(padding,margin) * @param 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) { case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; case MeasureSpec.AT_MOST: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
|
结论如下:

规律总结:

四. Measure过程详解
1. 单一View的Measure过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { onMeasure(widthMeasureSpec, heightMeasureSpec); } else { ... } }
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
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; }
|
由于UNSPECIFIED
模式适用于系统内部多次measure
情况,很少用到,所以暂时不讨论UNSPECIFIED
模式。
总结过程:
measure -> onMeasure -> setMeasuredDimension + getDefaultSize
2. 多个View,ViewGroup的Measure过程
① 原理
遍历 测量所有子View的尺寸,合并所有子View的尺寸,最终得到ViewGroup父视图的测量值。
② 过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { ... }
|
为什么ViewGroup
的measure
过程不像单一View
的measure
过程那样对onMeasure()
做统一的实现?
- 因为不同的
ViewGroup
子类(LinearLayout
、RelativeLayout
/ 自定义ViewGroup
子类等)具备不同的布局特性,这导致他们子View
的测量方法各有不同。
- 因此,ViewGroup无法对onMeasure()作统一实现。这个也是单一View的measure过程与ViewGroup过程最大的不同。
- 即 单一
View measure
过程的onMeasure()
具有统一实现,而ViewGroup
则没有。
- 其实,在单一
View measure
过程中,getDefaultSize()
只是简单的测量了宽高值,在实际使用时有时需更精细的测量。所以有时候也需重写onMeasure()
。
在自定义ViewGroup
中,关键在于:根据需求复写onMeasure()
从而实现你的子View测量逻辑。复写onMeasure()
的套路如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
|
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMeasure ; int heightMeasure ;
measureChildren(widthMeasureSpec, heightMeasureSpec);
void measureCarson{ ... }
setMeasuredDimension(widthMeasure, heightMeasure); }
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren;
for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
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 Measure过程 - 最易懂的自定义View原理系列(2)