Android View:一条线理解 View 绘制流程

View 绘制流程

一、需要了解的知识

  • DecorView
    DecroView 其实是一个 FrameLayout,它包含了一个垂直的 LinerLayout,这个 LinerLayout 包含手机通知栏、 titlebar 标题栏,下部分为内容栏也就是 Activity 通过 setContentView 设置的布局。
    内容栏的 id 为 content,布局加到了 id 为 content 的 FrameLayout 中。
    获取 content 内容栏:
ViewGroup content = findViewById(R.android.id.content);

获取设置的布局 View:

content.getChildAt(0);
DecorView 层次图
  • ViewRoot 和 ViewRootImpl
    ViewRoot 是连接 WindowManager 和 DecorView 的纽带,View 的三大流程均是通过 ViewRoot 来完成的。然而 ViewRoot 不是 View,它的实现类 ViewRootImpl 跟 View 也没有什么直接的关系。详情可阅Android里那些令人费解的命名(一)ViewRoot

  • MeasureSpec
    MeasureSpec 是 View 的一个 static 类,这个词像两个单词的组成,翻译为“测量规格”或“测量参数”。
    MeasureSpec 代表一个 32 位的 int 值,它包含了 View 的大小(低30位代表 SpecSize)和测量模式(高2位代表 SpecMode)。MeasureSpec 将这两个值打包在了一起并提供了打包和解包的方法。
    简单地说,MeasureSpec 代表了某个 View 的测量大小和显示模式。
    MeasureSpec一共有三种模式:

UPSPECIFIED : 父容器不对 View 有任何限制,要多大给多大,一般用于表示一种数值未定的测量状态;
EXACTLY:父容器已经检测或计算出 View 所需的大小,这是一个比较精确的数值,一般最终 View 的大小就是这里指定的 SpecSize;
AT_MOST:父容器指定了一个可用大小,View 的大小不能超过这个值。

二、从源头寻找 View 绘制流程

2.1 DecorView 的创建和测量

1.创建 ViewRootImpl 对象并调用 performTraversals 开始绘制

ActivityThread 中,当 Activity 对象创建完毕,会将 DecorView 添加到 Window 中,同时创建 ViewRootImpl 对象,并将 ViewRootImp 对象和 DecorView 建立关联。

// 创建 ViewRootImpl 对象
root = new ViewRootImpl(view.getContext(), display);
// 和 DecorView 建立关联
root.setView(view, wparams, panelParentView);

从 ViewRoot 的 performTraversals 方法开始,将 View 按照 measure、layout、和 draw 的过程绘制出来。performTraversals 方法会依次调用 performMeasure、performLayout、performDraw 来完成 DecroView 的绘制流程。


private void performTraversals() {
    ...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    // performMeasure 执行测量
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    WindowManager.LayoutParams lp = mWindowAttributes;
    if (didLayout) {
        // performLayout 执行 Layout,确定坐标
        performLayout(lp, mWidth, mHeight);
        ...
    }
    ...
    // performDraw 执行绘制
    performDraw();
    ...
}

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    ...
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
}

/**
 * 基于它的布局参数,计算出窗口中根视图的度量标准。
 * 也就是根据屏幕宽高和 DecorView 布局参数来确定 DecorView 的 MeasureSpec
 *
 * @param windowSize
 *            窗口的可用宽度或高度
 *
 * @param rootDimension
 *            窗口的一维(宽度或高度)的布局参数。
 *
 * @return 用于度量根视图的度量指标。
 */
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);
            break;
          ...
    }
    return measureSpec;
}

ViewGroup:performMeasure --> measure --> onMeasure 在这个过程中,onMeasure 会对所有子元素进行 measure 。
ViewGroup:performLayout --> layout --> onLayout 然后对所有子元素进行 layout
ViewGroup:performDraw --> draw --> onDraw 传递过程通过 dispatchDraw 来完成

2. 开始测量 DecorView

performMeasure 中其实调用了 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 方法,mView 可以看作是上文 ViewRootImpl 对象设置进来的 DecorView 的实例。接下来看一下 DecorView 的继承关系:

DecorView extends FrameLayout extends ViewGroup extends View

DecorView、FrameLayout 还有 ViewGroup 最终都继承了 View类,FrameLayout 和 ViewGroup 都没有 measure 方法,所以 mView.measure 最终会调用 View 类的 measure 方法。
measure 方法是 View 类的一个 final 方法,不可重写。DecorView 调用了 measure 方法,接下来看一下 View 调用 measure 方法做了什么。

View 类 measure 方法

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  ...
  onMeasure(widthMeasureSpec,heightMeasureSpec);
  ..
}

插播 1:View 中 onMeasure 所做的事情:

measure 方法调用了 onMeasure 方法,所以具体 View 的测量工作是在 onMeasure 方法中进行的:

View 类 onMeasure 方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

setMeasuredDimension方法可以简单理解为给 View 的 mMeasuredWidth,和 mMeasuredWidth 设置值,也就是确定 View 的测量宽高。这里的测量宽高是通过传递过来的父 View 的 MeasureSpec 和子 View 的默认属性共同决定的,getDefaultSize 方法会证明这一点。设置完成后就代表当前 View 的宽/高测量值确定了。
那么 View 的测量宽高是通过 getDefaultSize 方法返回的参数来确定具体的测量值。

getDefaultSize 方法传入两个参数

  • 第一个参数为 getSuggestedMinimumWidth 方法的返回值:
protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

根据该方法的返回值来看,如果当前 View 没有设置背景,则返回的值为 View 的 android:minWidth 属性所设置的值,可以为0。如果指定了背景,则返回的值为 android:minWidth 属性值和背景的最小宽度两者中的最大值。对于高度,逻辑也是一样的,android:minHeight 属性和背景共同决定。

  • 第二个参数为 measure 方法中传递过来的宽/高的 MeasureSpec 格式的测量值,是由父 View 的MeasureSpec 和子 View 自己的 LayoutParams 共同决定的,后文再作分析。

View 类 getDefaultSize 方法

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = View.MeasureSpec.getMode(measureSpec);
    int specSize = View.MeasureSpec.getSize(measureSpec);
    // 根据父 View 传递来的 SpecMode 来决定返回 View 的测量值
    switch (specMode) {
        case View.MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result;
}

上面已经分析过这两个参数,getDefaultSize 默认返回的是传递来的第一个参数,也就是由最小宽高和背景宽高共同决定的值。接下来进入 switch 判断,如果传递来的 SpecMode 为 UNSPECIFIED,则返回由 minHeight/minWidth 和背景共同决定的值。如果是 AT_MOST(规定上限)或 EXACTLY(精确值)模式,则返回由父 View 的MeasureSpec 和子 View 自己的 LayoutParams 共同决定的测量参数的值,也就是第二个参数中的 SpecSize。

3. 递归测量 DecorView 的子 View,最后测量 DecorView
View 的 measure 方法会调用 onMeasure 来做具体的测量工作。DecorView 其实是一个 FrameLayout 且重写了 onMeasure 方法,所以接下来会进入 FrameLayout 的 onMeasure 方法。接着会调用父类 ViewGroup 的 measureChildWithMargins 方法来测量所有子 View。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    ...

    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;
    // 遍历子 View 测量它们的值
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        // 不为 GONE 的子 View 都会参与测量
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            // 父类 ViewGroup 中的方法,用来测量所有子 View
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            ...
        }
    }
    ...
    // 父类 View 中的方法,用来确定当前 View 的测量值(当前是 FrameLayout)
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
    ...
}

插播 2:ViewGroup 中 measureChildWithMargins 所做的事情:

ViewGroup 类 measureChildWithMargins 方法源码

protected void measureChildWithMargins(View child,
                                       int parentWidthMeasureSpec, int widthUsed,
                                       int parentHeightMeasureSpec, int heightUsed) {

    // child.getLayoutParams()获取的是子 View 在 xml 中所设置的布局参数
    final ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();

    // 根据父 View 的测量参数 parentWidthMeasureSpec/parentHeightMeasureSpec
    // 以及子 View 的布局参数 Margin、Padding 和 width/height 来确定子 View 的测量参数
    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 继续测量。
    // 如果子 View 是 ViewGroup 则会递归往下级 View 测量
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

简单理解:首先,拿到子 View 的 xml 设置的布局参数,父 View 的测量参数(MeasureSpec)是由父 View 的 onMeasure 传递过来的。接着,将这些参数传递到 getChildMeasureSpec 方法来计算出子 VIew 的测量参数。最后子 View 调用 measure 方法来继续测量,如果该子 View 是 ViewGroup,会继续调用 measureChildWithMargins 来递归测量下级 View,如果子 View 只剩一个单独的 View,则调用 measure 之后再调用
onMeasure 中的 setMeasuredDimension 来完成测量。

接下来看 getChildMeasureSpec 方法来计算出子 VIew 的测量参数的过程:

ViewGroup 类 getChildMeasureSpec 方法源码

/**
 * @param spec 父 View 传递来的测量参数
 * @param padding 上面方法传递来的父View的padding和子View的margin以及widthUsed(已使用的宽度/高度)
 *        lp 是子View的布局参数
 *        mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed
 * @param childDimension 子View布局参数设定的宽或高
 * @return 子View的布局参数
 */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = View.MeasureSpec.getMode(spec); // 父View的SpecMode
        int specSize = View.MeasureSpec.getSize(spec); // 父View的SpecSize
        // 具体大小为父View的大小 减去 父View的padding和子View的margin以及widthUsed
        // 暂时称之为 View剩余大小
        int size = Math.max(0, specSize - padding);
        // 用来生成子View的测量参数
        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
            // 父View的大小是EXACTLY模式
            case View.MeasureSpec.EXACTLY:
                if (childDimension >= 0) { // 子View布局参数宽/高大于等于0
                    resultSize = childDimension; // 子View的宽/高为子View设置的布局参数值
                    resultMode = View.MeasureSpec.EXACTLY; // 子View的模式为精确模式
                } else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) { // 子View是MATCH_PARENT
                    resultSize = size; // 让子View的数值为计算好的View剩余大小
                    resultMode = View.MeasureSpec.EXACTLY; // 子View的模式为精确模式
                } else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) { // 子View是WRAP_CONTENT
                    resultSize = size; // 子View的数值为计算好的View剩余大小
                    resultMode = View.MeasureSpec.AT_MOST; // 子View的模式为AT_MOST,表示不能超过这个View剩余大小
                }
                break;

            // 父View的大小是AT_MOST模式
            case View.MeasureSpec.AT_MOST:
                if (childDimension >= 0) { // 子View布局参数宽/高大于等于0
                    resultSize = childDimension; // 子View的宽/高为子View设置的布局参数值
                    resultMode = View.MeasureSpec.EXACTLY; // 子View的模式为精确模式
                } else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) { // 子View是MATCH_PARENT
                    resultSize = size; // 子View的数值为View剩余大小
                    resultMode = View.MeasureSpec.AT_MOST; // 因为父View是AT_MOST模式其数据不确定,
                                                           // 所以子View也是AT_MOST不能超过View剩余大小
                } else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {// 子View是WRAP_CONTENT
                    // 子View想自适应,但它最大不能超过View剩余大小
                    resultSize = size;
                    resultMode = View.MeasureSpec.AT_MOST;
                }
                break;

            // 父View的大小是UNSPECIFIED模式
            case View.MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {// 子View布局参数宽/高大于等于0
                    // 子View宽高是精确数值,父View不限制所以子View也不加限制
                    resultSize = childDimension;
                    resultMode = View.MeasureSpec.EXACTLY; //子View宽高是精确数值所以其模式也应为EXACTLY
                } else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {// 子View是MATCH_PARENT
                    // 如果父View的大小未确定,返回0,否则返回View剩余大小
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = View.MeasureSpec.UNSPECIFIED; // 父View大小无限制,子View也应是
                } else if (childDimension == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    // 判断模式与上一个类似
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = View.MeasureSpec.UNSPECIFIED;
                }
                break;
        }
        // 根据上文子View的SpecMode和SpecSize来打包成一个MeasureSpec对象
        return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

简单总结:
首先拿到父 View 的 specMode 和 specSize,specSize是父 View 的测量宽/高。使用 specSize 减去 父 View 的 padding 和子 View 的 margin 以及widthUsed 来计算出一个剩余大小,也就是子 View 能拿来用作显示的区域大小,暂且称之为 View 剩余大小。
然后根据父 View 的 specMode 和子 View 具体的布局参数来确定子 View 的 specMode 和 specSize:

  • 父 View 是EXACTLY模式,说明父 View 的大小是精确的,一般来说,这个大小就是最终展示到屏幕上的大小。
  1. 如果子 View 的大小在布局中设定好了,并且大于等于 0,那么子 View 的值是确定的,并且其模式为 EXACTLY。无论父 View 的大小与模式。
  2. 如果子 View 的大小为 "match_parent",当父 View 精确大小,那么子 View 的大小为计算好的 View 剩余大小,并且模式也是 EXACTLY。
  3. 如果子 View 的大小为 "wrap_content",当父 View 精确大小,子 View 再怎么自适应也不应超过父 View 剩余大小,所以 specSize 为 View 剩余大小,specMode 为 AT_MOST。
  • 父 View 是AT_MOST模式,说明父 View 的大小不确定,但是会小于某个固定值。
  1. 同样,如果子 View 的大小在布局中设定好了,并且大于等于 0,那么子 View 的值是确定的,并且其模式为 EXACTLY。无论父 View 的大小与模式。
  2. 如果子 View 的大小为 "match_parent",子 View 的模式为 AT_MOST,并且其最大值为 View 剩余大小。
  3. 逻辑同上,也应是 AT_MOST 模式并且最大值为 View 剩余大小。
  • 父 View 是UNSPECIFIED模式,表面父 View 的大小没有约束,所以子 View 的大小也可以是没有限制大小的。
  1. 同样,如果子 View 的大小在布局中设定好了,并且大于等于 0,那么子 View 的值是确定的,并且其模式为 EXACTLY。
  2. 如果子 View 的大小为 "match_parent",子 View 的大小理所应当也是无限制模式的,并且如果父View的大小未确定,返回0,否则返回View剩余大小。
  3. 逻辑同上。

(3)DecorView 作为 FrameLayout 调用 FrameLayout 的 onMeasure 方法,onMeasure 方法又调用了 ViewGroup 的 measureChildWithMargins 方法来测量所有子 View。子 View 测量完毕后,接着调用父类 View 的 setMeasuredDimension 方法,这个方法在上面 View 的测量流程中有提到过。

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    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) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

总之就是 DecorView 作为 FrameLayout 先测量子 View,然后再确定自己的测量值。关于子 View 是如何测量的,可以回去看第一条插播。

2.2 Layout 过程

DecroView 完成 performMeasure 中的 measure 过程后,就完成了 DecroView 的大小和绘制模式,接下来就会进行 Layout 过程来放置 DecroView 的具体位置。然后判断 didLayout (didLayout 由 Window 状态等参数决定),如果为 true 则执行 Layout 过程。

private void performTraversals() {
    ...
    // performMeasure 执行测量
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    WindowManager.LayoutParams lp = mWindowAttributes;
    if (didLayout) {
        // performLayout 执行 Layout,确定坐标
        performLayout(lp, mWidth, mHeight);
        ...
    }
    ...
}

ViewRootImp 中 performLayout 方法

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                           int desiredWindowHeight) {
    ...
    // 赋值给 host 并判断是否为 null
    final View host = mView;
    if (host == null) {
        return;
    }
    ...
    // 进行具体的 layout 过程
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
}

由上面代码可知 DecorView 调用 layout 方法,这个方法被 ViewGroup 重写为 final 类型(说明所有继承 ViewGroup 的 View 的 layout 过程被 ViewGroup 统一管理),DecorView 是一个 FrameLayout 继承了 ViewGroup。所以会调用 ViewGroup 的 layout 方法。

ViewGroup layout 方法

@Override
public final void layout(int l, int t, int r, int b) {
    // mSuppressLayout 判断当前布局是否被父 ViewGroup 进行增加或删除的动画效果
    // mTransition 判断当前布局是否存在动画或动画正在改变布局
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        super.layout(l, t, r, b);
    } else {
        // transition 动画正在运行设置为 true,等待动画完成重新调用 requestLayout() 重新请求 Layout 步骤
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}

上述代码可以简单理解为 layout 之前先进行 Transition 动画的判定,如果有动画在执行则设置 mLayoutCalledWhileSuppressed 为 true 作为标记,等待动画完毕重新调用 layout。
如果Transition 没有在执行,则调用父类的 layout(l, t, r, b); 方法,也就是 View 类的 layout() 方法。

View layout 方法

public void layout(int l, int t, int r, int b) {
    ...
    // 先标记旧的属性值
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    // 判断 LayoutMode 是否为 LAYOUT_MODE_OPTICAL_BOUNDS 模式,如果是则执行 setOpticalFrame。
    // 不是则执行 setFrame,这个函数的作用为:判断当前 View 的属性是否发生变化,并返回是否有变化。
    // 同时为当前 View 设置一个大小和位置。
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    // 判断当前 View 是否发生变化,来决定是否需要重新 layout
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        // 判断是否在圆形可穿戴设备上绘制圆角
        if (shouldDrawRoundScrollbar()) {
            if(mRoundScrollbarRenderer == null) {
                mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
            }
        } else {
            mRoundScrollbarRenderer = null;
        }
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
        ...
    }
    ...
}
  1. 拿到上下左右的属性值并标记,然后判断属性值是否发生变化并更新设置。
  2. 如果 View 发生了变化则回调 onLayout() 来确定坐标,在初始情况下各个属性都是默认的。一旦子 View 完成 measure 过程,这些属性都会被赋值,所以会回调具体类的 onLayout() 方法。View 类的 onLayout() 方法是空实现
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

由于 DecorView 继承 FrameLayout 又继承 ViewGroup 又继承 View,所以倒着来看 :

ViewGroup 的 onLayout() 方法

@Override
protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);

简单粗暴的一个抽象方法,也就是说需要子类具体的去实现。

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();
        // 获取父 View 的相关属性
        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);
            // 省略的是一些根据子 View 的 LayoutGravity 和父 View 的边界属性来重新计算的代码
            ...
            // 计算完毕之后子 View 再递归进行 layout。根据具体的 View 回调相关的 onLayout 方法
             child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
}
  1. 到 layoutChildren 方法也是拿了一堆属性 ,然后遍历子 View 来balabala 进行计算(省略了)最终确定子 View 的左右上下边距再使用递归的方式来进行子 View 的 layout 方法。
  2. 进行 layout 会回调 onLayout 方法,不同的 View 会重写 onLayout 方法,具体可以去看不同 View 的源码。
  3. onLayout(boolean changed, int left, int top, int right, int bottom) 方法中的各种属性在 measure 过程中计算好传递过来的,但是具体进行 layout 过程可能会根据子 View 的 LayoutGravity 模式来进行具体的更改。

至此,Layout 过程告一段落。

2.3 Draw 过程

performLayout 执行完毕之后会进行 performDraw 方法来进行绘制,

private void performTraversals() {
    ...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    // performMeasure 执行测量
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    WindowManager.LayoutParams lp = mWindowAttributes;
    if (didLayout) {
        // performLayout 执行 Layout,确定坐标
        performLayout(lp, mWidth, mHeight);
        ...
    }
    ...
    // performDraw 执行绘制
    performDraw();
    ...
}

performDraw()方法经过层层调用会执行mView.draw(canvas);方法,这里的 mView 依旧是 DecorView 的实例。因为View的draw 方法一般不去重写,官网文档也建议不要去重写 draw 方法,所以接下来进入 View 类的 draw 方法:

public void draw(Canvas canvas) {
    ...
    /*
     * 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);
        ...
        
        // 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;
    }
  ...
}

这块的注释写的非常清晰,一条条列出来了绘制步骤,其中第 2 步和第 5 步是在特点情况下才会进行的,所以分析时省略这两块的步骤:

  1. 绘制背景
private void drawBackground(Canvas canvas) { 
     Drawable final Drawable background = mBackground; 
      ...... 
     //mRight - mLeft, mBottom - mTop layout确定的四个点来设置背景的绘制区域 
     if (mBackgroundSizeChanged) { 
        background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);   
        mBackgroundSizeChanged = false; rebuildOutline(); 
     } 
     ...... 
     //调用Drawable的draw() 把背景图片画到画布上
     background.draw(canvas); 
     ...... 
}
  1. 如有必要,请保存画布图层以准备淡入淡出
  2. 绘制View的内容
    View 的 onDraw() 和 ViewGroup 的 onDraw() 都是空实现,所以需要具体的子 View 去实现相应的绘制功能
  3. 绘制View的子View们
    View 的 dispatchDraw(canvas); 方法也是一个空实现,ViewGroup 实现了这个方法,在 ViewGroup 中该函数遍历所有子元素的 draw 方法,这样绘制事件就一层一层地传递了下去。

ViewGroup 的 dispatchDraw 方法

@Override
 protected void dispatchDraw(Canvas canvas) {
       ...
        if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        } else {
            for (int i = 0; i < count; i++) {
                final View child = children[getChildDrawingOrder(count, i)];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        }
      ......
    }

drawChild 方法直接调用了 child.draw(canvas, this, drawingTime); 方法。又是在循环中,所以会调用所有 child View 的 draw 方法。

  1. 如有必要,绘制渐变边缘并恢复图层
  2. 绘制装饰(例如滚动条)
  3. 绘制默认焦点高亮

到这里 View 的绘制流程就基本结束了。

参考资料:

Android View的绘制流程
《Android 开发艺术探索》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271