一、前言

前面已经介绍了自定义View的measure和layout过程,接下来进入到很重要的一步就是draw过程,用来对视图进行绘制,我们平时看到的各种View都需要经过draw过程才能在屏幕上显示出来!


二、流程分析

通常来说draw的过程会包括4部分,分别是绘制背景、绘制内容、绘制子View和绘制滚动条,如下图所示:

android 自定义view 只写一个构造函数报错 android自定义view流程_android


为了让大家更清楚的弄清楚这个流程,这里列出draw()方法的源码:

/**   
 * 手动的给View(和所有它的子View)制定的Canvas.在这个方法被调用之前这个View必须已经
 * 做了一个完整的布局。当重新绘制一个View,重载onDraw()方法代替重载这个方法。如果你要
 * 重载这个方法,先调用父View的这个方法
 * 
 * Manually render this view (and all of its children) to the given Canvas.
 * The view must have already done a full layout before this function is
 * called.  When implementing a view, implement
 * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
 * If you do need to override this method, call the superclass version.
 *
 * @param canvas The Canvas to which the View is rendered.
 */
public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;

    /*
     * Draw必须在合适的顺序下遍历执行下面的几个drawing步骤
     * 
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *      
     *      //绘制背景
     *      1. Draw the background
     *      
     *      //如果有必要,为fading去保存这个canvas的图层
     *      2. If necessary, save the canvas' layers to prepare for fading
     *      
     *      //绘制View的内容
     *      3. Draw view's content
     *      
     *      //绘制子View
     *      4. Draw children
     *     
     *      //如果有必要,绘制fading边框和恢复图层
     *      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) {
        final Drawable background = mBackground;
        if (background != null) {
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;

            if (mBackgroundSizeChanged) {
                background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
                mBackgroundSizeChanged = false;
            }

            if ((scrollX | scrollY) == 0) {
                background.draw(canvas);
            } else {
                canvas.translate(scrollX, scrollY);
                background.draw(canvas);
                canvas.translate(-scrollX, -scrollY);
            }
        }

  // 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
        //绘制子View
        dispatchDraw(canvas);

        // Step 6, draw decorations (scrollbars)
        //绘制滚动条
        onDrawScrollBars(canvas);

        // we're done...
        return;
    }

分析上面的代码可以看到

  • 第一步主要是用来对视图的背景进行绘制的,这里会先得到一个mBackground对象,然后根据layout过程确定的视图位置来设置背景的绘制区域,之后调用Drawable的draw()方法进行绘制,这里的mBackground对象其实是在XML中通过android:background属性设置的图片或者颜色,也可以是在代码中动态设置的setBackgroundColor()或setBackgroundResources()。
  • 第三步的作用是对视图的内容进行绘制,这里调用了onDraw()方法,而onDraw()方法本身是一个空方法,需要用户根据需要自己进行编写;
  • 第四步主要是对当前视图的所有子视图进行绘制,当然如果没有子视图就不需要进行绘制,所以对于View来说其dispatchDraw()方法是空的,而ViewGroup的dispatchDraw()方法中会有具体的实现代码。
  • 至于第六步主要是对视图的滚动条进行绘制,其实不管是对于常见的button还是ListView视图也好,他们都是有滚动条的,只是通常没有显示出来而已。

整个draw()过程其实需要我们重点关注的就是onDraw()过程,需要根据实际情况绘制不同的内容,下面举一个例子进行说明:

public class MyView extends View {
        private Paint mPaint;
        public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        }
        @Override
        protected void onDraw(Canvas canvas) {
            mPaint.setColor(Color.YELLOW);
            canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
            mPaint.setColor(Color.BLUE);
            mPaint.setTextSize(20);
            String text = "Hello";
            canvas.drawText(text, 0, getHeight() / 2, mPaint)
        }
    }

观察上面的源码,你会发现在绘制的时候调用了getWidth()和getHeight()方法,这里的两个方法返回的值是在onLayout()过程结束后获取到的视图的宽和高,因此canvas.drawRect(0, 0, getWidth(), getHeight())实际上绘制的区域就是layout的区域。


三、总结

至此,View绘制的三个过程都分析完了,后面将进行大量的自定义View实验来加深对自定义View的理解,本篇文章参考了郭霖大神的博客和浮城大亨的博客,感谢两位!

继续加油!