初识 ViewRoot 和 DecorView
ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View 的绘制流程通过 ViewRoot 来完成,在 ActivityThread 中,Activity 创建完毕后,会将 DecorView 添加到 Window中,同时会创建 ViewRootImpl 对象,并和 DecorView 建立关联。
View 的绘制流程是从 ViewRoot 的 performTraversals 方法开始,它经过 measure 用来测量 View 的宽和高,layout 来确定 View 在父容器中的放置位置,而 draw 则负责将 View 绘制在屏幕上。
理解 MeasureSpec
- MeasureSpec 代表一个32位 int 值,高 2 位代表 SpecMode 测量模式,低 30 位代表 SpecSize 某种测试模式下的规格大小。
- SpecMode 有三类
- UNSPECIFIED 父容器不对 View 有任何限制,要多大给多大,一般用于系统内部,表示一种测量状态。
- EXACTLY 父容器检测出 View 的精确大小,大小是 SpecSize 指定的值,对应于 LayoutParams 中的 match_parent 和具体数值两种模式。
- AT_MOST 父容器指定一个可用大小即 SpecSize ,View 的大小不能大于这个值,对应于LayoutParams 中的 wrap_content。
- 对于顶级 View(DecorView),其MeasureSpec由窗口尺寸和自身LayoutParams共同决定。
- 对于普通 View ,其MeasureSpec有父容器的 MeasureSpec 和自身的LayoutParams 共同决定。
普通 View 的 MeasureSpec 的创建规则
LayoutParams\parentSpecMode | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
dp/px | EXACTLY(childSize) | EXACTLY(childSize) | EXACTLY(childSize) |
match_parent | EXACTLY(parentSize) | AT_MOST(parentSize) | UNSPECIFIED(0) |
wrap_content | AT_MOST(parentSize) | AT_MOST(parentSize) | UNSPECIFIED(0) |
总结
1. 当 View 采用固定宽高的时候,不管父容器的 SpecMode 是什么,View 的MeasureSpec 都是精确模式,大小遵循LayoutParams中的大小。
2. 当 View 宽高是 match_parent 时,如果父容器 SpecMode 是精确模式,那么 View 也是精确模式,大小是父容器的剩余的空间大小。如果父容器 SepcMode 是最大模式,那么 View 也是最大模式,大小不会超过父容器的剩余空间。
3. 当 View 宽高wrap_content 时,不管父容器的 SpecMode 是什么,View 的MeasureSpec 都是最大模式,大小不能超过父容器的剩余空间。
View 的测量过程
- View 的 measure 的过程由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);
}
- 调用子元素的 measure 方法之前会通过 getChildMeasureSpec 方法得到子元素的MeasureSpec,与父容器的 MeasureSpec 和自身的 LayoutParams 有关。
- ViewGroup 中的 child.measure 方法,是调用的 View 的 measure 方法,再调用 onMeasure 方法进行测量。
View 的工作流程
- View 的 measure 过程是由 measure 方法完成,是一个 final 类型的方法,不能有子类重写,measure 方法中会调用 onMeasure 方法,方法如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
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;
}
只需要看 SpecMode 的 EXACTLY 和 AT_MOST 两种情况,返回的大小是SpecSize ,是 View 测量后的大小。
getSuggestedMinimumWidth 方法,如果 View 没有设置背景,那么用 minWith 属性所指定的值,如果有背景,取两者之间的最大值。
- 直接继承 View 的自定义控件,需要重写 onMeasure 方法,并设置 wrap_content 时的大小,否则相当于使用了match_parent,因为使用 wrap_content 时 SpecMode 是AT_MOST,根据查表这种情况下的 SpecSize 是 parentSize ,解决方式如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int withSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth,mHeight);
} else if (widthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth,heightSize);
} else if (heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(withSize,mHeight);
}
}