- 一自定义控件
- 二自定义控件的总类
- 三view对象渲染的过程
- 四ondraw 和dispatchdraw的区别
- 五ViewGroup渲染的过程
- 六如何自定义一个控件
- 自定义组合控件
- 自定义view
- 七请描述一下View的绘制流程
- 1mesarue过程
- 2layout 布局过程
- 3draw绘图过程
- 调用流程
一.自定义控件
什么是自定义控件
谷歌提供了大量的默认控件, ImageView TextView Button,
但是有的时候不能满足我们的业务需求
二.自定义控件的总类
- 组合控件(把系统现有的控件组合在一起,创建一个新的控件)
- 自定义控件( 创造出来一个系统原生没有的控件)
三.view对象渲染的过程
ondraw(Canvas canvas)
重写控件的ondraw方法,当一个控件被显示到屏幕的时候 就会调用onDraw();
canvas 代表的是屏幕的画布
四.ondraw() 和dispatchdraw()的区别
- 绘制VIew本身的内容,通过调用View.onDraw(canvas)函数实现
- 绘制自己的孩子通过dispatchDraw(canvas)实现
- View组件的绘制会调用draw(Canvas canvas)方法,draw过程中主要是先画Drawable背景,对 drawable调用setBounds()然后是draw(Canvas c)方法.
- 有点注意的是背景drawable的实际大小会影响view组件的大小,drawable的实际大小通过getIntrinsicWidth()和getIntrinsicHeight()获取,当背景比较大时view组件大小等于背景drawable的大小
- 画完背景后,draw过程会调用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法, dispatchDraw()主要是分发给子组件进行绘制,我们通常定制组件的时候重写的是onDraw()方法。值得注意的是ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw()方法, 而绕过了draw()方法,当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。
- 因此要在ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法,或者自定制一个Drawable,重写它的draw(Canvas c)和 getIntrinsicWidth(), getIntrinsicHeight()方法,然后设为背景。
五.ViewGroup渲染的过程
- onMeasure(); 测量,测量里面的每一个孩子的大小
- onLayout(); 控制摆放位置
- 调用每一个控件的ondraw();
六.如何自定义一个控件
自定义控件可以分为两种自定义组合控件和自定义 view。
自定义组合控件
自定义组合控件就是把多个控件做为一个整体看待、处理。这样的好处不仅可以减轻 xml的代码量,也提高了代码的复用性。
- 1.声明一个View 对象,继承相对布局,或者线性布局或者其他的ViewGroup。
- 2.在自定义的View 对象里面重写它的构造方法,在构造方法里面就把布局都初始化完毕。
- 3.根据业务需求添加一些api 方法,扩展自定义的组合控件;
- 4.希望在布局文件里面可以自定义一些属性。
- 5.声明自定义属性的命名空间
xmlns:xxxx="http://schemas.android.com/apk/res/com.itheima.mobilesafe"
- 6.在res 目录下的 values 目录下创建attrs.xml 的文件声明我们写的属性。
- 7.在布局文件中写自定义的属性。
- 8.使用这些定义的属性。自定义View 对象的构造方法里面有一个带两个参数的构造方法布局文件里面定义的属
性都放在AttributeSet attrs,获取那些定义的属性。
自定义view
自定义View 首先要实现一个继承自View 的类。添加类的构造方法,通常是三个构造方法,不过从 Android5.0开始构造方法已经添加到4个了。override父类的方法,如onDraw,(onMeasure)等。如果自定义的View有自己的属性,需要在values下建立attrs.xml文件,在其中定义属性,同时代码也要做修改。
七.请描述一下View的绘制流程
- 整个View树的绘图流程过程可简单概况为根据之前设置的状态
- 1.判断是否需要重新计算视图大小(measure)
- 2.是否重新需要安置视图的位置(layout)
- 3.以及是否需要重绘 (draw),其框架过程如下:
1、mesarue()过程
- 主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View 的控件的实际宽高都是由父视图和本身视图决定的。
- 具体的调用链如下: ViewRoot根对象的属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调View/ViewGroup 对象的onMeasure()方法,该方法实现的功能如下:
- 1、设置本View 视图的最终大小,该功能的实现通过调用 setMeasuredDimension()方法去设置实际
的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth)。 - 2 、如果该View 对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的
measure() 过 程 。 对 每 个 子 视 图 的 measure() 过 程 , 是 通 过 调 用 父 类 ViewGroup.java 类 里 的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了 View 对象的measure()方法。
2、layout 布局过程
- 主要作用:为将整个根据子视图的大小以及布局参数将 View树放到合适的位置上。具体的调用链如下:
- 1、layout 方法会设置该 View 视图位于父视图的坐标轴,即 mLeft,mTop,mLeft,mBottom(调用
setFrame()函数去实现)接下来回调onLayout()方法(如果该View 是ViewGroup对象,需要实现该方法,对每个子视
图进行布局)。 - 2、如果该View 是个ViewGroup类型,需要遍历每个子视图 chiildView,调用该子视图的 layout()方法去设置它的坐标值。
3、draw()绘图过程
由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View 树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View 类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。
调用流程 :
- 1 、绘制该View 的背景
- 2 、为显示渐变框做一些准备操作(大多数情况下,不需要改渐变框)
- 3、调用onDraw()方法绘制视图本身(每个View 都需要重载该方法,ViewGroup不需要实现该方法)
- 4、调用dispatchDraw ()方法绘制子视图(如果该 View 类型不为ViewGroup,即不包含子视图,不需要重载该方法)值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
总结:以实战为主,结合实际进行修改判断:
主要区分view和viewGoup的绘制,一个要onDraw画自身,一个需要ondispathDraw(canvs)画子孩子
viewGroup需要onMesure测绘内部孩子的尺寸+onLayout定位内部孩子位置+最后再决定是否绘制.
给大家准备了由浅入深的演示Demo,请大家下载下来进行学习和快速掌握