前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >View的绘制-draw流程详解

View的绘制-draw流程详解

作者头像
用户5546570
发布2019-06-06 10:36:05
1.3K0
发布2019-06-06 10:36:05
举报

目录

作用

根据 measure 测量出的宽高,layout 布局的位置,渲染整个 View 树,将界面呈现出来。

具体分析

以下源码基于版本27

DecorView 的draw 流程

《View的绘制-measure流程详解》中说过,View 的绘制流程是从 ViewRootViewImpl 中的 performMeasure()performLayoutperformDraw 开始的。在执行完 performMeasure()performLayout 后,开始执行 performDraw 方法:(以下源码有所删减)

//ViewRootViewImpl 类
 private void performDraw() {
    ....
    draw(fullRedrawNeeded);
    ....
 }
-------------------------------------------------------------------------
//ViewRootViewImpl 类
private void draw(boolean fullRedrawNeeded) {
    ....
    mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
    ....
}

-------------------------------------------------------------------------
//ThreadedRenderer 类
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    ....
    updateRootDisplayList(view, callbacks);
    ....
}
-------------------------------------------------------------------------
//ThreadedRenderer 类
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    ....
    updateViewTreeDisplayList(view);
    ....
}
-------------------------------------------------------------------------
//ThreadedRenderer 类
private void updateViewTreeDisplayList(View view) {
    view.mPrivateFlags |= View.PFLAG_DRAWN;
    view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
            == View.PFLAG_INVALIDATED;
    view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
    //这里调用了 View 的 updateDisplayListIfDirty 方法 
    //这个 View 其实就是 DecorView
    view.updateDisplayListIfDirty();
    view.mRecreateDisplayList = false;
}

接下来查看 View 的 updateDisplayListIfDirty 方法:

//View 类
 public RenderNode updateDisplayListIfDirty() {
    ....
    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
        dispatchDraw(canvas);
        drawAutofilledHighlight(canvas);
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().draw(canvas);
        }
        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
    } else {
        /*最终调用了 DecorView 的 draw 方法,为什么没有走上面的 dispatchDraw(canvas)
        我也母鸡啊,我是Debug 断点调试晓得走这里的,哈哈*/
        draw(canvas);
    }
    ....
}
-------------------------------------------------------------------------
//DecorView 重写了 draw 方法。所以走到了 DecorView 的 draw 方法
@Override
public void draw(Canvas canvas) {
    //调用父类 (View)的 draw 方法
    super.draw(canvas);

    if (mMenuBackground != null) {
        mMenuBackground.draw(canvas);
    }
}
View 的 draw 流程

就这样, View 的绘制就开始啦。主要有四个步骤:

  • drawBackground 绘制背景色
  • onDraw 绘制内容
  • dispatchDraw 绘制 children
  • onDrawForeground 绘制装饰(前景,滚动条)
//View 类
/**
 *手动将此视图(及其所有子项)渲染到给定的Canvas。在调用此函数前,视图必须已经完成了完整布局(layout)。
 *一般我们在自定义控件继承 View 的时候,不要重写 draw 方法,只需重写 onDraw 方法
 */
public void draw(Canvas canvas) {
    ....

    // 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
        //绘制 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;
    }
    ....
}

我们对四个步骤进行分析:

//View 类
//绘制背景
private void drawBackground(Canvas canvas) {
    final Drawable background = mBackground;
    //如果没有设置背景,就不进行绘制
    if (background == null) {
        return;
    }
    //如果设置了背景吗,且背景的大小发生了改变,
    //就用 layout 计算出的四个边界值来确定背景的边界
    setBackgroundBounds();

    // Attempt to use a display list if requested.
    if (canvas.isHardwareAccelerated() && mAttachInfo != null
            && mAttachInfo.mThreadedRenderer != null) {
        mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);

        final RenderNode renderNode = mBackgroundRenderNode;
        if (renderNode != null && renderNode.isValid()) {
            setBackgroundRenderNodeProperties(renderNode);
            ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
            return;
        }
    }

    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    if ((scrollX | scrollY) == 0) {
        //调用 Drawable 的 draw 方法来进行背景的绘制
        background.draw(canvas);
    } else {
        //平移画布
        canvas.translate(scrollX, scrollY);
        //调用 Drawable 的 draw 方法来进行背景的绘制
        background.draw(canvas);
        canvas.translate(-scrollX, -scrollY);
    }
}
-------------------------------------------------------------------------
//View 类
//绘制内容
protected void onDraw(Canvas canvas) {
    /*View 中的 onDraw 是一个空实现。也不难理解,当我们自定义控件继承 View 
    的时候,需要重写 onDraw 方法,通过 Canvas 和 Paint 来进行内容的绘制*/
}
-------------------------------------------------------------------------
//View 类
//绘制 children
protected void dispatchDraw(Canvas canvas) {
    /*View 中的 dispatchDraw 也是一个空实现。因为单独一个 View 
    本身是没有子元素的,不需要绘制 children */
}

-------------------------------------------------------------------------
//View 类
//绘制装饰
public void onDrawForeground(Canvas canvas) {
    //绘制指示器
    onDrawScrollIndicators(canvas);
    //绘制滚动条
    onDrawScrollBars(canvas);

    final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
    if (foreground != null) {
        if (mForegroundInfo.mBoundsChanged) {
            mForegroundInfo.mBoundsChanged = false;
            final Rect selfBounds = mForegroundInfo.mSelfBounds;
            final Rect overlayBounds = mForegroundInfo.mOverlayBounds;

            if (mForegroundInfo.mInsidePadding) {
                selfBounds.set(0, 0, getWidth(), getHeight());
            } else {
                selfBounds.set(getPaddingLeft(), getPaddingTop(),
                        getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
            }

            final int ld = getLayoutDirection();
            Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
                    foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
            foreground.setBounds(overlayBounds);
        }
        //调用 Drawable 的 draw 方法,绘制前景色
        foreground.draw(canvas);
    }
}

以上就是 View 的绘制流程了。ViewGroup 本身是继承 View 的,它的基本绘制流程也是通过父类 View 进行的,只不过它重写了 dispatchDraw 方法,来进行子元素的绘制。下面我们来进行具体分析:

ViewGroup 的绘制 dispatchDraw 流程
//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;
            }
        }

        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            //调用 drawChild 方法,进行绘制子元素
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    ....
}
-------------------------------------------------------------------------
//ViewGroup 类
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    //调用 View 的 draw 方法,这里要注意,调用的是 View 的三个参数的 draw 方法
    return child.draw(canvas, this, drawingTime);
}

在 View 中还有一个 draw(Canvas canvas) 的重载方法,就是 draw(Canvas canvas, ViewGroup parent, long drawingTime):

//View 类
/**
 * ViewGroup.drawChild()调用此方法以使每个子视图自己绘制。
 * 这是View专门根据图层类型和硬件加速来渲染行为的地方。
 */
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    ....
    //是否支持硬件加速
    boolean drawingWithRenderNode = mAttachInfo != null
            && mAttachInfo.mHardwareAccelerated
            && hardwareAcceleratedCanvas;

    if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
         if (layerType != LAYER_TYPE_NONE) {
             //未开启
             //调用 View 的 buildDrawingCache 方法
             buildDrawingCache(true);
        }
        cache = getDrawingCache(true);
    }
    //开启了硬件加速
    if (drawingWithRenderNode) {
        //调用 View 的 updateDisplayListIfDirty 方法
        renderNode = updateDisplayListIfDirty();
        if (!renderNode.isValid()) {
            // Uncommon, but possible. If a view is removed from the hierarchy during the call
            // to getDisplayList(), the display list will be marked invalid and we should not
            // try to use it again.
            renderNode = null;
            drawingWithRenderNode = false;
        }
    }
    ....
}

分别查看 buildDrawingCacheupdateDisplayListIfDirty 方法:

//View 类
public void buildDrawingCache(boolean autoScale) {
    ....
    buildDrawingCacheImpl(autoScale);
    ....
}
-------------------------------------------------------------------------
//View 类
private void buildDrawingCacheImpl(boolean autoScale) {
    ....
    // 如果不需要进行自身绘制,就直接调用 dispatchDraw 绘制子 Children
    //否则就直接调用 View 的 draw 方法
    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        dispatchDraw(canvas);
        drawAutofilledHighlight(canvas);
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().draw(canvas);
        }
    } else {
        draw(canvas);
    }
    ....
}
-------------------------------------------------------------------------

//View 类
public RenderNode updateDisplayListIfDirty() {
    ....
    // 如果不需要进行自身绘制,就直接调用 dispatchDraw 绘制子 Children
    //否则就直接调用 View 的 draw 方法
    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
        dispatchDraw(canvas);
        drawAutofilledHighlight(canvas);
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().draw(canvas);
        }
        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
    } else {
        draw(canvas);
    }
    ....
}

如此,从顶层 DecorView 的 draw 方法开始,然后调用 dispatchDraw 方法循环遍历绘制子元素,如果子元素是继承了 ViewGroup ,就再次循环调用 dispatchDraw 方法,一层层往下递归调用,直到每一个子元素都被绘制完成,整个 draw 流程也就结束了。

tips: 在我们使用真机进行源码断点调试的时候,可能会出现源码不能打断点的情况,或者断点没有走在该走的地方。这是因为国内手机厂商基本都是定制系统,可能修改了源码。这个时候可以使用模拟器进行断点调试。注意:模拟器版本号要和项目编译版本号一致!

setWillNotDraw 解析

在 View 中有一个方法是 setWillNotDraw:

//View 类
/**
 * If this view doesn't do any drawing on its own, set this flag to
 * allow further optimizations. By default, this flag is not set on
 * View, but could be set on some View subclasses such as ViewGroup.
 *
 * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
 * you should clear this flag.
 *
 * @param willNotDraw whether or not this View draw on its own
 */
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

从注释上看,如果此视图本身不执行任何绘制,就设置为 true,系统会进行一些绘制优化。View 本身是默认设置为 false 的,没有启动这个优化标记(这也不难理解,因为一般我们自定义控件继承 View 的时候,是要重写 onDraw 方法进行绘制的)。ViewGroup 默认是开启这个优化标记的。当然如果明确 ViewGroup 是要通过 onDraw 方法进行绘制的时候,要手动关闭这个标记( setWillNotDraw(false) )。

示例:

我们自定义一个控件,继承 ViewGroup,重写 onDraw 方法。

public class MyViewGroup extends ViewGroup {
    public MyViewGroup(Context context) {
        super(context);
        setWillNotDraw(false);
    }
    public MyViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        //这里如果不调用这句话,我们在使用的时候,onDraw 方法不会被调用
        setWillNotDraw(false);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //onLayout 在这里必须重写,因为在 ViewGroup 中 onLayout是一个抽象方法
    }
    //重写 onDraw 方法
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.BLACK);
    }
}

xml 中使用

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.ownchan.miclock.study.MyViewGroup
        android:layout_width="match_parent"
        android:background="@color/black"
        android:layout_height="match_parent"/>
</FrameLayout>

当我们的自定义控件在继承 ViewGroup 的时候,如果需要重写 onDraw 方法进行绘制,需要执行 setWillNotDraw(false)

推荐一个详解 draw 和 onDraw 调用时机好文: 你真的了解Android ViewGroup的draw和onDraw的调用时机吗

总结

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.04.25 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • 作用
  • 具体分析
    • DecorView 的draw 流程
      • View 的 draw 流程
        • ViewGroup 的绘制 dispatchDraw 流程
          • setWillNotDraw 解析
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档