View的绘制流程之四:View绘制的三个方法分析

View绘制的三大方法分析

在上一篇文章《View的绘制流程之三:View的绘制流程源码分析》中,我们已经知道了 ViewRootImpl调用 performTraversals() 方法绘制 View的整个流程,现在我们对其中的绘制 View的三大方法:performMeasure() 方法、performLayout() 方法、performDraw() 方法进行分析


一、performMeasure()方法

1、知识储备

performMeasure() 方法的流程图:
performMeasure() 方法

2、流程分析

Step 1:

我们看到 ViewRootImpl 的 performMeasure() 方法
★ ViewRootImpl # performMeasure()

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

mView 就是我们的 DecorView ,由于 measure() 方法是一个 final 方法,所以 DecorView 、FrameLayout 、ViewGroup 都无法重写,所以我们直接看到 View 的 measure() 方法

Step 2:

继续看到 View 的 measure() 方法
★ View # measure()

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ....
    if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {
        ....
        if (cacheIndex < 0 || sIgnoreMeasureCache) {

            // 这个方法会由子View进行重写,用于测量自身的宽高
            onMeasure(widthMeasureSpec, heightMeasureSpec);

            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }....
        ....
    }
    ....
}

View 的 measure() 方法会调用其 onMeasure() 方法,View 一般都会重写这个方法,在这个方法中加入自己的测量逻辑,所以我们现在直接看到 DecorView 的 onMeasure() 方法
★ DecorView # onMeasure()

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 1、获取屏幕的宽高
    final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
    // 通过屏幕宽高判断当前手机是横屏还是竖屏,isPortrait为true就是竖屏
    final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;

    // 2、会根据宽高的模式是AT_MOST还会对其进行额外的处理
    final int widthMode = getMode(widthMeasureSpec);
    final int heightMode = getMode(heightMeasureSpec);
    boolean fixedWidth = false;
    if (widthMode == AT_MOST) {
        ....
    }
    if (heightMode == AT_MOST) {
        ....
    }
    ....

    // 3、DecorView完成了自己的测量逻辑后就直接交给父类FrameLayout遍历子View进行测量
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    // 4、因为已经在前面对宽度进行了测量,所以这里可以获取到测量后的宽度
    // 下面会对宽度进行一些适配
    int width = getMeasuredWidth();
    boolean measure = false;
    widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
    if (!fixedWidth && widthMode == AT_MOST) {
        ....
    }
    // TODO: Support height?
    if (measure) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

首先,获取屏幕的宽高,对于竖屏和横屏,DecorView 有不一样的处理机制,所以要进行判断
然后,如果宽高的测量模式是 AT_MOST,还会对 DecorView进行一些额外的处理
接着,当 DecorView 完成了自己的测量逻辑后就会把测量流程交给父类 FrameLayout,让其遍历测量子View 的宽高
最后,当完成了子View 的测量后,DecorView 会对测量好的宽度进行一些适配

Step 3:

那么现在我们看到 FrameLayout ,看一下它是怎么测量子View的
★ FrameLayout # onMeasure()

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    // 1、如果父布局的宽高有一个不是EXACTLY模式(也就是大小是MATCH_PARENT),那么会对子View中
    // 同样有 MATCH_PARENT的宽或高进行重新测量
    final boolean measureMatchParentChildren =
            MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
            MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    mMatchParentChildren.clear();
    ....
    // 2、遍历所有子View,也就是说这里会对 Activity的Layout布局进行测量
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        // 3、只要View不是GONE都会对该View进行测量
        if (mMeasureAllChildren || child.getVisibility() != GONE) {

            // 4、测量子View的宽高
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            // 5、统计所有子View所占据的宽高
            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());
            // 6、判断是否需要添加宽或高为 MATCH_PARENT的子View到列表中,然后稍后对这些View重新测量
            if (measureMatchParentChildren) {
                if (lp.width == LayoutParams.MATCH_PARENT ||
                        lp.height == LayoutParams.MATCH_PARENT) {
                    mMatchParentChildren.add(child);
                }
            }
        }
    }
    ....
    // 7、测量完所有子View的宽高后就设置父布局的宽高
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
    // 8、对宽或高为 MATCH_PARENT的子View重新测量一遍
    count = mMatchParentChildren.size();
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            final View child = mMatchParentChildren.get(i);
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            ....
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}

FrameLayout 的测量流程如下:
第一步:如果父布局的宽高有一个不是 EXACTLY 模式(也就是大小为 MATCH_PARENT),那么就设置 measureMatchParentChildren 为true,作用是在完成测量子 后会对宽或高同样是 MATCH_PARENT 的 View 重新进行测量;这就意味着一个 View有可能会测量两次以上
第二步:遍历测量所有子 View

第三步:判断子View是否为 GONE,如果是那么就不会对 View 进行测量,除非通过 setMeasureAllChildren() 方法设置 mMeasureAllChildren 为true, mMeasureAllChildren 默认为 false,这样即使 View 是 GONE也会对其进行测量

第四步:通过 measureChildWithMargins() 方法测量子View的宽高

第五步:统计所有子View的宽高,maxWidth 和 maxHeight 最后会用于设置 FrameLayout (也就是 DecorView)的宽高

第六步:判断 measureMatchParentChildren 是否为true,如果是就将宽或高为 MATCH_PARENT 的子View加入到集合,等待重新测量

第七步:测量完所有的子View就通过 setMeasuredDimension() 方法设置 FrameLayout (也就是 DecorView)的宽高

第八步:对宽或高为 MATCH_PARENT 的子View重新测量一遍
 

小结:

  • 如果View的宽或高是MATCH_PARENT ,那么这个View会测量至少两次
  • View 是 GONE 是不会对其进行测量,除非通过在该View的父布局中使用 setMeasureAllChildren(true) 方法

Step 4:

现在我们继续看到 ViewGroup 的 measureChildWithMargins() 方法
★ ViewGroup # measureChildWithMargins()

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    // 第1个参数:父布局的宽度测量规格
    // 第2个参数:View的外面的padding值,包括父布局的padding值、子View的margin值、已经使用了的宽度值
    // 第3个参数:子View的宽度
    // 该方法用于根据以上参数获取子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进行测量宽高
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

首先,会调用 getChildMeasureSpec() 方法计算出子View宽高的测量规格
然后,调用子View的 measure() 方法完成最后的测量工作(子View:如我们 Activity的布局文件的根布局,该方法就是根布局的 measure() 方法)

Step 5:

最后,我们看到 ViewGroup 的 getChildMeasureSpec() 方法,这个方法会根据父布局的宽高测量规格为子View创建不同的 MeasureSpec
★ ViewGroup # getChildMeasureSpec()

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    // 父布局的测量模式
    int specMode = MeasureSpec.getMode(spec);
    // 父布局的宽高大小
    int specSize = MeasureSpec.getSize(spec);
    int size = Math.max(0, specSize - padding);
    int resultSize = 0;
    int resultMode = 0;
    // 根据父布局的测量模式指定子View的宽高
    switch (specMode) {
        case MeasureSpec.EXACTLY:
            ....
            break;
        case MeasureSpec.AT_MOST:
            ....
            break;
        case MeasureSpec.UNSPECIFIED:
            ....
            break;
    }
    // 最后生成一个宽度或高度的测量规格
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}


3、总结

performMeasure() 方法的绘制流程如下:

ViewRootImpl # performMeasure()  -->
View # measure() -->
PhoneWindow.DecorView # onMeasure()  -->
FrameLayout # onMeasure()  -->
ViewGroup # measureChildWidthMargins()  -->
     ┖→ ViewGroup # getChildMeasureSpec()
Activity布局中的根布局 # measure()




二、performLayout()方法

1、知识储备

第一:流程图

performLayout() 方法的流程图:
performLayout() 方法

第二:问题解答

View 的 layout()方法和 onLayout()方法,这个两个方法有什么区别???
layout() 方法是 View里面的方法,它会对 View的大小和位置进行检测,同时调用 onLayout()方法将 View大小和位置是否发生变化、View的左上右下四个位置传递给View,所以一般来说我们自定义 View都是重写 onLayout()方法,而不是直接重写 layout() 方法


2、流程分析

Step 1:

我们看到 ViewRootImpl 的 performLayout() 方法
★ ViewRootImpl # performLayout()

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                           int desiredWindowHeight) {
    ....
    try {
        // 调用view的Layout()方法进行摆放布局
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ....
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
}

由于已经对 host 进行了测量,所以这里可以直接获取测量的宽高,因为我们分析的是 Activity 布局的加载流程,所以这里的 host 是一个 DecorView,但是 DecorView 以及其父类 FrameLayout 都没有重写这个方法,所以我们直接看到 View 的 layout() 方法

Step 2:

看到 View 的 layout() 方法
★ View # layout()

@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
    .....
    // 如果View的大小或者位置发生改变,那么changed就为true
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        // 调用View子类的onLayout()方法,如果View继承了ViewGroup,那么就会要求重写这个方法
        // 对其子View进行摆放,所以DecorView会重写这个方法
        onLayout(changed, l, t, r, b);

        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>) li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }
    ....
}

onLayout()方法在 View和 ViewGroup中的区别:
(1)对于单一View :一般都不会重写 onLayout()方法,因为不会对 View的位置进行更改,除非是要在这个方法完成某些操作,如在该方法中获取 View测量的宽高
(2)对于ViewGroup :onLayout()方法在 ViewGroup被重定义为抽象方法,所以子类必须要实现该方法,完成对子View位置的摆放

由于我们分析的是 Activity布局的加载流程,所以当前的 View就是 DecorView,所以我们看到 DecorView的 onLayout()方法中

Step 3:

看到 DecorView的 onLayout()方法
★ PhoneWindow.DecorView # onLayout()

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    getOutsets(mOutsets);
    if (mOutsets.left > 0) {
        offsetLeftAndRight(-mOutsets.left);
    }
    if (mOutsets.top > 0) {
        offsetTopAndBottom(-mOutsets.top);
    }
}

★ 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 */);
}
★  FrameLayout   #   layoutChildren()
void layoutChildren(int left, int top, int right, int bottom,
                              boolean forceLeftGravity) {
    final int count = getChildCount();
    ....
    // 遍历子View,对子View进行摆放
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        // 只要子View不是GONE对会进行摆放,也就是说当我们设置了View为GONE,有可能
        // 就会对布局进行重新摆放,除去设置为GONE的View
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();
            int childLeft;
            int childTop;

            ....
            // 下面是计算摆放位置的left和top
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                ....
            }
            switch (verticalGravity) {
                ....
            }
            // 最后调用子View的layout()方法,该子View有可能是一个View或者一个ViewGroup,所以
            // 它又会重新走一遍layout()方法的流程,不断递归下去,直到子View不是ViewGroup
            // 现在分析的Activity的布局加载,所以这个child应该也是我们Activity布局文件中的根布局
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

可以看到 FrameLayout会遍历调用子 View的 layout()方法对子 View进行摆放,也就是说这里会调用到 Activity布局中的根布局的 layout()方法,但是一般来说,无论是单个 View或者ViewGroup都不会重写 layout()方法的,而是将 layout()方法交个 View进行处理,然后让 View调用单个 View或者ViewGroup的 onLayout()方法,所以在这之后就会调用 Activity布局中根布局 onLayout()方法,至此,performLayout() 方法分析完毕


3、总结

performLayout() 方法的调用顺序:

ViewRootImpl # performLayout()
View # layout()
DecorView # onLayout()
FrameLayout # onLayout()
FrameLayout # layoutChildren()
Activity布局中的根布局 # layout()






三、performDarw()方法

1、知识储备

第一:流程图

performDraw() 方法的流程图:
performDraw() 方法
 

第二:问题解答

View 的 draw()方法和 onDraw()方法,这个两个方法有什么区别???
draw()方法:

  • 对于View:会绘制背景、内容、装饰(如滚动条);
  • 对于ViewGroup:会绘制背景、内容、分发绘制事件给子View、装饰(如滚动条);

onDraw()方法:

  • 这个方法只是完成 draw()方法功能中内容绘制这一块的工作,实现View自身内容的绘制(不包含子View)



2、流程分析

Step 1:

我们看到 performDraw() 方法
★ ViewRootImpl # performDraw()

private void performDraw() {
    ....
    // 标记正在绘制View
    mIsDrawing = true;
    try {
        // 调用 ViewRootImpl的 draw()方法
        draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    ....
    if (mReportNextDraw) {
        ....
        try {
            mWindowSession.finishDrawing(mWindow);
        } catch (RemoteException e) {
        }
    }
}

我们继续看到 ViewRootImpl的 draw() 方法
★ ViewRootImp # draw()

private void draw(boolean fullRedrawNeeded) {
    // 获取 ViewRootImpl中的 Surface对象
    Surface surface = mSurface;
    if (!surface.isValid()) {
        return;
    }
    ....
    // 通知注册了观察View绘制的监听器,将要开始绘制view
    mAttachInfo.mTreeObserver.dispatchOnDraw();
    ....
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
            ....
        } else {
            ....
            // 调用 drawSoftware()方法绘制View
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        }
    }
    ....
}

可以看到,View的绘制跟测量、摆放的流程有些不一样
首先,会获取 ViewRootImpl中的 Surface对象,这个对象用于获取 canvas画布
然后,会通知注册了View draw过程的监听器,传递View开始绘制这一事件
最后,会调用 drawSoftware()方法开始绘制View

Step 2:

接着,我们看到 drawSoftware()方法
★ ViewRootImpl # drawSoftware()

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                             boolean scalingRequired, Rect dirty) {
    final Canvas canvas;
    try {
        ....
        // 1、从suface对象中获取canvas对象,同时锁住画布,保证canvas对象的线程安全
        canvas = mSurface.lockCanvas(dirty);
        ....
        // 2、指定canvas布局支持的bitmap的密度
        canvas.setDensity(mDensity);
    } ....
    try {
        ....
        try {
            ....
            // 3、设置屏幕密度
            canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
            attachInfo.mSetIgnoreDirtyState = false;
            // 4、如果子View没有重写该方法的话就会调用View的draw()方法
            // 但是 DecorView对该方法进行了重写
            mView.draw(canvas);
            drawAccessibilityFocusedDrawableIfNeeded(canvas);
        } ....
    } finally {
        try {
            // 5、解锁画布
            surface.unlockCanvasAndPost(canvas);
        } ....
    }
    return true;
}

该方法会完成以下操作:
(1)从 Surface中获取canvas画布,并锁定该画布,保证画布的线程安全
(2)指定 canvas画布支持的 bitmap位图的密度
(3)指定屏幕密度
(4)调用 View的draw()方法绘制View
(5)解锁canvas画布

现在,一般来说我们都不会重写View的draw()方法,但是 DecorView有些特殊,它重写了draw()方法,所以接下来我们看到该方法

Step 3:

我们看到 DecorView的 draw()方法中
★ PhoneWindow.DecorView # draw()

@Override
public void draw(Canvas canvas) {
    super.draw(canvas);
    // 如果有菜单背景那么就绘制菜单背景
    if (mMenuBackground != null) {
        mMenuBackground.draw(canvas);
    }
}

在 DecorView的 draw()方法中会判断是否需要绘制菜单背景;由于其父类没有重写 draw()方法,所以我们看到 View的 draw()方法
★ View # draw()

@CallSuper
public void draw(Canvas canvas) {
    ....
    // 1、如果有需要就会绘制背景,所以优化View可以尝试尽量避免给View设置不必要的背景
    if (!dirtyOpaque) {
        drawBackground(canvas);
    }
    // 2、如果有必要,保存画布的图层以准备渐变
    int paddingLeft = mPaddingLeft;
    final boolean offsetRequired = isPaddingOffsetRequired();
    .....
    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);
    }
    // 3、调用onDraw()方法开始绘制内容,这个方法会由子View进行重写
    if (!dirtyOpaque) onDraw(canvas);
    // 4、分发draw事件给所有子View,然后绘制子View
    dispatchDraw(canvas);
    // 5、绘制渐变效果并恢复画布的图层
    final Paint p = scrollabilityCache.paint;
    final Paint p = scrollabilityCache.paint;
    final Matrix matrix = scrollabilityCache.matrix;
    final Shader fade = scrollabilityCache.shader;
    if (drawTop) {
        ....
    }
    if (drawBottom) {
        ....
    }
    if (drawLeft) {
        ....
    }
    if (drawRight) {
        ....
    }
    canvas.restoreToCount(saveCount);
    ....
    // 6、绘制装饰,如:前景、滚动条
    onDrawForeground(canvas);
}

View的 draw()方法会完成以下操作:
(1)判断是否需要绘制背景,如果需要就先绘制背景,所以如果要优化View可以尝试避免给View设置不必要的背景
(2)如果有必要,保存画布的图层以准备渐变
(3)调用 onDraw()方法开始绘制View的内容,这个方法由子View进行重写,View中默认为空实现
(4)调用方法分发绘制事件给子View,该方法会由ViewGroup方法进行重写,在View中默认为空实现
(5)绘制渐变效果并恢复画布的图层
(6)绘制一些装饰,如前景、滚动条

draw()方法中有两个比较重要的方法 onDraw()方 和 dispatchDraw();由于 DecorView是实现了 onDraw()方法,我们先看到该方法
★ DecorView # onDraw()

@Override
public void onDraw(Canvas c) {
    super.onDraw(c);
    mBackgroundFallback.draw(mContentRoot, c, mContentParent);
}

其父类都没有重写该方法,我们继续看到 View的 onDraw()方法,这是一个空实现

★ View  #  onDraw()
protected void onDraw(Canvas canvas) {
}


Step 4:

继续看到 dispatchDraw()方法,由于 DecorView、FrameLayout都没有重写该方法,所以我们直接看到ViewGroup中
★ ViewGroup # dispatchDraw()

@Override
protected void dispatchDraw(Canvas canvas) {
    ....
    // 遍历所有子View,调用 drawChild()方法绘制子View
    for (int i = 0; i < childrenCount; i++) {
        while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
            // 获取子View
            final View transientChild = mTransientViews.get(transientIndex);
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                    transientChild.getAnimation() != null) {

                // 绘制子View
                more |= drawChild(canvas, transientChild, drawingTime);
            }
            transientIndex++;
            if (transientIndex >= transientCount) {
                transientIndex = -1;
            }
        }
        ....
    }
    ....
}

继续看到 drawChild()方法

★  ViewGroup  #  drawChild()
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

可见,这里会调用子View的 draw()方法,也就是说这里会调用 Activity布局中根布局的 draw()方法,但是一般View都不会重写该方法,所以接着会调用 Activity布局中根布局的 onDraw()方法


3、总结

performDraw() 方法的绘制流程如下:

ViewRootImpl # performDraw()  -->
ViewRootImpl # draw() -->
ViewRootImpl # drawSoftware() -->
DecorView # draw() --> 
View # draw() --> 
    drawBackground()
    onDraw()
    dispatchDraw()  --> 
    onDrawForeground()
ViewGroup # dispatchDraw() --> 
ViewGroup # drawChild()  --> 
ViewGroup # draw(3个参数)  -->
View # draw() --> 
Activity布局中的根布局 # onDraw()




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值