看见好多文章都写View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,从刚开始迈步的时候就摔了一大跤,为什么呢?因为我在View的源码中就找不到ViewRoot这个类,只有一个ViewRootHandler类,我以为我找错了,我觉得ViewRoot应该是单独的类,于是尝试着声明了一个ViewRoot类型的变量,企图直接找到他,然而根本没有这东西,我猜会不会在ViewGroup呢?毕竟都带个View嘛,然而还是没有找到...我就滚回去看了一下ViewRootHandler,发现里面有一行requestLayout()!顾名思义,请求布局,那我就切(按着ctrl,用鼠标左键点他)进去
checkThread是确认发起请求的布局是否处在主线程,这里有一个scheduleTraversals(),和performTraversals()有点像,都是遍历,那就切进去看看吧,似乎没有我们要找的东西,不过你看这里有一个getLooper(),根据学到的异步消息机制,我们可以知道Looper是Queue的管家,将队列中存在的信息取出,分发回handleMessage()中,我认为这个scheduleTraversals()的意义应该是在主线程中完成了一次View绘制,那我可不可以认为正在传递的消息就是View的绘制流程呢?我刚刚上下乱翻的时候见到了不少的xxImpl,Impl就是执行的缩写,我姑且试一下吧,搜搜Impl
还真让我搜到了一个ViewRootImpl(),我估计这个应该就是View的绘制流程,然后我找到了可以证明这是View的绘制流程的圣物,绘制View的开端——performTraversals()!于是可以正式分析View的绘制流程了
众所周知View的绘制流程分为3个最主要的阶段,onMeasure()、onLayout()和onDraw(),下面我就一个一个找出来配合其他博客做一些分析
onMeasure()
6.0版本onMeasure()似乎改名了,改叫measureHierarchy()
MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格
EXACTLY: 对子View提出了一个确切的建议尺寸(SpecSize)
AT_MOST: 子View的大小不得超过SpecSize
UNSPECIFIED: 对子View的尺寸不作限制,通常用于系统内部
measureHierarchy()确定了绘制的矩形的规格和大小,由getRootMeasureSpec()获取到了最外层的视图(根视图),再把获取到的大小和规格赋给childWidthMeasureSpec和childHeightMeasureSpec,我们切进去看看里面写了什么
我们可以观察到lp.width和lp.height在创建出Viewgroup实例的时候被赋值了,默认为MATCH_PARENT,MeasureSpec调用了makeMeasureSpec组装一个MeasureSpec,当rootDimension等于MATCH_PARENT的时候,MeasureSpec的specMode就等于EXACTLY,当rootDimension等于WRAP_CONTENT的时候,MeasureSpec的specMode就等于AT_MOST,都不是的时候就由开发人员来决定大小。MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的,也就意味着根视图总是会充满全屏的
把通过makeMeasureSpec规定好的高和宽传给performMeasure(),切进去看一下他的源码
mView就是大小和规格已经确定的View,调用它的measure()开始测量,切进去看看
调用measure()来算出一个View应该为多大。参数为父View对其宽高的约束信息。
什么情况下才会开始测量呢?可以看出是在需要测量或者强制测量时开始测量,如果这时发现缓存中存在已经测量好的View就不在测量,如果没有View的缓存就调用onMeasure()开始真正的实际测量,最后将测量好的结果作为最终值返回,那么实际测量又是怎样测量的呢?切进去看看
可以看到实际测量工作由getDefaultSize()完成,用它来获取View的大小,但是在深入下去路就断了,我查了百度后才知道实际执行测量工作的是FrameLayout的onMeasure(),实际测量是这样的,先通过measureChildWithMargins()测量子View,在测量自身,还要把padding的部分也算进去,这样就能测量出一个能够正常显示自身及其所有子View的结果,再调用resolveSizeAndState()把测量好的结果缓存起来,最后就通过getDefaultSize()获取默认尺寸,完成实际测量
需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取View测量出的宽高,以此之前调用这两个方法得到的值都会是0。
View大小的控制是由父View、布局文件(MATCH_PARENT和WRAP_CONTENT)、以及View本身共同完成的,父View会提供给子View参考的大小,而开发人员可以在XML文件中指定View的大小,然后View本身会对最终的大小进行拍板
onLayout()
经过了一轮测量,View的大小已经测量好了,需要对View进行布局,就是确定View的位置,接着就会调用View里的layout(),我们切进去看一下吧
layout()接收4个参数代表着上(top)下(below)左(left)右(right),这个坐标是相对于当前的父布局而言的。chang判断View的大小是否变化,是否要对当前View进行重绘。同时还会在这里把传递过来的四个参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量,接下来是重头戏onLayout(),切进去看看
怎么是个空方法???经过百度查证,这个确实是个空方法!因为onLayout()是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父View决定子View的显示位置。所以我们来看下ViewGroup中的onLayout()方法是怎么写的吧
ViewGroup中的onLayout()方法竟然是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。没错,像LinearLayout、RelativeLayout等布局,都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的,这些布局源码都比较复杂,有兴趣你可以去看看
onDraw()
measure和layout的过程都结束后,接下来就进入到draw的过程了,从名称上来看你可以得知这才是真正的绘制View的时候,老方法搜一下draw,一下子就出来了
总共有7个步骤,但是注释里写着跳过2和5步骤,搜了一下说是不常用....不过这是我见到的最言简意赅的一次了.....
步骤1:如果需要,绘制背景
步骤3:绘制内容
步骤4:绘制子View
步骤6:绘制滚动条等
步骤7:绘制默认焦点高亮
当你切进去看这些源码内容时, 你会发现View是不会帮我们绘制内容部分的,因此需要每个View根据想要展示的内容来自行绘制,绘制的方式主要还是通过Canvas类
总结
简单总结一下,View的绘制流程简单来说就是在屏幕上绘制一块矩形的区域,首先执行onMeasure测量View的大小,如果有缓存过的View就用缓存里的,没有就用measure重新测量,父View会给子View提供一个参考,程序员可以通过xml文件来主动设置View的大小,View的大小最终由View自身决定。执行完成后开始执行onLayout,确定好View的位置,而onLayout是个抽象方法必须要重写。最后执行onDraw来进行绘制,就是通过Canvas类来实现各种各样的绘制,但是View不会绘制内容部分,每个View需要根据自己想要的内容自行绘制。写到这里就算完篇了,有不少东西看的不是很懂,只能根据网上的东西猜到大概的意思。如果你觉得看完本篇还不满意的话,我建议你下个源码去读一读吧,说不准你能有不小的收获