View的绘制-draw流程详解

栏目: Android · 发布时间: 4年前

内容简介:根据 measure 测量出的宽高,layout 布局的位置,渲染整个 View 树,将界面呈现出来。以下源码基于版本27在
View的绘制-draw流程详解

作用

根据 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);
    }
}
复制代码

以上流程,推荐两篇文章: ViewRootImpl的performDraw过程 ~~~~~~~~~~~~~~~~~浅谈ondraw的前世今身

View 的 draw 流程

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

drawBackground
onDraw
dispatchDraw
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 流程也就结束了。

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的调用时机吗


以上所述就是小编给大家介绍的《View的绘制-draw流程详解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

高效前端:Web高效编程与优化实践

高效前端:Web高效编程与优化实践

李银城 著 / 机械工业出版社 / 2018-3-15 / 89.00元

这不是一本单纯讲解前端编程技巧的书,而是一本注重思想提升和内功修炼的书。 全书以问题为导向,精选了前端开发中的34个疑难问题,从分析问题的原因入手,逐步给出解决方案,并分析各种方案的优劣,最后针对每个问题总结出高效编程的最佳实践和各种性能优化的方法。 全书共7章,内容从逻辑上大致可以分为两大类: 第一类,偏向实践,围绕HTML、CSS、JavaScript等传统前端技术,以及PW......一起来看看 《高效前端:Web高效编程与优化实践》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具