Android View的绘制流程

485 阅读11分钟

不要停下脚步,一往无前

前言

Android View的绘制流程是View相关的核心知识点,也是高阶UI必须要了解的前提。在Android系统中,Window是最基本的窗口单元,每个activity都会创建一个,PhoneWindow是Window的唯一实现类,是View系统和Activity(可以理解为控制器)之前的纽带。Window下有个DecorView,DecorView是一个FrameLayout,是我们自己布局的载体。

一、window的创建

startActivity最终会调用到ActivityThread的handleLaunchActivity方法来创建Activity,

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ....
    // 创建Activity,会调用Activity的onCreate方法
    // 从而完成window的创建
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;
        handleResumeActivity(r.tolen, false, r.isForward, !r.activity..mFinished && !r.startsNotResumed);
    }
}

具体的创建过程是在performLaunchActivity方法中完成的,代码如下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       	......
				// 根据反射创建activity
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
        } 
  			......
        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            if (activity != null) {
                // ......           
                Window window = null;
               	......
              	// actitiy和window绑定
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window);
            }
        } 
  			......
        return activity;
    }

activity的attach方法,将window与activity绑定:

final void attach(Context context, ......,Window window) {
        attachBaseContext(context);
        mFragments.attachHost(null /*parent*/);
        // 创建PhoneWindow,并赋值给activity的mWindow成员
        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
  	......
}

二、DecorView的创建和加载

创建好Activity,并且与创建好的Window绑定后,会调用ActivityThread的handleResumeActivity方法

final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, 	boolean reallyResume, int seq, String reason) {
    ......
    unscheduleGcIdler();
    mSomeActivitiesChanged = true;

    // 调用activity的onResume方法
    r = performResumeActivity(token, clearHide, reason);

    if (r != null) {
        final Activity a = r.activity;
				......
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
          	// 获取到window中的decorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
          	// 得到了WindowManager,WindowManager是一个接口
            // 并且继承了接口ViewManager
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            ......
            // 实际调用的是WindowManagerImpl(WindowManager接口实现类)的addView方法
            if (a.mVisibleFromClient && !a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            }
          ......
        } 
       ......
    }
}

除了Window和DecorView这两个主力之外,还有一个很重要的角色是ViewRoot,ViewRoot是连接WindowManager和DecorView之间的纽带,View的三大流程均由其来完成。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联,相关源码如下所示:

// WindowManagerGlobal的addView方法中
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ......
    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
        ......
        // 创建ViewRootImpl实例
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }
	
    try {
      	// 把DecorView加载到Window中
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        synchronized (mLock) {
            final int index = findViewLocked(view, false);
            if (index >= 0) {
                removeViewLocked(index, true);
            }
        }
        throw e;
    }
}

三、绘制的整体流程

整个绘图流程在ViewRoot类的performTraversals()函数展开,该函数所做 的工作可简单概况为是否需要重新计算视图大小(measure)、是否需要重新安置视图的位置(layout)、以及是否需要重绘(draw)

private void performTraversals() {
    ...
    //最外层ViewGroup是MATCH_PARENT,在实例被创建时确定
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
  	...
    //测量流程
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  	...
    //布局流程
    performLayout(lp, mWidth, mHeight);
		...
    //绘制流程
    performDraw();
  	...
}

大概的流程图如下:

0.MeasureSpec

需要先了解MeasureSpec,它是一个32位的整形值,其高2位表示测量模式SpecMode,低30位表示某个测试模式下的规格大小SpecSize。

测量模式有三种:

// 不指定模式, 父视图没有限制子视图的大小,子视图可以是想要
// 的任何尺寸,通常用于系统内部,应用开发中很少用到。
public static final int UNSPECIFIED = 0 << MODE_SHIFT;

// 精确模式,视图宽高指定为match_parent或具体数值时生效,
// 表示父视图已经决定了子视图的精确大小,这种模式下View的测量
// 值就是SpecSize的值。
public static final int EXACTLY     = 1 << MODE_SHIFT;

// 最大值测量模式,当视图的宽高指定为wrap_content时生效,此时
// 子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。
public static final int AT_MOST     = 2 << MODE_SHIFT;

1) DocerView的MeasureSpec创建

//desiredWindowWidth和desiredWindowHeight是屏幕的尺寸
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

private static int getRootMeaureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
        case ViewGroup.LayoutParams.MATRCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
    }
    return measureSpec;
}

2)子View的MeasureSpec创建

子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来的,具 体计算逻辑封装在getChildMeasureSpec()里。

/**
*
* 目标是将父控件的测量规格和child view的布局参数LayoutParams相结合,得到一个 * 最可能符合条件的child *	view的测量规格。
* @param spec 父控件的测量规格
* @param padding 父控件里已经占用的大小
* @param childDimension child view布局LayoutParams里的尺寸
* @return child view 的测量规格
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimesion) {
    int specMode = MeasureSpec.getMode(spec);//父控件的测量模式
    int specSize = MeasureSpec.getSize(spec);//父控件的测量大小

    // padding是指父容器中已占用的空间大小,因此子元素可用的
    // 大小为父容器的尺寸减去padding
    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (sepcMode) {
        // 当父控件的测量模式 是 精确模式,也就是有精确的尺寸了 
        case MeasureSpec.EXACTLY:
        		//如果child的布局参数有固定值,比如"layout_width" = "100dp"
						//那么显然child的测量规格也可以确定下来了,测量大小就是100dp,测量模式也是EXACTLY
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } 
        		//如果child的布局参数是"match_parent",也就是想要占满父控件
        		//而此时父控件是精确模式,也就是能确定自己的尺寸了,那child也能确定自己大小了
        		else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } 
        		//如果child的布局参数是"wrap_content",也就是想要根据自己的逻辑决定自己大小, 
        		//比如TextView根据设置的字符串大小来决定自己的大小
						//那就自己决定自己的大小,不过你的大小肯定不能大于父控件的大小
						//所以测量模式就是AT_MOST,测量大小就是父控件的size
        		else if (childDimesion == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 当父控件的测量模式 是 最大模式,也就是说父控件自己还不知道自己的尺寸,但是大小不能超过size 
        case MeasureSpec.AT_MOST:
        		//同样的,既然child能确定自己大小,尽管父控件自己还不知道自己大小,也优先满足孩子的需求
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } 
        		//child想要和父控件一样大,但父控件自己也不确定自己大小,所以child也无法确定自己大小 
        		//但同样的,child的尺寸上限也是父控件的尺寸上限size
        		else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } 
        		//child想要根据自己逻辑决定大小,那就自己决定自己的尺寸,但不能超过父控件
        		else if (childDimension == LayoutParams.WRAP_CONTENT) {
                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 him 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 = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size....
                // find out how big it should be
                resultSize = 0;
                resultMode == MeasureSpec.UNSPECIFIED;
            }
            break;
        }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

可以看到,对于应用层 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定对于不同的父容器和view本身不同的LayoutParams,view就可以有多种MeasureSpec。

  1. 当view采用固定宽 高的时候,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精确模式并且其大小遵循Layoutparams中的大小;
  2. 当view的宽高是match_parent时,这个时候如果父容器的模式是精准模式,那么 view也是精准模式并且其大小是父容器的剩余空间,如果父容器是最大模式,那么view也是最大模式并且其大小不会超过父容器的剩余空间;
  3. 当view的宽高是wrap_content时,不管父容器的模式是精准还是最大化,view的模式总是最大化并且大小不能超过父容器的剩余空间。
  4. Unspecified模式,这个模式主要用于系统内 部多次measure的情况下,一般来说,我们不需要关注此模式。

1. measure流程

测试流程从ViewRootImpl.performMeasure()开始:

//=============ViewRootImpl.java==============
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
       ......
       mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
       ......
}

此处的mView在ViewRootImplsetView方法中赋值

//=============ViewRootImpl.java==============
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    	...
      // 此处的view,就是在ActivityThread的addView方法中传过来的,也就是DecorView
      mView = view;
   		....
      // 此处attrs,就是在ActivityThread的addView方法中传过来的
      mWindowAttributes.copyFrom(attrs);
  		...
}
      

可以看到,从ViewRootImpl的performMeasure方法开始,一开始进行测试流程的就是从DecorViewmeasure()操作开始的,追踪代码进入到View的measure()方法:

//=============View.java==============
/**
 * <p>
 * This is called to find out how big a view should be. The parent
 * supplies constraint information in the width and height parameters.
 * </p>
 *
 * <p>
 * The actual measurement work of a view is performed in
 * {@link #onMeasure(int, int)}, called by this method. Therefore, only
 * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
 	view的实际测量工作是在onMeasure()方法中完成的,其子类可以并且必须实现该方法。
 * </p>
 *
 *
 * @param widthMeasureSpec Horizontal space requirements as imposed by the
 *        parent
 * @param heightMeasureSpec Vertical space requirements as imposed by the
 *        parent
 *
 * @see #onMeasure(int, int)
 */
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  ....
    // measure ourselves, this should set the measured dimension flag back
    onMeasure(widthMeasureSpec, heightMeasureSpec);
  ....       
}

再看我们熟悉的这个onMeasure(int,int)方法:

//=============View.java==============
**
* <p>
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
* should be overridden by subclasses to provide accurate and efficient
* measurement of their contents.
* </p>
*
* <p>
* <strong>CONTRACT:</strong> When overriding this method, you
* <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
* measured width and height of this view. Failure to do so will trigger an
* <code>IllegalStateException</code>, thrown by
* {@link #measure(int, int)}. Calling the superclass'
* {@link #onMeasure(int, int)} is a valid use.
* </p>
*
* <p>
* The base class implementation of measure defaults to the background size,
* unless a larger size is allowed by the MeasureSpec. Subclasses should
* override {@link #onMeasure(int, int)} to provide better measurements of
* their content.
* </p>
*
* <p>
* If this method is overridden, it is the subclass's responsibility to make
* sure the measured height and width are at least the view's minimum height
* and width ({@link #getSuggestedMinimumHeight()} and
* {@link #getSuggestedMinimumWidth()}).
* </p>
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
*                         The requirements are encoded with
*                         {@link android.view.View.MeasureSpec}.
* @param heightMeasureSpec vertical space requirements as imposed by the parent.
*                         The requirements are encoded with
*                         {@link android.view.View.MeasureSpec}.
*
* @see #getMeasuredWidth()
* @see #getMeasuredHeight()
* @see #setMeasuredDimension(int, int)
* @see #getSuggestedMinimumHeight()
* @see #getSuggestedMinimumWidth()
* @see android.view.View.MeasureSpec#getMode(int)
* @see android.view.View.MeasureSpec#getSize(int)
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

方法很短,注释却很长,注意注释的第二段:当重写这个方法时,你必须调用setMeasuredDimension(int, int)方法来保存View的宽高尺寸,不然的话,会抛出IllegalStateException异常。此方法的调用,也代表着测量阶段的结束。

对于ViewGroup的测量,主要是通过measureChild方法:

/**
 * 请求所有的子View去测量自己, 要考虑到子View的测量要求MeasureSpec及其padding.
 * 会跳过所有状态为GONE的子View, 最主要的工作是在getChildMeasureSpec方法中处理的
 
 * @param widthMeasureSpec The width requirements for this view
 * @param heightMeasureSpec The height requirements for this view
 */
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);
}

/** 
  * 最繁重的工作都在此方法中,也就是为每个子View计算出它的测量规则MeasureSpec
  * 此方法在对上一小节"子View的MeasureSpec创建"的介绍中已经分析过。
  * 目标是将 ChildView 的 MeasureSpec 和 LayoutParams 结合起来去得到一个最合适的结果。
  *
  * @param spec 对该 View 的测绘要求
  * @param padding 当前 View 在当前唯独上的 paddingand,也有可能含有 margins
  *
  * @param childDimension 在当前维度上(height 或 width)的具体指
  * @return 子视图的 MeasureSpec 
  */
 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
     .........
     // 根据获取到的子视图的测量要求和大小创建子视图的 MeasureSpec
     return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
 }

然后回到了开始所提及的View的measure()->onMeasure()->setDimention()方法。

2.layout流程

首先子View的具体位置是相对于父View而言的,View类的onLayout()方法不需要重写,ViewGroup的onLayout是一个抽象方法,必须要实现这一方法。

layout过程,就是通过测量后的尺寸,获取到view的mMeasuredWidth和mMeasuredHeight,然后通过子View的layout(l,t,r,b)方法来确定子View在父布局中的相对位置。

从ViewRootImpl的performLayout方法开始:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                           int desiredWindowHeight) {
  ···
    //host,也就是setView方法传进来的DecorView
    final View host = mView;
  ···
    try {
      // 调用了DecorView的layout(r,l,t,b)方法,也就是ViewGroup的layout方法
      host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
      ···
        if (numViewsRequestingLayout > 0) {
          ···
            if (validLayoutRequesters != null) {
              ···
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
              ···
            }
        }
    } finally {
      ···
    }
  ···
}

然后看ViewGroup的layout:

@Override
// 方法被final修饰,和measure一样,不能被修改或重写
public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
      	//直接调用了View的layout方法
        super.layout(l, t, r, b);
    } else {
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}

再看View的layout方法

public void layout(int l, int t, int r, int b) {
  ···
    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);
    ···
  }
  ···
}

可以看到,真正进行布局的方法是setOpticalFrame(l,t,r,b)setFrame(l,t,r,b)方法,而setOpticalFrame里面也是调用了setFrame(l,t,r,b)方法。此方法的四个参数确定了其在父View当中的位置。

setFrame(l,t,r,b)方法中会有一个布尔值来判断是否需要对视图进行重绘。

对子View的布局都是通过onLayout()方法中进行的。

布局过程也是通过递归的方式进行的,如果子View仍然是父视图,则会继续layout下去,直到遇到子View的onlayout空方法,则该子View布局流程结束。

3. draw流程

依然先从ViewRootImpl中的performDraw()方法开始:

// ----------------ViewRootImpl---------------
private void performDraw() {
    ...
    try {
        draw(fullRedrawNeeded);
    } finally {
        ...
    }
	...
}

private void draw(boolean fullRedrawNeeded) {
    Surface surface = mSurface;
    if (!surface.isValid()) {
        return;
    }
		.......
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
            ......
        } 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) {
								......
                // 此mView也就是DecorView
                mView.draw(canvas);
      					......
    }

也就是最终会调用到DecorView的draw(canvas)方法,

// ----------------DecorView---------------
@Override
 public void draw(Canvas canvas) {
   	// 调用父类的draw方法
     super.draw(canvas);

     if (mMenuBackground != null) {
         mMenuBackground.draw(canvas);
     }
}

跟踪super.draw(canvas)方法,最终会跳到View的draw方法。

   /**
     * Manually render this view (and all of its children) to the given Canvas.
     * The view must have already done a full layout before this function is
     * called.  When implementing a view, implement
     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
     * If you do need to override this method, call the superclass version.
     *
     * @param canvas The Canvas to which the View is rendered.
     */
    @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;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // 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);

            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;
        }

通过注释可以看到,一般绘制过程分为7步,但第2步和第5步通常可以忽略,大概步骤:

Step 1. 绘制背景; Step 2. 忽略跳过; Step 3. 绘制内容; Step 4. 绘制子视图; Step 5. 忽略跳过; Step 6. 绘制装饰(前景,滚动条); Step 7. 绘制默认焦点高光。

可以看到第三步onDraw(),也就是我们自定义View一般必须要重写的一个方法,该方法是空方法,因为每个子View的具体内容不一样,需要我们自己去实现逻辑。

第四步,即dispatchDraw(canvas):

// ----- View.java -----
/**
  * Called by draw to draw the child views. This may be overridden
  * by derived classes to gain control just before its children are drawn
  * (but after its own view has been drawn).
  * @param canvas the canvas on which to draw the view
  */
 protected void dispatchDraw(Canvas canvas) {

 }

可以看到View中的该方法是个空实现,但注释写的很清楚,如果该View包含子View的话,就要实现该方法,然后我们去ViewGroup的该方法中去看:

// ------ViewGroup---------
@Override
protected void dispatchDraw(Canvas canvas) {
		...
    for (int i = 0; i < childrenCount; i++) {
        while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
            final View transientChild = mTransientViews.get(transientIndex);
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                    transientChild.getAnimation() != null) {
                more |= drawChild(canvas, transientChild, drawingTime);
            }
            transientIndex++;
            if (transientIndex >= transientCount) {
                transientIndex = -1;
            }
        }
		....
}

可以看到,在遍历其子View的时候,调用了绘制子View的方法,最终调用了子View的draw方法。同样的,如果该子 View 还有子视图,也会继续遍历下去调用 drawChild() 方法,继续绘制子 View,直到叶子 View 为止,这样不断递归下去,直到画完整棵 DecorView 树。