我的GitHub | 我的博客 | 我的微信 | 我的邮箱 |
---|---|---|---|
baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
目录
扔物线自定义 View 系列教程总结-5
全文整理自 扔物线(HenCoder)自定义 View 系列文章
重新整理的目标:
- 内容压缩:去除活跃气氛的段子、图片,去除无意义的解释、代码,去除不刚兴趣的内容,压缩比至少 50%
- 排版优化:更清晰的结构,更精简的标题,更规范的缩进、标点符号、代码格式,好的结构才能更好的吸收
- MarkDown:以标准的 MarkDown 格式重新编排,纯文本更易迭代维护
扔物线自定义 View 系列教程分
绘制
、布局
和触摸反馈
三部分内容。
绘制顺序
总结
Android 里面的绘制都是按顺序的,先绘制的内容会被后绘制的盖住
,所以控制好绘制顺序是非常重要的。
一个完整的绘制过程会依次绘制以下几个内容:
- 绘制背景:
drawBackground()
- 绘制主体:
onDraw()
- 绘制子 View:
dispatchDraw()
- 绘制滑动边缘渐变和滑动条以及前景:
onDrawForeground()
drawBackground 绘制背景
这个方法是 private
的,不能重写,也就是说不能自定义绘制。可以通过android:background
属性或View.setBackgroundXxx()
方法设置背景。
onDraw 绘制主体
onDraw()
是负责自身主体内容
绘制的。在 View
中,onDraw()
是空实现,所以直接继承 View 的类,它们的 super.onDraw()
什么也不会做。
写在 super.onDraw 下面
绘制内容就会盖住控件原来的内容,可以为控件增加点缀性内容。
在 Debug 模式下绘制出 ImageView 的图像尺寸信息:
写在 super.onDraw 上面
绘制的内容会被控件的原内容盖住,这种用法的场景相对来说会少一些。
通过在文字的下层绘制纯色矩形来作为「强调色」:
dispatchDraw 绘制子 View
在绘制过程中,每个 View 和 ViewGroup 都会先调用 onDraw()
方法来绘制主体,再调用 dispatchDraw()
方法来绘制子 View。
如果你继承了一个 LinearLayout
,重写了它的 onDraw()
方法,在 super.onDraw()
后插入了你自己的绘制代码,以便使它能够在内部绘制一些斑点作为点缀。但是你会发现,当你添加了子 View 之后,你的斑点不见了。造成这种情况的原因是:在绘制过程中,每一个 ViewGroup 会先调用自己的 onDraw()
来绘制完自己的主体之后再去绘制它的子 View,那么在子 View 绘制完成之后,先前绘制的斑点就被子 View 盖住了。
注:由于 View 没有子 View,所以一般来说这个方法只对
ViewGroup
有意义。
写在 super.dispatchDraw 下面
绘制的内容发生在子 View 的绘制之后,所以绘制内容会盖住子 View
。
上面说的
LinearLayout
案例,就应该放在super.dispatchDraw
下面
写在 super.dispatchDraw 上面
这其实和前面讲的把绘制代码写在 super.onDraw()
之后的效果是一样的。
onDrawForeground 绘制前景
- 在
Android 6.0(API 23)
之前,仅FrameLayout
支持前景,6.0 后所有View
都支持 - 滑动边缘渐变和滑动条可以通过
android:scrollbarXXX
系列属性或View.setXXXScrollbarXXX()
系列方法来设置 - 前景可以通过
android:foreground
属性或View.setForeground()
方法来设置
虽然这三部分是依次绘制的,但它们被一起写进了这个方法里,所以不能在它们之间插入自定义绘制
写在 super.onDrawForeground 下面
绘制代码会在滑动边缘渐变、滑动条和前景之后被执行,所以绘制内容会盖住滑动边缘渐变、滑动条和前景
。
在 ImageView 左上角添加「New」标签,要求不能被遮罩(foreground)盖住
写在 super.onDrawForeground 上面
这种写法,和前面讲的把绘制代码写在 super.dispatchDraw()
的下面的效果是一样的。
在 ImageView 左上角添加「New」标签,要求需要被遮罩(foreground)盖住
draw 总调度方法
draw()
是绘制过程的总调度方法
,一个 View 的整个绘制过程都发生在 draw()
方法里。前面讲到的背景、主体、子 View 、滑动相关以及前景的绘制,它们其实都是在 draw()
方法里调用的。
// View.java 的 draw() 方法的简化版大致结构
public void draw(Canvas canvas) {
drawBackground(Canvas); // 绘制背景(不能重写)
onDraw(Canvas); // 绘制主体
dispatchDraw(Canvas); // 绘制子 View
onDrawForeground(Canvas); // 绘制滑动相关和前景
}
所以也可以通过重写 draw()
方法来做自定义的绘制。
写在 super.draw 下面
绘制的内容会在其他所有绘制完成之后再执行,也就是说,它的绘制内容会盖住其他的所有绘制内容
。
它的效果和把绘制代码写在 super.onDrawForeground()
下面时的效果是一样的,都会盖住其他的所有内容。
写在 super.draw 上面
绘制的内容会在其他所有绘制之前被执行,所以这部分绘制内容会被其他所有的内容盖住,包括背景。
是不是觉得没用?觉得怎么可能会有谁想要在背景的下面绘制内容?别这么想,有的时候它还真的有用。
例如 EditText
,它下面的那条横线是它的背景(background
)
如果我想给这个 EditText
加一个绿色的底,我不能使用给它设置绿色背景色android:background="#66BB6A"
的方式,因为这就相当于是把它的背景替换掉,从而会导致下面的那条横线消失
在这种时候,你就可以重写它的 draw()
方法,然后在 super.draw()
的上方插入代码,以此来在所有内容的底部涂上一片绿色:
public void draw(Canvas canvas) {
canvas.drawColor(Color.parseColor("#66BB6A")); // 涂上绿色
super.draw(canvas);
}
注意事项
关于绘制方法,有两点需要注意:
1、在 ViewGroup
的子类中重写除 dispatchDraw()
以外的绘制方法时,可能需要调用 setWillNotDraw(false)
出于效率的考虑,
ViewGroup
默认会绕过draw()
方法,换而直接执行dispatchDraw()
,以此来简化绘制流程。所以如果你自定义了某个ViewGroup
的子类并且需要在它的除dispatchDraw()
以外的任何一个绘制方法内绘制内容,你 可能 会需要调用View.setWillNotDraw(false)
这行代码来切换到完整的绘制流程。是「可能」而不是「必须」的原因是,有些 ViewGroup 是已经调用过setWillNotDraw(false)
了的,例如ScrollView
。
2、在重写的方法有多个选择时,优先选择 onDraw()
一般情况,一段绘制代码写在不同的绘制方法中效果是一样的,但有一个例外:如果绘制代码既可以写在 onDraw()
里,也可以写在其他绘制方法里,那么优先写在 onDraw()
。因为 Android 有相关的优化,可以在不需要重绘的时候自动跳过 onDraw()
的重复执行,以提升开发效率。享受这种优化的只有 onDraw()
一个方法。
2021-5-7