上一篇文章《Android 应用界面显示流程》讲到,从Activity.setContentView(int)方法,把布局文件某xml交给了PhoneWindow,PhoneWindow把布局inflate出来附在了DecorView里,然后交给了WMS管理,WMS和AMS调度后最终把画面显示出来。

这篇文章讲ViewRootImpl.performTraversals(),是如何把DecorView绘制出来的。

我们知道自定义View中,需要搞懂三个重要方法onMeasure、onLayout和onDraw。performTraversals()也是分别从measure、layout和draw这3个流程去把View绘制出来的。

方法流程大概是这样的,源码基于Android5.1.1

private void performTraversals() {    ...    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);    ...    performLayout(lp, desiredWindowWidth, desiredWindowHeight);    ...    performDraw();    ...}private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {    ...    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);    ...}private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,        int desiredWindowHeight) {    ...    final View host = mView;    ...    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());    ...}private void performDraw() {    ...    draw(fullRedrawNeeded);    ...}private void draw(boolean fullRedrawNeeded) {    ...    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.draw(canvas);    ...}

 

 

 

 

1.performMeasure

我们知道View里面有个MeasureSpec的概念,类型是int。这个int型的32位的二进制数里由两部分组成分别是mode和size,头两位表示类型mode,00表示未指定,01表示精确值,10表示最大值。后面30位表示具体的数值size。举两个例子就是可以有『精确值|100』『最大值|1080』这种长度规格。

private void performTraversals() {    ...    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);    ...}private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {    ...    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);    ...}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 = 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;}

执行performMeasure需要MeasureSpec,所以首先需要构建一个MeasureSpec,通过getRootMeasureSpec传入了窗口的实际大小和LayoutParams.MATCH_PARENT(来自WindowManager.LayoutParam的默认值)。MeasureSpec.makeMeasureSpec用于把这两个mode和size合并为一个int型整数。然后performMeasure里,执行了mView.measure,mView就是DecorView了。

View.javapublic final void measure(int widthMeasureSpec, int heightMeasureSpec) {    ...    onMeasure(widthMeasureSpec, heightMeasureSpec);    ...   }==================================================================================FrameLayout.java@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    int count = getChildCount();    ...    //遍历测量子View    for (int i = 0; i < count; i++) {        final View child = getChildAt(i);        //需要判断子View不是GONE的状态        if (mMeasureAllChildren || child.getVisibility() != GONE) {            //调用ViewGroup的measureChildWithMargins测量子View            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);            final LayoutParams lp = (LayoutParams) child.getLayoutParams();            maxWidth = Math.max(maxWidth,                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);            maxHeight = Math.max(maxHeight,                    child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);            childState = combineMeasuredStates(childState, child.getMeasuredState());            if (measureMatchParentChildren) {                if (lp.width == LayoutParams.MATCH_PARENT ||                        lp.height == LayoutParams.MATCH_PARENT) {                    mMatchParentChildren.add(child);                }            }        }    }    ...}==================================================================================ViewGroup.javaprotected void measureChildWithMargins(View child,        int parentWidthMeasureSpec, int widthUsed,        int parentHeightMeasureSpec, int heightUsed) {    //获取子类的LayoutParams    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();    //getChildMeasureSpec的第二个参数,是已经用掉了多少空间。    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);}//根据父View的spec,padding+margin已消耗的空间,子View的LayoutParam最终确定子View的specpublic static int getChildMeasureSpec(int spec, int padding, int childDimension) {    //父View mode    int specMode = MeasureSpec.getMode(spec);    //父View size    int specSize = MeasureSpec.getSize(spec);    //如果父View的大小-已消耗空间 = 负数了,子View的大小就设为0    int size = Math.max(0, specSize - padding);    int resultSize = 0;    int resultMode = 0;    //判断父View的mode    switch (specMode) {    // Parent has imposed an exact size on us    case MeasureSpec.EXACTLY:        if (childDimension >= 0) {            resultSize = childDimension;            resultMode = MeasureSpec.EXACTLY;        } else if (childDimension == LayoutParams.MATCH_PARENT) {            // Child wants to be our size. So be it.            resultSize = size;            resultMode = MeasureSpec.EXACTLY;        } else if (childDimension == LayoutParams.WRAP_CONTENT) {            // Child wants to determine its own size. It can't be            // bigger than us.            resultSize = size;            resultMode = MeasureSpec.AT_MOST;        }        break;    // Parent has imposed a maximum size on us    case MeasureSpec.AT_MOST:        if (childDimension >= 0) {            // Child wants a specific size... so be it            resultSize = childDimension;            resultMode = MeasureSpec.EXACTLY;        } else if (childDimension == LayoutParams.MATCH_PARENT) {            // Child wants to be our size, but our size is not fixed.            // Constrain child to not be bigger than us.            resultSize = size;            resultMode = MeasureSpec.AT_MOST;        } else if (childDimension == LayoutParams.WRAP_CONTENT) {            // Child wants to determine its own size. It can't be            // bigger than us.            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.measure(int, int)方法是final类型的,意味着子类均不可重写。所以对于不同的View测量自身,需要重写onMeasure方法。不同的ViewGroup有不同布局策略(比如对与一个wrap_content的ViewGroup布局,LinearLayout是对子View顺序摆放,不会重叠,所以需要的空间是累加的,而FrameLayout只需要求子View空间的最大值就行),所以ViewGroup有不同的onMeasure方法。

 

这里还有一个重要的前提,就是对于ViewGroup,是有义务测量自己的子View的。Android正式通过这种递归方式,才使得代理里只需要对DecorView操作,自然会递归到最上层的子View。

子View的MeasureSpec是根据父View的MeasureSpec和自己的LayoutParams共同决定的。LayoutParams也就是我们平时在xml中写的layout_widht, layout_height, layout_gravity等这些带layout_前缀的属性。根据这两种排列组合,可以有这几种最终结果。

 

竖排父View\横排子View childDimension >= 0 childDimension == LayoutParams.MATCH_PARENT childDimension == LayoutParams.WRAP_CONTENT
MeasureSpec.EXACTLY 准确值|子View大小 准确值|父View大小-padding-margin 最大值|父View大小-padding-margin
MeasureSpec.AT_MOST 准确值|子View大小 最大值|父View大小-padding-margin 最大值|父View大小-padding-margin
MeasureSpec.UNSPECIFIED 准确值|子View大小 未指定|0 未指定|0

这几种结果最终也是以MeasureSpec呈现,然后会通过调用到子View的measure方法,把childWidthMeasureSpec和childHeightMeasureSpec传到了子View里。到此完成了一次循环。之后就是不断重复此循环,直到某个View再也没有子View为止。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}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;}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;}

上面是View的onMeasure方法实现,根据文档说明,在此方面必须调用了setMeasureDimension,主要是为了给属性mMeasuredWidth和mMeasuredHeight赋值。后面的布局会使用到这两个属性。

 

不同的View有不同的onMeasure方法,它应该根据传进来的MeasureSpec来补充自己的测量逻辑。一个正常的View如果拿到MeasureSpec的mode为准确值(MeasureSpec.EXACTLY),它应该直接设置大小为size大小。如果拿到的MeasureSpec的mode为最大值(MeasureSpec.AT_MOST),它应该自己根据自身需要测量自身内容,比如TextView就是测量文字需要占据多少空间,最终结果取自身测量结果和size的最小值,也就是所谓的不要超过MeasureSpec要求的最大值。

对于ViewGroup,需要把子View都测量完才会最终确定确定自己的尺寸。如上面的例子说的,如果是个wrap_content的LinearLayout需要测量完所有子View占据多少空间才能知道自己应该多大。

 

 

 

 

2.performLayout

onMeasure方法中会给赋值,在layout这步骤就会使用到

ViewRootImpl.javaprivate void performTraversals() {    ...    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);    ...    performLayout(lp, desiredWindowWidth, desiredWindowHeight);    ...    performDraw();    ...}private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,        int desiredWindowHeight) {    ...    final View host = mView;    ...    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());    ...}==================================================================================ViewGroup.java@Overridepublic final void layout(int l, int t, int r, int b) {    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {        if (mTransition != null) {            mTransition.layoutChange(this);        }        super.layout(l, t, r, b);    } else {        // record the fact that we noop'd it; request layout when transition finishes        mLayoutCalledWhileSuppressed = true;    }}@Overrideprotected abstract void onLayout(boolean changed,        int l, int t, int r, int b);==================================================================================View.javapublic void layout(int l, int t, int r, int b) {    ...    boolean changed = isLayoutModeOptical(mParent) ?                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);    ...        onLayout(changed, l, t, r, b);    ...}protected boolean setFrame(int left, int top, int right, int bottom) {    ...        mLeft = left;        mTop = top;        mRight = right;        mBottom = bottom;    ...}

performLayout里面会有其他功能的代码,比如一定情况下,使用desiredWindowWidth和desiredWindowHeight重新执行测量流程。但是重点是在执行host这个DecorView的Layout方法。Layout方法里会调用到setFrame方法,里面会对四个属性赋值,mLeft、mTop、mRight和mBottom。这4个属性是最终确定该View边界的字段。需要注意的是四个值是相对父View的位置,而不是整个屏幕的。

然后是流程是ViewGroup.layout -> View.layout -> onLayout方法。如果是继承ViewGroup,就需要实现onLayout方法。因为ViewGroup.onLayout是抽象类型的。如果是继承View,则无需实现onLayout。

对于DecorView,会执行到FrameLayout.onLayout方法。FrameLayout是ViewGroup,所以需要确定所有子View的位置。在layouChildren里,会根据布局的Gravity属性进行子View的摆放。同样也是递归,直到View再也没有子View停止。

@Overrideprotected 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();    mForegroundBoundsChanged = true;        for (int i = 0; i < count; i++) {        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);        }    }}

 

 

 

 

 

3.performDraw

ViewRootImpl.javaprivate void performTraversals() {    ...    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);    ...    performLayout(lp, desiredWindowWidth, desiredWindowHeight);    ...    performDraw();    ...}private void performDraw() {    ...    draw(fullRedrawNeeded);    ...}private void draw(boolean fullRedrawNeeded) {    ...    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.draw(canvas);    ...}==================================================================================View.javapublic 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);        // Step 6, draw decorations (scrollbars)        onDrawScrollBars(canvas);        if (mOverlay != null && !mOverlay.isEmpty()) {            mOverlay.getOverlayView().dispatchDraw(canvas);        }        // we're done...        return;    }    /*     * Here we do the full fledged routine...     * (this is an uncommon case where speed matters less,     * this is why we repeat some of the tests that have been     * done above)     */    boolean drawTop = false;    boolean drawBottom = false;    boolean drawLeft = false;    boolean drawRight = false;    float topFadeStrength = 0.0f;    float bottomFadeStrength = 0.0f;    float leftFadeStrength = 0.0f;    float rightFadeStrength = 0.0f;    // Step 2, save the canvas' layers    int paddingLeft = mPaddingLeft;    final boolean offsetRequired = isPaddingOffsetRequired();    if (offsetRequired) {        paddingLeft += getLeftPaddingOffset();    }    int left = mScrollX + paddingLeft;    int right = left + mRight - mLeft - mPaddingRight - paddingLeft;    int top = mScrollY + getFadeTop(offsetRequired);    int bottom = top + getFadeHeight(offsetRequired);    if (offsetRequired) {        right += getRightPaddingOffset();        bottom += getBottomPaddingOffset();    }    final ScrollabilityCache scrollabilityCache = mScrollCache;    final float fadeHeight = scrollabilityCache.fadingEdgeLength;    int length = (int) fadeHeight;    // clip the fade length if top and bottom fades overlap    // overlapping fades produce odd-looking artifacts    if (verticalEdges && (top + length > bottom - length)) {        length = (bottom - top) / 2;    }    // also clip horizontal fades if necessary    if (horizontalEdges && (left + length > right - length)) {        length = (right - left) / 2;    }    if (verticalEdges) {        topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));        drawTop = topFadeStrength * fadeHeight > 1.0f;        bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));        drawBottom = bottomFadeStrength * fadeHeight > 1.0f;    }    if (horizontalEdges) {        leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));        drawLeft = leftFadeStrength * fadeHeight > 1.0f;        rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));        drawRight = rightFadeStrength * fadeHeight > 1.0f;    }    saveCount = canvas.getSaveCount();    int solidColor = getSolidColor();    if (solidColor == 0) {        final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;        if (drawTop) {            canvas.saveLayer(left, top, right, top + length, null, flags);        }        if (drawBottom) {            canvas.saveLayer(left, bottom - length, right, bottom, null, flags);        }        if (drawLeft) {            canvas.saveLayer(left, top, left + length, bottom, null, flags);        }        if (drawRight) {            canvas.saveLayer(right - length, top, right, bottom, null, flags);        }    } else {        scrollabilityCache.setFadeColor(solidColor);    }    // Step 3, draw the content    if (!dirtyOpaque) onDraw(canvas);    // Step 4, draw the children    dispatchDraw(canvas);    // Step 5, draw the fade effect and restore layers    final Paint p = scrollabilityCache.paint;    final Matrix matrix = scrollabilityCache.matrix;    final Shader fade = scrollabilityCache.shader;    if (drawTop) {        matrix.setScale(1, fadeHeight * topFadeStrength);        matrix.postTranslate(left, top);        fade.setLocalMatrix(matrix);        p.setShader(fade);        canvas.drawRect(left, top, right, top + length, p);    }    if (drawBottom) {        matrix.setScale(1, fadeHeight * bottomFadeStrength);        matrix.postRotate(180);        matrix.postTranslate(left, bottom);        fade.setLocalMatrix(matrix);        p.setShader(fade);        canvas.drawRect(left, bottom - length, right, bottom, p);    }    if (drawLeft) {        matrix.setScale(1, fadeHeight * leftFadeStrength);        matrix.postRotate(-90);        matrix.postTranslate(left, top);        fade.setLocalMatrix(matrix);        p.setShader(fade);        canvas.drawRect(left, top, left + length, bottom, p);    }    if (drawRight) {        matrix.setScale(1, fadeHeight * rightFadeStrength);        matrix.postRotate(90);        matrix.postTranslate(right, top);        fade.setLocalMatrix(matrix);        p.setShader(fade);        canvas.drawRect(right - length, top, right, bottom, p);    }    canvas.restoreToCount(saveCount);    // Step 6, draw decorations (scrollbars)    onDrawScrollBars(canvas);    if (mOverlay != null && !mOverlay.isEmpty()) {        mOverlay.getOverlayView().dispatchDraw(canvas);    }}

类似的,也调用到的DecorView.draw方法,DecorView和FrameLayout都有draw方法的实现。但最重要的部分还是写在了View.draw里面,里面的代码不少,但是通过注释配合代码还是能知道其过程。

1.是绘制背景

2.第二步保存图层信息

3.绘制自身内容

4.绘制子View内容

5.绘制View的边缘渐变内容,还原图层

6.绘制装饰比如滚动条

第三步绘制自身内容是调用了onDraw方法,所以对于ViewGroup一般不用重写这方法。第四步调用到dispatchDraw。

@Overrideprotected void dispatchDraw(Canvas canvas) {    ...    // Draw any disappearing views that have animations    if (mDisappearingChildren != null) {        final ArrayList disappearingChildren = mDisappearingChildren;        final int disappearingCount = disappearingChildren.size() - 1;        // Go backwards -- we may delete as animations finish        for (int i = disappearingCount; i >= 0; i--) {            final View child = disappearingChildren.get(i);            more |= drawChild(canvas, child, drawingTime);        }    }    ...}protected boolean drawChild(Canvas canvas, View child, long drawingTime) {    return child.draw(canvas, this, drawingTime);}

里面会遍历所有的子View,调用到子View.draw方法,开始下一层的递归绘制。

对于常用的几个 LinearLayout、FrameLayout、RelativeLayout、ConstraintLayout,看了下源码发现,只有LinearLayout有重写 onDraw 方法用来画一个叫 Divider 的东西(没怎么听过啊),其他的都没有自己的实现。也很合理,对于 ViewGroup 这种容器,基本是没什么东西需要绘制的,所以把「绘制背景」这项功能放到 draw() 方法就行了。

 

 

 

更多相关文章

  1. android studio编写运行java main的三种方法(亲测)
  2. Android(安卓)Canvas清屏失效
  3. Android(安卓)ADB server didn't ACK 错误解决
  4. 【Android】TextView 显示超链接的几种方法
  5. Android(安卓)中部分文字高亮显示方法
  6. Android面试宝典(更新中)
  7. Android(安卓)WebView 详细介绍
  8. android之AsyncQueryHandler详解
  9. Surface与SurfaceHolder.Callback

随机推荐

  1. XUI 一个简洁而又优雅的Android原生UI框
  2. 学习Android从0开始之基础篇(1)-Android的
  3. android:TextView中的文本链接之--链接的
  4. 解决Android(安卓)library projects cann
  5. Google Android(安卓)SDK开发范例大全
  6. :45套精美的 ( Android, iPhone, iPad )
  7. android camera(四):camera 驱动 GT2005
  8. 小白学习android(一):android开发常识问题
  9. Android的自动测试研究--Robotium
  10. Qt on Android Episode 3(翻译)