第一部分:Measure测量
从上到下递归地调用每个 View 或者 ViewGroup 的 measure() 方法,测量他们的尺寸并计算它们的位置;measure() 方法被父 View 调用,在 measure() 中做一些准备和优化工作后,调用 onMeasure() 来进行实际的自我测量。
onMeasure() 做的事,View 和 ViewGroup 不一样:
View:View 在 onMeasure() 中会计算出自己的尺寸然后保存;
ViewGroup:ViewGroup 在 onMeasure() 中会调用所有子 View 的 measure() 让它们进行自我测量,并根据子 View 计算出的期望尺寸来计算出它们的实际尺寸和位置然后保存。同时,它也会根据子 View 的尺寸和位置来计算出自己的尺寸然后保存;
介绍一个方法:resolveSize()能让子 View 的计算结果符合父 View 的限制(当然,如果你想用自己的方式来满足父 View 的限制也行)。
重写 onMeasure() 来修改已有的 View 的尺寸的具体做法如下:
第一步:重写 onMeasure() 方法,并在里面调用 super.onMeasure(),触发原有的自我测量;
第二步:在 super.onMeasure() 的下面用 getMeasuredWidth() 和 getMeasuredHeight() 来获取到之前的测量结果,并使用自己的算法,根据测量结果计算出新的结果;
第三步:调用 setMeasuredDimension() 来保存新的结果。
----------------------------------------------
第二部分:Layout布局
从上到下递归地调用每个 View 或者 ViewGroup 的 layout() 方法,把测得的它们的尺寸和位置赋值给它们。layout() 方法被父 View 调用,在 layout() 中它会保存父 View 传进来的自己的位置和尺寸,并且调用 onLayout() 来进行实际的内部布局。
onLayout() 做的事, View 和 ViewGroup 也不一样:
View:由于没有子 View,所以 View 的 onLayout() 什么也不做。
ViewGroup:ViewGroup 在 onLayout() 中会调用自己的所有子 View 的 layout() 方法,把它们的尺寸和位置传给它们,让它们完成自我的内部布局。
----------------------------------------------
第三部分:Draw绘制
1:onDraw()方法,画主体
2:dispatchDraw()方法
继承自ViewGourp的类中有个dispatchDraw()方法(这是绘制子 View 的方法),默认情况下继承自ViewGourp的类是不会执行onDraw()方法的,为了节省系统开销,可以通过设置其背景颜色,或者在构造方法中调用setWillNotDraw(false);来调用它的onDraw()方法。
3:onDrawForeground()方法,画前景
4:draw() 总调度方法
一个 View 的整个绘制过程都发生在 draw() 方法里。背景、主体、子 View 、滑动相关以及前景的绘制,它们其实都是在 draw() 方法里的。onDraw() dispatchDraw() onDrawForeground() 这三个方法在 draw() 中被依次调用,因此它们的遮盖关系是dispatchDraw() 绘制的内容盖住 onDraw() 绘制的内容;onDrawForeground() 绘制的内容盖住 dispatchDraw() 绘制的内容。而在它们的外部,则是由 draw() 这个方法作为总的调度。所以,也可以重写 draw() 方法来做自定义的绘制。
一个完整的绘制过程会依次绘制以下几个内容:
1:背景
2:主体(onDraw())
3:子 View(dispatchDraw())
4:滑动边缘渐变和滑动条
5:前景
一般来说,一个 View(或 ViewGroup)的绘制不会这几项全都包含,但必然逃不出这几项,并且一定会严格遵守这个顺序。
关于绘制方法,有两点需要注意一下:
1:出于效率的考虑,ViewGroup 默认会绕过 draw() 方法,换而直接执行 dispatchDraw(),以此来简化绘制流程。这一点上面已经提到过了,所以如果你自定义了某个 ViewGroup 的子类(比如 LinearLayout)并且需要在它的除 dispatchDraw() 以外的任何一个绘制方法内绘制内容,你可能会需要调用 View.setWillNotDraw(false) 这行代码来切换到完整的绘制流程(是「可能」而不是「必须」的原因是,有些 ViewGroup 是已经调用过 setWillNotDraw(false) 了的,例如 ScrollView)。
2:有的时候,一段绘制代码写在不同的绘制方法中效果是一样的,这时你可以选一个自己喜欢或者习惯的绘制方法来重写。但有一个例外:如果绘制代码既可以写在 onDraw() 里,也可以写在其他绘制方法里,那么优先写在 onDraw() ,因为 Android 有相关的优化,可以在不需要重绘的时候自动跳过 onDraw() 的重复执行,以提升开发效率。享受这种优化的只有 onDraw() 一个方法。
总结
![](https://img.haomeiwen.com/i6213762/2fe2bf06e21bd7ed.png)
![](https://img.haomeiwen.com/i6213762/b144abd513b25445.png)
View的绘制流程
View的绘制流程:OnMeasure()——>OnLayout()——>OnDraw()
各步骤的主要工作:
1:测量视图大小。从顶层父View到子View递归调用view.measure方法,measure方法又回调OnMeasure。
2:确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。
3:绘制视图。从顶层父View向子View的递归调用view.draw方法的过程,ViewRoot创建一个Canvas对象,然后调用drawBackground(画背景),OnDraw(画主体),dispatchDraw(画子view,如果有子view),onDrawForeground(画前景及滚动条)。
网友评论