最近总是对View的绘制流程感到迷惑,也没弄清楚onLayout()和onMeasure()方法的执行顺序,除了打Log观察之外,还仔细阅读了Android SDK中关于View绘制流程的说明以加深理解,所以特翻译如下:
当Activity获取焦点时,其布局将会被绘制。Android框架将会处理这一绘制过程,Activity所做的事情就是提供其布局的根节点(译注:布局文件中自动生成的父布局,带有xmlns:android="http://schemas.android.com/apk/res/android")。
绘制开始于布局的根节点,根节点将被用来测量并组织布局树。绘制的过程就是遍历树的过程,在此过程中还要舍弃无用的View。每个ViewGroup都会依次调用其子View的绘制方法(译注:通过ViewGroup的drawChild()方法调用childView的Draw()方法),而每个子View则开始绘制自己。由于树的遍历时有序的,这就意味着(1)ViewGroup总是优先于其子View先被绘制出来,(2)兄弟View按树中的顺序依次绘制。
绘制有两个过程:measure过程和layout过程。measure过程实现在measure()方法中,是一个自顶向下分析过程。在递归过程中,从布局树顶端开始,每一个View自上而下地推演自己的尺寸规格,当measure过程执行完毕,每一个View都获取了一个测量值;layout过程实现在layout()方法中,也是一个自顶向下分析过程。在layout过程中,ViewGroup利用measure过程中得到的测量值将子View放置在恰当的位置。
随着某个ViewGroup的子View的measure()方法返回,最终这个ViewGroup的measure()方法返回,其getMeasuredWidth()和getMeasuredHeight()方法的返回值将完成计算(译注:即measure()方法返回时,才可调用getMeasuredWidth()和getMeasuredHeight()方法获取这个View的原始测量宽度和原始测量高度,在我实际使用中发现getMeasuredWidth()比起getWidth()方法能更快地收敛到View的最终大小)。子View的原始测量宽度和原始测量高度必须遵守ViewGroup的布局约束,这样才能保证measure过程结束后,ViewGroup接受其子View的测量值。ViewGroup可能多次调用其子View的measure()方法,比如,ViewGroup首先不指定尺寸去调用每个子View的measure()方法,以判断子View需要多大的尺寸,如果在这种不受约束情况下,所有的子View的测量尺寸之和远超出或者远小于ViewGroup的容纳能力,那么ViewGroup将指定尺寸再次调用每个子View的measure()方法。
measure过程使用两个类来表示尺寸:View使用View.MeasureSpec类来向ViewGroup声明自己想要如何被测量和如何被放置;LayoutParams基类则仅负责描述View的宽度和高度。尺寸可以被指定为以下三种:
1)具体数值(布局中指定单位可用dp或者px,代码中指定则单位为px)
2)FILL_PARENT,View要求在ViewGroup中占尽可能大的空间。
3)WRAP_CONTENT,View要求在ViewGroup中占用足够放下其内容的空间即可。
此外,还有各种LayoutParams基类的子类用于向ViewGroup的子类提供不同的功能,比如RelativeLayout有其自己的RelativeLayout.LayoutParams子类用于提供额外的功能,比如让子View水平或垂直居中。
View.MeasureSpecs用于自顶向下地传递尺寸需求,可指定为以下三种:
1)UNSPECIFIED:这个参数是ViewGroup用于确定子View尺寸需求的,例如,Linearlayout可能会分辨将高度设定为UNSPECIFIED,而将宽度设定为具体的240px来调用其子View的measure()方法,以此确立在240px的宽度下,子View应该有多高。
2)EXACTLY:这个参数是ViewGroup设定具体的尺寸给一个子View,子View必须使用此尺寸并保证其子View适应此尺寸。
3)AT_MOST:这个参数是ViewGroup设定一个最大尺寸给一个子View,子View必须保证其子View适应此尺寸。
翻译完了,算是把View的基本绘制流程梳理了一遍,接下来分析View源码就不会弄得不明不白了~~~