我的自定义View学习笔记(五)——View 的绘制顺序

  • 这个一个系列,本系列讲的都是本人自定义 View 的学习笔记。目的是加深影响,便于在以后工作中遇到相关问题的时候,能够有个印象知道到哪里去寻找答案。
  • 这是我学习扔物线大神的自定义 View 教程,自己记录的笔记。链接在这里HenCoder,强烈推荐大家去原地址学习,毕竟我写的笔记只是针对我个人的口味而言的,用词和对概念的理解不一定有扔物线大神准确。
  • 我再发一次地址:https://hencoder.com

1、super.onDraw() 前或后

1.1 在 super.onDraw() 后
  • 会在原有内容绘制完成后再执行操作,所以绘制内容会遮盖原有内容
  • 通常用于点缀功能,在原有 View 上进行点缀

image

1.2 在 super.onDraw() 前
  • 会在原内容绘制开始之前绘制,绘制的内容会被原内容遮盖
  • 通常用于设置背景的功能

image

2、 dispatchDraw(): 绘制子 View 的方法

  • 在绘制过程中,每一个 ViewGroup 会先调用自己的 onDraw() 来绘制完自己的主体之后再去绘制它的子 View。这里说的「绘制子 View」是通过另一个绘制方法的调用来发生的,这个绘制方法叫做:dispatchDraw()

image

2.1 写在 super.dispatchDraw() 的下面
  • 重写 dispatchDraw() 方法,并在 super.dispatchDraw() 下面写上你绘制的代码,就可以覆盖在子 View 之上了
public class SpottedLinearLayout extends LinearLayout {  
    ...

    // 把 onDraw() 换成了 dispatchDraw()
    protected void dispatchDraw(Canvas canvas) {
       super.dispatchDraw(canvas);

       ... // 绘制斑点
    }
}
2.2 写在 super.dispatchDraw() 的上面
  • 写在 super.disatchDraw() 上面时,则是在子 View 绘制之前执行,会被子 View 遮盖

3、绘制过程简述

  • 一个完整的绘制过程会依次绘制以下几个内容:
    • 1.背景
    • 2.主体(onDraw())
    • 3.子 View(dispatchDraw())
    • 4.滑动渐变边缘和滑动条
    • 5.前景
  • 一般来说,一个 View(或 ViewGroup)的绘制不会这几项全都包含,但必然逃不出这几项,并且一定会严格遵守这个顺序
    • 第 1 步——背景,它的绘制发生在一个叫 drawBackground() 的方法里,但这个方法是 private 的,不能重写,你如果要设置背景,只能用自带的 API 去设置(xml 布局文件的 android:background 属性以及 Java 代码的 View.setBackgroundXxx() 方法
    • 第 4、5 两步——滑动边缘渐变和滑动条以及前景,这两部分被合在一起放在了 onDrawForeground() 方法里,这个方法是可以重写的。

image

4、onDrawForeground()

  • 这个方法是 API 23 才引入的
  • 在 onDrawForeground() 中,会依次绘制滑动边缘渐变、滑动条和前景
4.1 写在 super.onDrawForeground() 的下面
  • 绘制代码会在滑动边缘渐变、滑动条和前景之后被执行,那么绘制内容将会盖住滑动边缘渐变、滑动条和前景。
4.2 写在 super.onDrawForeground() 的上面

-绘制内容就会在 dispatchDraw() 和 super.onDrawForeground() 之间执行,那么绘制内容会盖住子 View,但被滑动边缘渐变、滑动条以及前景盖住

4.3 想在滑动边缘渐变、滑动条和前景之间插入绘制代码?
  • 虽然这三部分是依次绘制的,但它们被一起写进了 onDrawForeground() 方法里,所以你要么把绘制内容插在它们之前,要么把绘制内容插在它们之后。而想往它们之间插入绘制,是做不到的

image

5、draw() 总调度方法

  • draw() 是绘制过程的总调度方法。一个 View 的整个绘制过程都发生在 draw() 方法里。前面讲到的背景、主体、子 View 、滑动相关以及前景的绘制,它们其实都是在 draw() 方法里的
  • 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() 方法来做自定义的绘制

image

5.1 写在 super.draw() 的下面
  • 由于 draw() 是总调度方法,所以如果把绘制代码写在 super.draw() 的下面,那么这段代码会在其他所有绘制完成之后再执行,也就是说,它的绘制内容会盖住其他的所有绘制内容
5.2 写在 super.draw() 的上面
  • 由于 draw() 是总调度方法,所以如果把绘制代码写在 super.draw() 的上面,那么这段代码会在其他所有绘制之前被执行,所以这部分绘制内容会被其他所有的内容盖住,包括背景。是的,背景也会盖住它

6、注意

  • View.setWillNotDraw(false)
    • 出于效率的考虑,ViewGroup 默认会绕过 draw() 方法,换而直接执行 dispatchDraw(),以此来简化绘制流程。所以如果你自定义了某个 ViewGroup 的子类(比如 LinearLayout)并且需要在它的除 dispatchDraw() 以外的任何一个绘制方法内绘制内容,你可能会需要调用 View.setWillNotDraw(false) 这行代码来切换到完整的绘制流程(是「可能」而不是「必须」的原因是,有些 ViewGroup 是已经调用过 setWillNotDraw(false) 了的,例如 ScrollView)。
  • 优先使用 onDraw() 方法,提升效率
    • 有的时候,一段绘制代码写在不同的绘制方法中效果是一样的,这时你可以选一个自己喜欢或者习惯的绘制方法来重写。但有一个例外:如果绘制代码既可以写在 onDraw() 里,也可以写在其他绘制方法里,那么优先写在 onDraw() ,因为 Android 有相关的优化,可以在不需要重绘的时候自动跳过 onDraw() 的重复执行,以提升开发效率。享受这种优化的只有 onDraw() 一个方法

7、总结

image

image

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值