一切的起源

之前有分析过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的执行机制,是否会阻塞调用线程呢?)