一、前言
前面已经介绍了自定义View的measure和layout过程,接下来进入到很重要的一步就是draw过程,用来对视图进行绘制,我们平时看到的各种View都需要经过draw过程才能在屏幕上显示出来!
二、流程分析
通常来说draw的过程会包括4部分,分别是绘制背景、绘制内容、绘制子View和绘制滚动条,如下图所示:
为了让大家更清楚的弄清楚这个流程,这里列出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的理解,本篇文章参考了郭霖大神的博客和浮城大亨的博客,感谢两位!
继续加油!