深入了解View(三)—— draw绘制流程分析

layout流程简介

measure和layout的过程都结束后,接下来就进入到draw的过程了。同样,根据名字你就能够判断出,在这里才真正地开始对视图进行绘制。

draw同样是由ViewRoot的performTraversals方法发起,它将调用DecorView的draw方法,并把成员变量canvas传给给draw方法。而在后面draw遍历中,传递的都是同一个canvas。所以android的绘制是同一个window中的所有View都绘制在同一个画布上。等绘制完成,将会通知WMS把canvas上的内容绘制到屏幕上。

draw()方法内部的绘制过程总共可以分为六步:
1.绘制背景
2.如有必要,绘制渐变效果(通常不绘制)
3.调用onDraw
4.调用dispatchDraw
5.如有必要,绘制颜色渐变淡的边框,并恢复画布(即画布改变的内容附加到原有内容上)(通常不绘制)
6.调用onDrawScrollBars

其中第2步和第5步在一般情况下很少用到,因此这里我们只分析简化后的绘制过程。代码如下所示:

public void draw(Canvas canvas) {
    if (ViewDebug.TRACE_HIERARCHY) {
        ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
    }
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
    // Step 1, draw the background, if needed
    int saveCount;
    if (!dirtyOpaque) {
        final Drawable background = mBGDrawable;
        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);
            }
        }
    }
    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);
        // we're done...
        return;
    }
}

可以看到,第一步是从第9行代码开始的,这一步的作用是对视图的背景进行绘制。这里会先得到一个mBGDrawable对象,然后根据layout过程确定的视图位置来设置背景的绘制区域,之后再调用Drawable的draw()方法来完成背景的绘制工作。那么这个mBGDrawable对象是从哪里来的呢?其实就是在XML中通过android:background属性设置的图片或颜色。当然你也可以在代码中通过setBackgroundColor()、setBackgroundResource()等方法进行赋值。

接下来的第三步是在第34行执行的,这一步的作用是对视图的内容进行绘制。可以看到,这里去调用了一下onDraw()方法,那么onDraw()方法里又写了什么代码呢?进去一看你会发现,原来又是个空方法啊。其实也可以理解,因为每个视图的内容部分肯定都是各不相同的,这部分的功能交给子类来去实现也是理所当然的。

第三步完成之后紧接着会执行第四步,这一步的作用是对当前视图的所有子视图进行绘制。但如果当前的视图没有子视图,那么也就不需要进行绘制了。因此你会发现View中的dispatchDraw()方法又是一个空方法,而ViewGroup的dispatchDraw()方法中就会有具体的绘制代码。

以上都执行完后就会进入到第六步,也是最后一步,这一步的作用是对视图的滚动条进行绘制。那么你可能会奇怪,当前的视图又不一定是ListView或者ScrollView,为什么要绘制滚动条呢?其实不管是Button也好,TextView也好,任何一个视图都是有滚动条的,只是一般情况下我们都没有让它显示出来而已。绘制滚动条的代码逻辑也比较复杂,这里就不再贴出来了,因为我们的重点是第三步过程。

通过以上流程分析,相信大家已经知道,View是不会帮我们绘制内容部分的,因此需要每个视图根据想要展示的内容来自行绘制。如果你去观察TextView、ImageView等类的源码,你会发现它们都有重写onDraw()这个方法,并且在里面执行了相当不少的绘制逻辑。绘制的方式主要是借助Canvas这个类,它会作为参数传入到onDraw()方法中,供给每个视图使用。Canvas这个类的用法非常丰富,基本可以把它当成一块画布,在上面绘制任意的东西,那么我们就来尝试一下吧。

简单绘制示例

这里简单起见,我只是创建一个非常简单的视图,并且用Canvas随便绘制了一点东西,代码如下所示:

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 View";
        canvas.drawText(text, 0, getHeight() / 2, mPaint);
    }
}

可以看到,我们创建了一个自定义的MyView继承自View,并在MyView的构造函数中创建了一个Paint对象。Paint就像是一个画笔一样,配合着Canvas就可以进行绘制了。这里我们的绘制逻辑比较简单,在onDraw()方法中先是把画笔设置成黄色,然后调用Canvas的drawRect()方法绘制一个矩形。然后在把画笔设置成蓝色,并调整了一下文字的大小,然后调用drawText()方法绘制了一段文字。

就这么简单,一个自定义的视图就已经写好了,现在可以在XML中加入这个视图,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.example.viewtest.MyView 
        android:layout_width="200dp"
        android:layout_height="100dp"
        />

</LinearLayout>

将MyView的宽度设置成200dp,高度设置成100dp,然后运行一下程序,结果如下图所示:
深入了解View(三)—— draw绘制流程分析_第1张图片
图中显示的内容也正是MyView这个视图的内容部分了。由于我们没给MyView设置背景,因此这里看不出来View自动绘制的背景效果。

当然了Canvas的用法还有很多很多,这里我不可能把Canvas的所有用法都列举出来,剩下的就要靠大家自行去研究和学习了。

到此为止,我们把视图绘制流程的第三阶段也分析完了。整个视图的绘制过程就全部结束了。

部分节选自:http://blog.csdn.net/guolin_blog/article/details/16330267

你可能感兴趣的:(android,布局,onDraw,draw,绘制)