ViewTree的遍历过程performTraversals。
遍历过程主要对应三个函数performMeasure(计算大小),perforLayout(计算位置),performDraw(绘制内容)。具体看下这三个步骤执行的条件,及怎么执行的?
1)performMeasure
private void performTraversals() @ViewRootImpl.java{
//mView是ViewTree的根。
final View host = mView;
// mWinFrame是WMS计算后的结果,通过mWindowSession.relayout(...mWinFrame...)传递回来的。
Rect frame = mWinFrame;
//mFirst是不是第一次执行performTraversal。
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
}else{
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
//当前的宽高跟期望值不一致。
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;
}
}
// measureHierarchy是第一次可能会调用performMeasure的地方。mStopped表示窗口的拥有者Activity处于stopped状态,所以这个窗口就不再是active的。
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
}
//是否执行performMeasure,前面的layoutRequested是其中一部分条件,measureHierarchy方法中还有一些判断。
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
boolean goodMeasure = false;
//View请求的宽是 WRAP_CONTENT。
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// mTmpValue是期望的对话框的最大宽度,
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
if (baseSize != 0 && desiredWindowWidth > baseSize) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
//如果View请求的宽度不是 WRAP_CONTENT, goodMeasure为false,会执行 performMeasure。
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
如果上面的条件没能满足performMeasure的执行条件,performTraversals往下走,还有判断是不是执行performMeasure。
private void performTraversals() @ViewRootImpl.java{
// mStopped当前activity不处于stoped状态; mReportNextDraw通常在点亮屏幕扥带第一次绘制时,用于加快systemUI跟窗口管理之间的交互时,设置为true。
if (!mStopped || mReportNextDraw) {
//触摸模式是否引起焦点变化。
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
//WMS计算后得到的宽高跟View经过onMeasure计算的值不一致。
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
以上条件满足,开始执行performMeasure。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
//直接调用ViewTree的根元素mView的 measure,View中measure和onMeasure子类都可以重载,通常建议重载onMeasure,测量工作也是onMeasure中进行的。
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measure方法会调用onMeasure执行实际的测量工作。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) @View.java{
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
其中的参数widthMeasureSpec,heightMeasureSpec是上一级父View要求的宽高,依次往下传递到viewTree的各个元素。在ViewRootImpl中,这两个值是通过getRootMeasureSpec计算得出的,然后传给ViewTree的根元素DecorView。
子类在重写onMeasure方法时,必须调用setMeasuredDimension来存储该view测量后的宽高。
插播下widthMeasureSpec,heightMeasureSpec这两个值的解释,这两个值是通过
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
计算出来的,MeasureSpec是View的子类,也即是说widthMeasureSpec这个int值中包含了两部分,一是size,一是mode(MeasureSpec.EXACTLY),详细看下它的格式:
View.java
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
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);
}
}
}
IntRange 表表示size应该在一个范围内,重点看(size & ~MODE_MASK) | (mode & MODE_MASK);
MODE_MASK 是0x3左移30位,结果是0x11000等等后面都是0,所以size & ~MODE_MASK即是把size的最高两位空出来了,而mode & MODE_MASK 等于只取mode的最高两位,最终的结果:(size & ~MODE_MASK) | (mode & MODE_MASK) 的最高两位是mode,剩余的位数表示size。
看一个具体View的子类,如何重写onMeasure。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) @FrameLayout.java{
int count = getChildCount();
//循环处理所有的子View。
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//测量每个子对象,条件是 mMeasureAllChildren为true,相关的属性是:android.R.styleable#FrameLayout_measureAllChildren;并且View是可见的。
if ( mMeasureAllChildren || child.getVisibility() != GONE) {
//测量子对象,考虑padding,margins,如果子view是个ViewGroup,则会调用其child.measure函数继续递归,直到叶节点。
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
//得到view(Child)的计算结果,child.getMeasuredWidth()对应了View的MeasuredWidth变量,这个值通常是在onMeasure结束时通过setMeasuredDimension来设置的。
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
}
}
//考虑其他因素,如padding,对于View类来说,它只有padding,padding指的是内容区域与外围边框的距离,有left、top、right、bottom四个值;对于ViewGroup来说,除了padding,还有margin,margin指的是内容中各个元素之间的距离。
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
//对前面测量的宽高的最大值做调整,
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//保存测量的结果。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
2)performLayout计算位置。
private void performTraversals() @ViewRootImpl.java{
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
//这个条件跟前面performMeasure类似, mWidth, mHeight是由WMS计算后的值,表示WMS期望的宽高。
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) @ViewRootImpl.java{
final View host = mView;
//调用的ViewTree根元素的方法 layout,传入的参数也是DecorView的测量后的宽高。
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
子类不应该重载layout方法,而是应该重载onLayout方法,然后在onLayout方法中,调用每个子view的layout方法。四个参数表示的是这个view相对于它的父view四个方向的位置。
public void layout(int l, int t, int r, int b) {
//通过setFrame将这4个边距记录到成员变量中mLeft;mTop;mBottom;mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//view.java中的onLayout是null的,调用具体子类的onLayout。
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) ==
PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
}
//将flag恢复。
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
@FrameLayout.java{
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity)
@FrameLayout.java{
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++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
//子view设置的layout属性。
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//在onMeasure中计算的宽高,这里在计算出left,top就可以确定一个子view在父View中具体位置。
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
// gravity属性,是在不改变子View大小的情况下,控制其在父View中的位置,比如放在父对象的顶部、左部、垂直居中、水平居中等。
int gravity = lp.gravity;
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
//根据具体的布局属性,计算left。
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
}
//根据具体的布局属性,计算top。
switch (verticalGravity) {
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
}
//调用子View的layout方法,依次递归下去。
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
3)performDraw。遍历流程的最后一步,
private void performTraversals() @ViewRootImpl.java{
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
// cancelDraw表示当前draw被取消,需要重新遍历; 在前面relayoutWindow过程中,如果WMS没有成功申请到surface,newSurface会置为true,这是也需要重新遍历。
if (!cancelDraw && !newSurface) {
// performDraw重点调用了draw方法。
performDraw();
}
}
图形绘制的方式分软件和硬件两种,什么时候会使用硬件方式绘制呢?
当执行Activity的onCreate方法时,调用了PhoneWindow.java的setContentView,接着调用了PhoneWindow.java的installDecor,
private void installDecor() @PhoneWindow.java{
// mDecor为null时,调用 generateDecor传入的featureId是-1,这个id将会影响后期的绘制方式。
if (mDecor == null) {
mDecor = generateDecor(-1);
}else{…...}
}
生成DecorView对象时,把这个featureId保存在DecorView的变量mFeatureId中。除了生成应用程序的DecorView时featureId是-1,其他类型窗口的featureId都是大于0的值,如:
public abstract class Window @Window.java{
/** Flag for the "options panel" feature. This is enabled by default. */
public static final int FEATURE_OPTIONS_PANEL = 0;
/** Flag for the context menu. This is enabled by default. */
public static final int FEATURE_CONTEXT_MENU = 6;
/**
* Flag for enabling the Action Bar.
* This is enabled by default for some devices. The Action Bar
* replaces the title bar and provides an alternate location
* for an on-screen menu button on some devices.
*/
public static final int FEATURE_ACTION_BAR = 8;
…...
}
只有featureid小于0时,willYouTakeTheSurface方法的返回值才可能不为null。
public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() @DecorView.java{
return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null;
}
在ViewRootImpl.java中,willYouTakeTheSurface方法的返回值,会影响到会不会生成对象mAttachInfo.mHardwareRenderer(mAttachInfo.mHardwareRenderer= ThreadedRenderer.create(mContext, translucent);)而这个对象判断绘制方式时会用到。
在回头看下performDraw调用的draw方法。
private void draw(boolean fullRedrawNeeded) @ViewRootImpl.java{
//surface是画板,如果没有合法的surface,UI数据就无法正常存储和显示。
Surface surface = mSurface;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
//这个条件下,调用硬件绘制的方式。
if (mAttachInfo.mHardwareRenderer != null &&
mAttachInfo.mHardwareRenderer.isEnabled()) {
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
}else{
//否则,调用软件绘制的方式。
if (!drawSoftware(surface, mAttachInfo,
xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
}
看下软件渲染是怎么绘制的。
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) @ViewRootImpl.java{
//canvas是作画的工具集。
final Canvas canvas;
//锁定一个canvas对象,将使用这个canvas把数据画到surface中。
canvas = mSurface.lockCanvas(dirty);
mView.mPrivateFlags |= View.PFLAG_DRAWN;
//做必要的坐标变换。
canvas.translate(-xoff, -yoff);
mTranslator.translateCanvas(canvas);
//从Viewtree的根开始绘制。
mView.draw(canvas);
//绘制结束,解锁canvas,提交结果,canvas的绘制结果通过surface提交给surfacefligner,最后合成到framebuffer中,才能显示到屏幕上。
surface.unlockCanvasAndPost(canvas);
}