自定义View之绘制流程总结
绘制流程分三个方面来总结
- View绘制
- layout布局
- measure测量
View绘制
先上图,这图是下面参考文章来的,清晰明了,直接贴上来。
下面就对上面图做一个简单的讲解。
实际上自定义View的时候 你心中有上面图描绘的顺序,就可以了。概念方面的描述,就不用了多说了,网上一堆,图已经非常实用了。
自定义绘制最基本的形态:继承 View 类,在 onDraw() 中完全自定义它的绘制。
除了继承 View 类,自定义绘制更为常见的情况是,继承一个具有某种功能的控件,去重写它的 onDraw() ,在里面添加一些绘制代码,做出一个「进化版」的控件。而这种基于已有控件的自定义绘制,就不能不考虑 super.onDraw() 了:你需要根据自己的需求,判断出你绘制的内容需要盖住控件原有的内容还是需要被控件原有的内容盖住,从而确定你的绘制代码是应该写在 super.onDraw() 的上面还是下面。
每一个 ViewGroup 会先调用自己的 onDraw() 来绘制完自己的主体之后再去绘制它的子 View。所以你在 ViewGroup 绘制的图形,很可能会在 子View 绘制完成之后,就被子 View 盖住了。
具体来讲,这里说的「绘制子 View」是通过另一个绘制方法的调用来发生的,这个绘制方法叫做:dispatchDraw()。也就是说,在绘制过程中,每个 View 和 ViewGroup 都会先调用 onDraw() 方法来绘制主体,再调用 dispatchDraw() 方法来绘制子 View。
只要重写 dispatchDraw(),并在 super.dispatchDraw() 的下面写上你的绘制代码,这段绘制代码就会发生在子 View 的绘制之后,从而让绘制内容盖住子 View 了。
除了 onDraw() dispatchDraw() 和 onDrawForeground() 之外,还有一个可以用来实现自定义绘制的方法: draw()。
draw() 是绘制过程的总调度方法。一个 View 的整个绘制过程都发生在 draw() 方法里。前面讲到的背景、主体、子 View 、滑动相关以及前景的绘制,它们其实都是在 draw() 方法里的。
Layout布局
先看理论:
布局过程分两个阶段:
测量阶段:从上到下递归地调用每个 View 或者 ViewGroup 的 measure() 方法,测量他们的尺寸并计算它们的位置;
布局阶段:从上到下递归地调用每个 View 或者 ViewGroup 的 layout() 方法,把测得的它们的尺寸和位置赋值给它们。
View 或 ViewGroup 的布局过程
测量阶段,调用 onMeasure() 来进行实际的自我测量。 onMeasure() 做的事,View 和 ViewGroup 不一样:
View:View 在 onMeasure() 中会计算出自己的尺寸然后保存;
ViewGroup:ViewGroup 在 onMeasure() 中会调用所有子 View 的 measure() 让它们进行自我测量,并根据子 View 计算出的期望尺寸来计算出它们的实际尺寸和位置(实际上 99.99% 的父 View 都会使用子 View 给出的期望尺寸来作为实际尺寸,原因在下期或下下期会讲到)然后保存。同时,它也会根据子 View 的尺寸和位置来计算出自己的尺寸然后保存;
布局阶段,layout() 方法被父 View 调用,在 layout() 中它会保存父 View 传进来的自己的位置和尺寸,并且调用 onLayout() 来进行实际的内部布局。onLayout() 做的事, View 和 ViewGroup 也不一样:
View:由于没有子 View,所以 View 的 onLayout() 什么也不做。
ViewGroup:ViewGroup 在 onLayout() 中会调用自己的所有子 View 的 layout() 方法,把它们的尺寸和位置传给它们,让它们完成自我的内部布局。
布局过程自定义:
三类
重写 onMeasure() 来修改已有的 View 的尺寸;
重写 onMeasure() 来全新定制自定义 View 的尺寸;
重写 onMeasure() 和 onLayout() 来全新定制自定义 ViewGroup 的内部布局。
重写 onMeasure() 来修改已有的 View 的尺寸;下面是代码示例
/**
* 重写 onMeasure() 来修改已有的 View 的尺寸
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//先让他自己测量完
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取他自己测量的结果
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
//改变测量的结果。如果宽短就是把长变的跟宽一样,反之亦然,使之成为正方形
if (measuredWidth > measuredHeight) {
measuredWidth = measuredHeight;
} else {
measuredHeight = measuredWidth;
}
//重新设置测量结果来保存最终的宽度和高度
setMeasuredDimension(measuredWidth, measuredHeight);
}
重写 onMeasure() 来全新定制自定义 View 的尺寸;下面是代码示例
/**
* 重写 onMeasure() 来全新定制自定义 View 的尺寸;
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//全由自己计算图形的长和宽
int measuredWidth = 200;
int measuredHeight = 300;
//widthMeasureSpec 和 heightMeasureSpec 是父View对子View限制,要满足他调用下面的方法
//限制的分类:
//UNSPECIFIED:不限制:随便设置宽高
//AT_MOST:限制上限:越界的话就会砍掉多余的
//EXACTLY:限制固定值:直接返回限制的固定值
measuredWidth = resolveSize(measuredWidth, widthMeasureSpec);
measuredHeight = resolveSize(measuredHeight, heightMeasureSpec);
//设置计算测量结果来保存最终的宽度和高度
setMeasuredDimension(measuredWidth, measuredHeight);
}
重写 onMeasure() 和 onLayout() 来全新定制自定义 ViewGroup 的内部布局;
这个有些复杂。据说只有1%的开发者去弄清楚,属于高级自定义。
定制 Layout 内部布局的方式分为两大步
重写 onMeasure() 来计算内部布局
重写 onLayout() 来摆放子 View
重写 onMeasure() 来计算内部布局
1,调用子View onMeasure() 自我测量
2,根据子View给出的尺寸,得出子View的位置,并保存位置和尺寸
3,根据子View的尺寸和位置,计算出自己的尺寸 ,并用 setMeasuredDimension()保存
重写 onLayout() 来摆放子 Viev
调用每个子View的layout,把位置和尺寸传给它。
这个例子不好写,以后用到时再完善例子
参考文章 https://zhuanlan.zhihu.com/p/28499694
https://zhuanlan.zhihu.com/p/30764107
https://zhuanlan.zhihu.com/p/32414322
未完待续。。。。