一切的起源
之前有分析过Activity的启动过程,view的绘制起源其实也是包含在其中的,老规矩,先上图:
view的绘制起源
首先,DecorView是Activity的根view,Activity#setContentView其实就是在DecorView中加入子view,从图中可知,DecorView的绘制起点是在Activity的创建过程中(onResume之前)触发的,由ViewRootImpl承担DecorView的绘制过程,DecorView作为ViewGroup会最终调用子view的绘制,绘制过程主要分为三个部分:measure(测量)、layout(布局)、draw(绘制)。
measure(测量)过程
其实View的绘制过程和事件传递过程都是类似的,从最底层的DecorView开始,一层层向上冒泡传递,如图所示:
绘制过程
View的measure过程算是绘制过程中最复杂的一部分了,相比于layout、draw只需要拿到测量后的尺寸直接应用即可,measure过程一开始并不知道ViewGroup的最终大小,需要遍历测量所有子view的尺寸,再进行自身的测量,如果有问题可能还需进行再次测量才能得到最终的尺寸,一次绘制过程可能需要多次measure才能完成,所以在自定义view时,最好不要在measure过程去获取view的最终尺寸,在layout之后获取才比较可靠。
measure的尺寸信息使用MeasureSpec来保存,包含mode、size两部分的信息,关于mode有三种模式:
MeasureSpec.UNSPECIFIED:不限尺寸,一般只有系统中的view才能用到
MeasureSpec.AT_MOST:指定最大可用大小,对应于wrap_content
MeasureSpec.EXACLITY:固定大小,对应于match_parent和指定具体宽高
自定义在支持wrap_content时,需要注意复写onMeasure方法,原代码wrap_content会直接当作match_parent处理:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST: //对应的wrap_content直接取specSize,相当于match_parent
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
layout和draw过程
layout过程很简单,只需要遍历子view根据规则排列即可。draw过程我们最熟悉的应该是onDraw方法,大部分情况下自定义只需要复写该方法即可,但在对于某些特殊显示效果时就需要注意各个draw方法之间的前后顺序了,后面调用的方法绘制的内容总是会覆盖在前面。
AdjustLayout自动换行布局
这边写了一个自动换行的AdjustLayout自定义view,大家有兴趣可以参考手动去重写绘制过程,相信会对整个绘制流程有更深刻的印象。github地址:AdjustLayout
思考题
文章最初分析了view的绘制起源,介于Activity的onCreate和onResume之间,请大家思考下在onResume中能否获取到view的measuredWidth?这个思考题主要涉及到ViewRootImpl.performTraversal的调用方式和Android的消息机制,类似的问题可以换成在onCreate方法中调用View.post(),是post中的方法先执行还是onResume先执行到,有兴趣的童鞋可以尝试下。(其实这个问题再深入挖掘可以关联到ipc的执行机制,是否会阻塞调用线程呢?)