一、概述
测量过程分为 View的measure过程 和 ViewGroup的measure过程。
View的类型 | measure 过程 |
---|---|
单一的View (如 ImageView) | 只测量自身 |
ViewGroup | 遍历测量该 ViewGroup 下的所有子 View |
版本: Android SDK 29
关联文章:
- 《View系列 (一) — Android 坐标系》
- 《View系列 (二) — MeasureSpec 详解》
- 《View系列 (三) — Measure 流程详解》
- 《View系列 (四) — Layout 流程详解》
- 《View系列 (五) — Draw 流程详解》
二、单一 View 的测量流程
1. 流程图
2. 源码分析
ViewGroup.measureChildWithMargins()
// ViewGroup.class
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
// 1.子View的LayoutParams参数
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 2.根据父容器的MeasureSpec和子View的LayoutParams来确认子View的MeasureSpec。
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);
// 3.将子View的MeasureSpec传递给子View,如果子View的单一View,方便其测量自身。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
View.measure()
// View.class
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// ...省略代码...
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
// 这里执行单一View的onMeasure()方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// ...省略代码...
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
// ...省略代码...
// 赋值
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
// 将子View的大小赋值给下面两个字段
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
View.getSuggestedMinimumWidth()
// View.class
// 1.无背景,就返回mMinWidth,这个参数其实就是布局文件里的 android:minWidth 属性。
// 2.有背景,返回mMinWidth与背景宽度中的较大值。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
// Drawable.class
public int getMinimumWidth() {
// 如果背景有宽度,就返回宽度。
final int intrinsicWidth = getIntrinsicWidth(); // getIntrinsicWidth()默认返回-1
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
View.getDefaultSize()
// View.class
public static int getDefaultSize(int size, int measureSpec) {
//参数 measureSpec 是子View的MeasureSpec
int result = size;
// 获取子View的LauoutParams参数和父容器协调后的尺寸。
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED: //这里其实就是特殊处理一下UNSPECIFIED时,子View的大小。
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
三、ViewGroup 的测量流程
原理
- 遍历测量所有子View的尺寸大小。
- 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值。
1. 流程图
2. 源码分析
// View.class
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// ...省略代码...
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
// 这里执行单一View的onMeasure()方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// ...省略代码...
}
为什么ViewGroup 中没有重写 onMeasure() 方法,而是在各个继承自 ViewGroup 的容器控件中实现?
每个容器型控件的布局特性都不一样,所以无法对 onMeasure 进行统一处理。
在 ViewGroup 中,measureChildren() 方法
可以用来遍历测量子View。
// ViewGroup.class
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
// 遍历当前ViewGroup内部的所有子View
for (int i = 0; i < size; ++i) {
final View child = children[i];
// 子View显示模式不为GONE
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
// 测量子View
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
// 该方法与上面的 measureChildWithMargins()方法类似
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// 1.子View的LayoutParams参数
final LayoutParams lp = child.getLayoutParams();
// 2.根据父容器的MeasureSpec和子View的LayoutParams来确认子View的MeasureSpec。
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 3.将子View的MeasureSpec传递下去。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
关于 onMeasure() 方法,我们以 LinearLayout.onMeasure() 为例进行分析。
// LinearLayout.class
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
// 竖直方向排列时,子View的高度相加,宽度取最宽的值。
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0; // 该变量是保存该ViewGroup在竖直方向的总高度
int maxWidth = 0; //记录最大宽度
int childState = 0;
float totalWeight = 0;
// 获取垂直方向上的子View个数
final int count = getVirtualChildCount();
// ...省略代码...
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean skippedMeasure = false;
// See how tall everyone is. Also remember max width.
// 遍历子View并获取其宽/高,并记录下子View中最大的宽度值
for (int i = 0; i < count; ++i) {
// ...省略代码... View为null和可见性为GONE的View
// ...省略添加分割线高度的逻辑
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 记录子View是否有weight属性设置,用于后面判断是否需要二次measure
totalWeight += lp.weight;
// 是否开启权重
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
/*
* 如果LinearLayout的specMode为EXACTLY且子View设置了weight属性,在这里会跳过子View的measure过程,
* 同时标记skippedMeasure属性为true,后面会根据该属性决定是否进行第二次measure。
* 若LinearLayout的子View设置了weight,会进行两次measure计算,比较耗时。
* 这就是为什么LinearLayout的子View需要使用weight属性时候,最好替换成RelativeLayout布局。
*/
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
// ...省略代码...
// 已用的高度值
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
// 遍历当前child内的子View,并进行测量
measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight);
// 获取当前子View的高度
final int childHeight = child.getMeasuredHeight();
// ...省略代码...
// mTotalLength用于存储LinearLayout在竖直方向的高度
final int totalLength = mTotalLength;
// 将子View测量的高度添加到mTotalLength
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
// ...省略代码...
}
// ...省略代码...
// 获取当前ViewGroup控件中,宽度最大值(子控件左右外边距 + 子控件宽度)
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
childState = combineMeasuredStates(childState, child.getMeasuredState());
// ...省略代码...
}
// ...省略代码...
// Add in our padding
// 高度值还包括LinearLayout自身的上下内边距
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
// 从最小高度值和当前LinearLayout高度值中取较大值。
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
// ...省略代码-权重导致的重新测量...
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
// 宽度值还包括LinearLayout自身的左右内边距
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// 将计算子View后得到的宽高值赋值给当前的LinearLayout
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState);
// ...省略代码...
}
到此,View的绘制流程就分析完了。