View的绘制流程
概念
- View:View是所有UI组件的基类
- ViewGroup:UI组件的容器,本身也是View的子类
- ViewRootImpl:绘制View的辅助类
- MeasureSpec:描述View相关信息的一种属性
MeasureSpec
在measure
方法中,系统会根据父容器约束的信息将当前view的LayoutParams
封装成measureSpec,然后View的onMeasure
方法根据measureSpec值来计算当前View的宽高。
顶层的DecorView
调用getRootMeasureSpec
获取屏幕的宽和高作为MeasureSpec属性
MeasureSpec
是一个int
类型的属性,高2位表示SpecMode
(模式),低30位表示SpecSize
(尺寸)。(详见View.java中的静态类MeasureSpec)
MeasureSpec
提供封装和解包的方法封装:makeMeasureSpec()
解包:getMode() \ getSize()
MeasureSpec
的模式- AT_MOST:view的大小需要计算,对应于 wrap_content
- EXACTLY:父容器已经得知view的大小,对应于 match_parent 或者指定大小
- UNSPECIFIED:view的大小不受父容器的限制,随意大小
View的测量
流程
DecorView的测量
performTraversals
view的测量是从根节点View(此处我们假设是DecorView)开始逐层递归进行的。
/**
* ViewRootImpl.java
**/
private void performTraversals() {
...
//获取视窗大小
int childWidthMeasureSpec = getRootMeasureSpec (mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec (mHeight, lp.height);
//测量视图大小,从DecorView开始
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
/**
* 测量视图
**/
private void performMeasure(int childWidthMeasureSpec,
int childHeightMeasureSpec) {
if (mView == null) {
return;
}
...
//调用View.java 的measure方法
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
measure
View的measure
方法及逐级调用:
//View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
//如果含有PFLAG_FORCE_LAYOUT会强制刷新布局,reqeustLayout会加入此标记
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
...
//判断是否需要重新测量,如果宽高没有发生变化,则不需重新
if (forceLayout || needsLayout) {
//清除表示已经测量过的标记位
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
//从右向左解析布局,如果其他布局需要特殊处理
resolveRtlPropertiesIfNeeded();
//从缓存中读取信息
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
//缓存不可用,从此处开始测量视图尺寸,调用onMeasure方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
//缓存可用,从缓存中取值,不必重新测量
long value = mMeasureCache.valueAt(cacheIndex);
//直接保存尺寸信息
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// 自定义View没有调用setMeasuredDimension方法保存测量结果,会抛出异常
if ((mPrivateFlags &
PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
...
}
//测量尺寸并保存
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),
widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(),
heightMeasureSpec));
}
//如果View没有背景,返回View本身的最小宽度,如果设置了背景,取二者较大值
protected int getSuggestedMinimumHeight() {
return
(mBackground == null) ? mMinHeight : max(mMinHeight,
mBackground.getMinimumHeight()); }
//
public static int getDefaultSize(int size, int measureSpec) {
//view意图的尺寸
int result = size;
//获取SpecMode和SpecSize
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
//该模式不限制view尺寸,view尺寸可以随心所欲
case MeasureSpec.UNSPECIFIED:
result = size;
break;
//该两个模式下,view尺寸由父容器指定
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
//保存尺寸信息
protected final void setMeasuredDimension(int measuredWidth,
intmeasuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
//保存尺寸
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
//保存尺寸
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
//保存测量完的尺寸信息到view的成员变量中
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
//设置标识位为测量完毕状态
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
onMeasure
需要注意,DecoreView
的是FrameLayout
的子类,因此meaure方法调用的是FramenLayout
的onMeasure
方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//子View数量
int count = getChildCount ();
//宽或高是否匹配match_parent模式
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//逐个遍历可见的View
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//测量该View的大小
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
//获取该view的布局参数
final LayoutParams lp = (LayoutParams) child . getLayoutParams ();
//记录子View的最大高度和宽度
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());
//保存与父View相同宽或高的view
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT
) {
mMatchParentChildren.add(child);
}
}
}
}
//该View的宽高需要加上父View的padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
//保存测量结果
setMeasuredDimension(
resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(
maxHeight, heightMeasureSpec,
childState < < MEASURED_HEIGHT_STATE_SHIFT
)
);
//获取match_parent的view数量
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child . getLayoutParams ();
//遍历算的view的宽高
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
//view的宽是父view的宽 - 父padding - 子margin
final int width = Math.max(
0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin
);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY
);
} else {
childWidthMeasureSpec = getChildMeasureSpec(
widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width
);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(
0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin
);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY
);
} else {
childHeightMeasureSpec = getChildMeasureSpec(
heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height
);
}
//重新测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
measureChildWithMargins
measureChildWithMargins
方法:
parentWidthMeasureSpec和parentHeightMeasureSpec是父view对于子view的约束条件,即子view可获得的最大宽度和高度。widthUsed和heightUsed描述父view已经使用的宽度和高度。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec,
int widthUsed,
int parentHeightMeasureSpec,
int heightUsed) {
//获取布局参数
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//通过父View的WidthMeasureSpec计算子view的WidthMeasureSpec
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);
//测量子view大小
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
getChildMeasureSpec
测量子view的时候需要考虑子view的尺寸以及margin值还有padding值,这些参数通过调用getChildMeasureSpec
获得,这个方法把父view的measureSpec以及自身的layoutParams属性传递进去获取子view的measureSpec。
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 them 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的测量
measure
子view的measure
方法参考 @DecorView的measure
方法
onMeasure
不同的View对应的onMeasure
方法的实现有所差异,略。
https://www.wangt.cc/2021/01/深入理解android中view绘制三大流程及measurespec详解/
6666