ViewRoot对应与ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRootImpl来完成的。
在ActivityThread中,当Activity对象被创建完毕后,会将DecoreView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecoreView关联。源码如下:
root = new ViewRootImpl(view.getContext(),display);
root.setView(view,wparms,panelParentView);
View的绘制流程是从ViewRoot的performTraversal方法开始的,它经过measure,layout和draw三个过程才能将一个View绘制出来。 其中measure 用来测量View的宽和高,layout用来确定View在父容器中的放置位置,而draw则负责将View绘制在屏幕上,针对perforTraversal的大致流程,如下图:
performTraversal会依次调用performMeasure、performLayout和performDraw三个方法,这三个方法分别完成顶级View的measure、layout和draw这三大流程,其中performMeasure会调用measure方法,在measure方法又会调用onMeasure方法,在onMeasure方法中则会对所有子元素进行measure过程,这个时候measure流程就从父容器传递带子元素中,这样就完成了一次measure过程。 接着子元素会重复父容器的measure过程,如此反复就完成了整个View树的遍历。 同理performLayout和performDraw的传递流程和performMeasure类似。 唯一不同的是,performDraw的过程在draw方法中通过dispathDraw来实现。
DecorView为顶级View,其实是一个FrameLayout。 一般情况下它内部包含一个竖直方向的LinearLayout,在这个LinearLayout里有上下文两部分(具体情况和Android版本及主题有关),上面是标题栏,下面是内容栏。
在Activity中通过setContentView所设置的布局就是被加到内容栏中,而内容栏的id为content。因此我们可以ViewGroup content = findViewById(R.id.content).
MeasureSpec 代表一个32位int数值,高2位代表SpecMode测量模式,低30位代表SpecSize某种测量模式下的规格大小。
先看看它的源码:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT; //11000000000000000000000000000000
public static final int UNSPECIFIED = 0 << MODE_SHIFT;//00000000000000000000000000000000
public static final int EXACTLY = 1 << MODE_SHIFT;//01000000000000000000000000000000
public static final int AT_MOST = 2 << MODE_SHIFT;//10000000000000000000000000000000
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK); //把size填充到后30位,mode填充到前2位
}
}
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
...
...
}
可以看到定义的MODE_MASK=0x3 << 30 频繁地被使用。 那它为什么是 0x3 再左移30位呐?为什么不是0x4再左移40位呐?这肯定有它使用道理的,而不是说Google觉得3很吉利就选它。
首先 0x3 用二进制表示就是 11,左移30位后就是11000000000000000000000000000000。为什么是30位呐?因为Int是32位的,11左右30位后就是刚好32位了。
好了,了解了这个之后。那问题来了,它又有什么用呐?二进制肯定要看运算符运算嘛。
0 & 0 = 0
0 | 0 = 0
0 & 1 = 0
0 | 1 = 1
1 & 1 = 1
1 | 1 = 1
~ 0 = 1
~ 1 = 0
也就是任何值 & 1 或者 ~ 0 都是原来的值。
前面为什么说MeasureSpec 代表一个32位int数值,高2位代表SpecMode测量模式,低30位代表SpecSize呐?
因为MODE_MASK 的30位为 0 , 前2位为1 。那么只要拿MeasureSpec & MODE_ MASK就可以拿到MeasureSpec的前2位的值,也就是 代表的mode的值。同理拿
MeasureSpec & ~MODE_ MASK就可以拿到MeasureSpec的后30位的值,也就是size。
如:MeasureSpec的值为 100000000000000000001111011000。
getSize : 100000000000000000001111011000 & ~ 11000000000000000000000000000000 = 00000000000000000000001111011000 (等于十进制值 1080)
getMode : 100000000000000000001111011000 & 11000000000000000000000000000000 = 10000000000000000000000000000000 (等于AT_MOST)
模式 | 二进制数值 | 描述 |
---|---|---|
UNSPCIFIED | 00 | 默认值,父控件没有给子View任何限制,子View可以设置为任意大小。 这种情况不多。 (不确定值) |
EXCATLY | 01 | 表示父控件已经确切的指定了子View的大小。(完全确定值) |
AT_MOST | 10 | 表示子View具体没有尺寸限制,但是存在上限,上限一般为父View的大小。 当控件的宽高指定为wrap_content时为这种模式。 (最大值) |
以数值1080(二进制位1111011000)为例(其中模式和实际数值是连载一起的,为了展示将他们分开了):
模式名称 | 模式数值 | 实际数值 |
---|---|---|
UNSPECIFIED | 00 | 000000000000000000001111011000 |
EXCATLY | 01 | 010000000000000000001111011000 |
AT_MOST | 10 | 100000000000000000001111011000 |
对于顶级iView和普通View来说,MeasureSpec的转换过程略有不同。
DecoreView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定的。
普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定的。
MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。
对于普通的View的measure过程由ViewFroup传递而来,从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); // 获取子View的宽MeasureSpec
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
从上面获取子View宽的MeasurePec的方法getChildMeasureSpec()可以看到 子View的MeasureSpec的创建与父容器的MeasureSpec和子View自身的LayoutParams有关。
再具体看下ViewGroup的getChildMeasureSpec(…)方法:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //父View的测量模式
int specSize = MeasureSpec.getSize(spec); //父View的测量尺寸
int size = Math.max(0, specSize - padding); // 子View可设置的最大尺寸(父View尺寸减去内边距和子View的margin)
int resultSize = 0;
int resultMode = 0;
// 注意 : LayoutParams.MATCH_PARENT = -1, LayoutParams.WRAP_CONTENT = -2。childDimension小于0时为这两种情况
switch (specMode) {
case MeasureSpec.EXACTLY: // 父View测量模式为 : 精准模式
if (childDimension >= 0) { //子View的尺寸为具体值
resultSize = childDimension; //子View最终尺寸即为原本的指定尺寸
resultMode = MeasureSpec.EXACTLY; //子View的测量模式为精准模式
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;//父View为具体值了,子View为MatchParent,所以子View尺寸也确定具体值了
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST: //父View为最大模式,如MatchParent或者WrapContent
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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);//把测量尺寸和模式结合生成MeasureSpec
}
View的MeasureSpec的创建规则总结如下:
简单的说就是:
如果只是一个原始的View,那么通过measure过程就完成了测量过程;
如果是一个ViewGroup,除了完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归执行这个过程。
View的measure过程由其measure方法来完成,measure方法是一个final类型的方法。因此不能重新该方法,在View的measure过程会调用View的onMeasure方法。因此只需要看onMeasure的实现即可。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasureDimensiion方法会设置View宽/高的测量值。 我们只需要getDefaultSize这个方法即可:
/**
* size为上面传入的getSuggestedMinimumWidth()
*/
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;
}
对于我们来说,我们只需看AT_MOST和XACTLY这两种情况。
至于UNSPECIFIED这种情况,一般用于系统内部的测量过程。这种情况 下View的大小为getDefaultSize()的第一个参数,即getSuggestedMinnimumWidth和即getSuggestedMinnimumHeight这两个方法的返回值。看一下源码:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
看代码可知如果View没有设置背景,那么View的宽度为通过android:minWidth这个属性所指定的mMinWidth,如果这个属性不指定,则mMinWidth默认为0;如果View指定了背景,则View的宽度为max(mMinWidth, mBackground.getMinimumWidth())。那么mBackground.getMinimumWidth())是什么呢?该方法在Drawable类中:
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
可以看出,getMinimumWidth返回的就是Drawable的原始宽度,否则返回0 。 如ShapeDrawable原始宽度为0,BitmapDrawable有原始宽度(图片的尺寸)。
getSuggestedMinimumWidth和getSuggestedMinimumHeight的返回值就是View在UNSPECIFIED情况下的测量宽/高。
现在问题来了。那么平时我们自定义View时为什么要从写onMeasure()方法呐?
从第三部分MeasureSpec的讲解getChildMeasureSpec(…)可以看到,如果View在布局使用的是wrap_content,那么它的specMode是AT_MOST模式,在这种模式下,它的宽高等于specSize,也就是parentSize,相当于matchParent。 也就是说继承View的自定义控件需要自定义处理wrapContent的情况:
int mWidth = 50;
int mHeight = 100;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, mHeight);
}
}
在上面代码中,我们只需给View指定一个默认的内部宽/高(mWidth/mHeight),并在wrap_content是设置宽/高即可。对于非wrap_content情形,我们沿用系统的测量值即可。
对于ViewGroup来说,除了完成自己的measure过程以外,还会遍历调用所有子元素的measure方法,各个子元素在递归去执行这个过程。 和View不同的是,ViewGroup是一个抽象类,因此它没有重写View的onMeasure
方法,但是它提供了一个叫measureChildren的方法,如下:
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);
}
那为什么ViewGroup并没有定义其测量的具体过程?
因为ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类去具体实现。比如LinearLayout,RelativeLayout等。
1、是否可以在onCreate或者onResume里面去获取这个View的宽/高?
答: 不行。因为View的测量measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate、onStart、onResume时某个View已经测量完毕。如果没有,则获得的宽/高为0.那解决方法呢?
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus){
int width = view.getMeasuredWidth();
int height = view.getMinimumHeight();
}
}
@Override
protected void onStart() {
super.onStart();
ViewTreeObserver observer = customView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
customView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int width = customView.getMeasuredWidth();
int height = customView.getMeasuredHeight();
}
});
}
2、重写measure时,一定要调用setMeasuredDimension()。
在说layout之前,先说下View的坐标体系:
可以看到通过 getLeft和getTop等可以获取到View在父容器内的相对位置。查源码看:
@ViewDebug.CapturedViewProperty
public final int getLeft() {
return mLeft;
}
可以看到返回的是一个mLeft,那这个mLeft是怎么来的呢? 这就是View的layout的作用。 通过layout()方法确定了View的mLeft,mTop,mRight,mBottom。也就是layout确定了View在父类的位置。
//该方法会在父容器调用(也就是说这几个参数是有父容器确定的)
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
... ...
}
}
... ...
}
//通过该方法给mLeft等赋值,确定
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
... ...
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
... ...
}
return changed;
}
//因为单一View是没有子View的,所以空实现
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
ViewGroup不仅会通过layout()过程计算自身的位置,还会遍历子View并通过onLayout()方法确定子View在自身中的位置。 我们实现ViewGroup时需要重写onLayout方法去自定义控件的子控件放置规则。
以下为FrameLayout的onLayout方法:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) { //遍历子View
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);//调用子View的layout()方法
}
}
}
View的draw过程是将View绘制到屏幕上面,View的绘制过程遵循以下几步:
对于单一的View来说,其dispathDraw()方法为空实现,而ViewGroup的dispatch方法内部为遍历子View并调用其draw(canvas)的过程。
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas); //绘制背景
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas); //绘制自己
// Step 4, draw the children
dispatchDraw(canvas); //绘制子View
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas); //绘制装饰
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
...
}
@CallSuper
public void dispatchDraw(Canvas canvas) {
......
for (int i = 0; i < childrenCount; i++) {
......
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime); // 绘制子View
}
}
......
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime); // 调用了子View的draw()方法
}
以下为draw过程: