Android中绘制是按照一定顺序依次绘制的。后绘制的内容会遮盖住先绘制的内容。这边来研究一下其绘制顺序。
1、super.onDraw() 前后
对于继承自view的自定义view。往往要重写它的onDraw()方法来将自己想要的绘制内容写入进来,例如:
publicclass AppView extends View {
...
protectedvoid onDraw(Canvas canvas) {
super.onDraw(canvas);
... // 自定义绘制代码
}
...
}
在上面的样例中,把自定义绘制代码写在super.onDraw(canvas)前面或后面,甚至删掉都无所谓。因为View这个类里面的onDraw()是空的。但是当你继承某种功能的控件,再去重写它的onDraw(),这时候就必须加上super.onDraw()了,此时就需要根据需求判断将自己代码写在其上面还是下面。比如当你想实现一个TextView的强调色,可以将强调色背景代码写在super.onDraw()的前面。
2、dispatchDraw():绘制子View的方法
当你想对一个ViewGroup增加一些绘制内容,如果你只是重写了onDraw(),很有可能被ViewGroup的子view给遮盖住,因为在绘制的时候是先绘制ViewGroup的onDraw()内容,再绘制其子view的onDraw()内容。具体来说,其实绘制子view是通过另一个绘制方法:dispatchDraw()来进行的,ViewGroup会先调用自己的onDraw()方法来绘制主体,再调用dispatchDraw()方法来绘制子View。所以想将viewGroup的内容绘制在子view上面,需要重写dispatchDraw(),并且在方法体里面先写super.dispatchDrow(),再写需要绘制的自定义代码。当然如果先写自己代码再写super.dispatchDrow(),就会产生子view覆盖住的情况。
3、绘制过程简述
绘制过程中最典型的两个部分是上面讲到的主体和子View,但它们并不是绘制过程的全部。除此之外,绘制过程还包含一些其他内容的绘制。具体来讲,一个完整的绘制过程会依次绘制以下几个内容:
1 背景
2 主体(onDraw())
3 子View(dispatchDraw())
4 滑动边缘渐变和滑动条
5 前景
一般来说,一个View(或ViewGroup)的绘制不会这几项全都包含,但必然逃不出这几项,并且一定会严格遵守这个顺序。例如通常一个LinearLayout只有背景和子View,那么它会先绘制背景再绘制子View;一个ImageView有主体,有可能会再加上一层半透明的前景作为遮罩,那么它的前景也会在主体之后进行绘制。需要注意,前景的支持是在Android 6.0(也就是API 23)才加入的;之前其实也有,不过只支持FrameLayout,而直到6.0才把这个支持放进了View类里。
这其中的第2、3两步,前面已经讲过了;第1步——背景,它的绘制发生在一个叫drawBackground()的方法里,但这个方法是private的,不能重写,你如果要设置背景,只能用自带的API去设置(xml布局文件的android:background属性以及Java代码的View.setBackgroundXxx()方法),而不能自定义绘制;而第4、5两步——滑动边缘渐变和滑动条以及前景,这两部分被合在一起放在了onDrawForeground()方法里,这个方法是可以重写的。
滑动边缘渐变和滑动条可以通过xml的android:scrollbarXXX系列属性或Java代码的View.setXXXScrollbarXXX()系列方法来设置;前景可以通过xml的android:foreground属性或Java代码的View.setForeground()方法来设置。而重写onDrawForeground()方法,并在它的super.onDrawForeground()方法的上面或下面插入绘制代码,则可以控制绘制内容和滑动边缘渐变、滑动条以及前景的遮盖关系。
4、onDrawForeground()
如果你把绘制代码写在了super.onDrawForeground()的下面,绘制代码会在滑动边缘渐变、滑动条和前景之后被执行,那么绘制内容将会盖住滑动边缘渐变、滑动条和前景。如果你把绘制代码写在了super.onDrawForeground()的上面,绘制内容就会在dispatchDraw()和super.onDrawForeground()之间执行,那么绘制内容会盖住子View,但被滑动边缘渐变、滑动条以及前景盖住。这种写法,和前面2.1讲的,重写dispatchDraw()并把绘制代码写在super.dispatchDraw()的下面的效果是一样的:绘制内容都会盖住子View,但被滑动边缘渐变、滑动条以及前景盖住。
想在滑动边缘渐变、滑动条和前景之间插入绘制代码?很简单:不行。
虽然这三部分是依次绘制的,但它们被一起写进了onDrawForeground()方法里,所以你要么把绘制内容插在它们之前,要么把绘制内容插在它们之后。而想往它们之间插入绘制,是做不到的。
5 、draw()总调度方法
除了onDraw() dispatchDraw()和onDrawForeground()之外,还有一个可以用来实现自定义绘制的方法:draw()。
draw()是绘制过程的总调度方法。一个View的整个绘制过程都发生在draw()方法里。前面讲到的背景、主体、子View、滑动相关以及前景的绘制,它们其实都是在draw()方法里的。
// View.java 的 draw() 方法的简化版大致结构(是大致结构,不是源码哦):
public void draw(Canvas canvas) {
...
drawBackground(Canvas); //绘制背景(不能重写)
onDraw(Canvas); //绘制主体
dispatchDraw(Canvas); //绘制子View
onDrawForeground(Canvas); //绘制滑动相关和前景
...
}
从上面的代码可以看出,onDraw() dispatchDraw() onDrawForeground()这三个方法在draw()中被依次调用,因此它们的遮盖关系也就像前面所说的——dispatchDraw()绘制的内容盖住onDraw()绘制的内容;onDrawForeground()绘制的内容盖住dispatchDraw()绘制的内容。而在它们的外部,则是由draw()这个方法作为总的调度。所以,你也可以重写draw()方法来做自定义的绘制。
注意
关于绘制方法,有两点需要注意一下:
1 出于效率的考虑,ViewGroup默认会绕过draw()方法,换而直接执行dispatchDraw(),以此来简化绘制流程。所以如果你自定义了某个ViewGroup的子类(比如LinearLayout)并且需要在它的除dispatchDraw()以外的任何一个绘制方法内绘制内容,你可能会需要调用View.setWillNotDraw(false)这行代码来切换到完整的绘制流程(是「可能」而不是「必须」的原因是,有些ViewGroup是已经调用过setWillNotDraw(false)了的,例如ScrollView)。
2 有的时候,一段绘制代码写在不同的绘制方法中效果是一样的,这时你可以选一个自己喜欢或者习惯的绘制方法来重写。但有一个例外:如果绘制代码既可以写在onDraw()里,也可以写在其他绘制方法里,那么优先写在onDraw(),因为Android有相关的优化,可以在不需要重绘的时候自动跳过onDraw()的重复执行,以提升开发效率。享受这种优化的只有onDraw()一个方法。
网友评论