近期闲来无事,就总结下了View的绘制过程,其实就是提高的自己的学习能力,早就想总结这块知识,一直犹豫自己要不要,来写这篇博客,而且网上这块内容的知识也很多,俗语说好记性不如烂笔头,所以自己就总结了一下,跟大家分享一下。。好了闲话不说了,进入正题:
整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘(draw)。
1. measure
为整个View树计算实际的大小,即设置实际的高(mMeasuredHeight)和宽(mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。
1.调用measure()方法去计算View的大小。
2.回调onMeasure()方法:
如果是普通View,调用setMeasuredDimension()设置自身大小。
如果是ViewGroup,遍历子View,调用子View的measure()方法测量子View,然后调用setMeasuredDimension()设置自身大小。
measure函数原型为View.java该函数不能被重载,它的内部调用了onMeasure方法,而此方法正是让子类去实现自身的测量策略。
测量过程先测量自身,再测量子View,最后设置大小的原因:子View的测量过程中需要使用到父View的大小,例如:fill_parent,父View的大小有时候由子类来决定,例如:wrap_content。
2. layout
为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。
1.在layout()方法中调用setFram()方法比对自身的位置是否在父view中发生了改变,如果改变了,设置新位置,会调用invalidate()方法。
2.回调onLayout()方法:
如果是普通View,可以空实现。
如果是ViewGroup,需要遍历每个子视图chiildView,调用该子View的layout()方法去设置它的坐标值。
3、draw
由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些”需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。
draw()方法的流程大致如下:
1.绘制该View的背景。
2.为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框) 。
3.调用onDraw()方法绘制视图本身(每个View都需要重载该方法,ViewGroup不需要实现该方法)。
4.调用dispatchDraw()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个地方”需要重绘”的视图才会调用draw()方法)。
5.绘制滚动条。
invalidate()方法 :
说明:请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些”需要重绘的”视图,即谁(View的话,只绘制该View;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。
一般引起invalidate()操作的函数如下:
1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身。
3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View。
4 、setEnabled()方法 : 请求重新draw()。
5、View设置了状态选择器,并且状态发生了改变。
requestLayout()方法 :会导致调用measure()过程和layout()过程 。
说明:只是对View树重新布局layout过程包括measure()和layout()过程,但layout内部有策略会选择是否invalidate()。
一般引起invalidate()操作的函数如下:
1、setVisibility()方法:
当View的可视状态在INVISIBLE/ VISIBLE转换为GONE状态时,会间接调用requestLayout()和invalidate方法。同时,由于整个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要”重新绘制”的视图。
requestFocus()函数说明:
说明:请求View树的draw()过程,但只绘制”需要重绘”的视图,因为焦点的变化一般都会有状态选择器的改变。
需要注意的地方:在重写上述绘制流程中,我们不要对View的层级做出改变,如非得以,可以加开关进行控制,否则会陷入无限的递归调用,当然也可以通过addViewInLayout,removeViewInLayout进行操作,在onMeasure之前我们是无法获取View的paramers,可以通过监听回调获取,如果需要在监听中重新设置大小,需要在获取的回调里删除监听接口。