1. 绘制顺序
绘制过程从布局的根节点开始,然后对整个布局树型结构(layout tree)进行测量并绘制,绘制过程沿着布局树型结构(layout tree)进行,依次渲染其中的各个View。在这个遍历过程中,遇到ViewGroup时,每个ViewGroup(注意,ViewGroup也属于View的一种)将依次绘制包含于其中的View;遇到View时,View将绘制自身。由于绘制的过程是沿着布局树型结构(layouttree)遍历的,所以,布局树型结构(layout tree)中的父节点将先于孩子结点被绘制,而兄弟结点则按照它们出现的顺序进行绘制。
2. pass传递
绘制布局需要经过两个pass process(传递过程?),分别是measure pass和layoutpass(可以翻译为测量的传递过程,计算布局的传递过程?)。measure pass是在方法measure(int,int) 中进行的,measure pass自上而下地递归遍历布局结构。在递归过程中,每个View都沿着布局树传递dimension specifications,在measure pass结束时,每个View都将获得自身的测量结果。第二个pass process是layout pass,这个过程在在layout(int,int,int,int) 中进行,并且和measurepass一样是自上而下的(top-down),在layout pass过程中,每个父结点都将利用measurepass的测量结果,计算出各个子结点的位置。
3.子节点的绘制冲突解决
各个View的测量宽度和测量高度必须遵从其父结点传递过来的限制条件(其实就是上文提到的dimension specifications)。这样才能保证在measure pass结束时,每个父节点能接受其各个子结点的测量结果。一个parent View可能不止触发其子结点的measure()方法依次,比如,父结点可能第一次首先测量每个没指定大小的子结点,测量出它们需要占用多大的空间,而如果经过第一轮测量,各个子结点的计算结果相互之间有所冲突,则父结点将再次调用各个子结点的onMeasure()方法测量各个子结点。
4. ViewGroup.LayoutParams和MeasureSpec
(1) ViewGroup.LayoutParams
View对象可以借助ViewGroup.LayoutParams对象去告诉它的父结点,它需要怎样的大小和放置在什么位置。最基础的ViewGroup.LayoutParams类只能描述View的高和宽。它可以指定的高和宽有以下三类
某个确切的数值
MATH_PARENT,代表该View希望和它的父结点一样大(减去padding)
WRAP_CONTENT,代表该View希望大小足够包围住它的内容(加上padding)
ViewGroup.LayoutParams有几个子类,它们分别为ViewGroup几个对应的子类而存在。比如,RelativeLayout有自己的一个ViewGroup.LayoutParams的子类,它有一个特殊的功能,可以讲子结点设置在水平方向上的中点,或者垂直方向上的中点。
(2) MeasureSpec
MeasureSpec对象是由父结点传递给子节点的一个对象,父结点对子结点的限制信息保存在该对象中。MeasureSpec对象可以有以下三种模式:
UNSPECIFIED:父结点对子结点的大小没有任何要求。
EXACTLY: 父结点要求其子节点的大小指定为某个确切的值。其子节点以及其他子孙结点都需要适应该大小。
AT MOST:父结点要求其子节点的大小不能超过某个最大值,其子节点以及其他子孙结点的大小都需要小于这个值
requestLayout
当一个VIEW的布局属性发生了变化的时候,可以调用该方法,让父VIEW调用onmeasure 和onlayout重新定位该view的位置,需要在UI线程调用
invalidate
强制使view重绘,需要在UI线程调用
postinvalidate
作用同上,但是可以直接在子线程调用
最高两位是00的时候表示"未指定模式"。即MeasureSpec.UNSPECIFIED
最高两位是01的时候表示"'精确模式"。即MeasureSpec.EXACTLY
最高两位是11的时候表示"最大模式"。即MeasureSpec.AT_MOST
典型用法:
1. private int getMeasuredLength(int length, boolean isWidth) {
2. int specMode = MeasureSpec.getMode(length);
3. int specSize = MeasureSpec.getSize(length);
4. int size;
5. int padding = isWidth ? getPaddingLeft() + getPaddingRight()
6. : getPaddingTop() + getPaddingBottom();
7. if (specMode == MeasureSpec.EXACTLY) {
8. size = specSize;
9. } else {
10. size = isWidth ? padding + mWave.length / 4 : DEFAULT_HEIGHT
11. + padding;
12. if (specMode == MeasureSpec.AT_MOST) {
13. size = Math.min(size, specSize);
14. }
15. }
16. return size;
17. }
5. view的measure过程
具体来说,View的父控件ViewGroup会调用View的measure方法,ViewGroup会将一些宽度和高度的限制条件传递给View的measure方法。
在View的measure方法会首先从成员变量中读取以前缓存过的量算结果,如果能找到该缓存值,那么就基本完事了,如果没有找到缓存值,那么measure方法会执行onMeasure回调方法,measure方法会将上述的宽度和高度的限制条件依次传递给onMeasure方法。onMeasure方法会完成具体的量算工作,并将量算的结果通过调用View的setMeasuredDimension方法保存到View的成员变量mMeasuredWidth 和mMeasuredHeight中。
量算完成之后,View的父控件就可以通过调用getMeasuredWidth、getMeasuredState、getMeasuredWidthAndState这三个方法获取View的量算结果。
measure
---------------------------------------------->
首先,我们要知道并不是只要View的measure方法执行的时候View就一定要傻傻的真的去做量算工作,View也喜欢偷懒,如果View发现没有必要去量算的话,那它就不会真的去做量算的工作。
具体来说,View先查看是不是要强制量算以及这次measure中传入的MeasureSpec与上次量算的MeasureSpec是否相同,如果不是强制量算或者MeasureSpec与上次的量算的MeasureSpec相同,那么View就不必真的去量算了。
如果不满足上述条件,View就考虑去做量算工作。但是在量算之前,View还想偷懒,它会以MeasureSpec计算出的key值作为键,去成员变量mMeasureCache中查找是否缓存过对应key的量算结果,如果能找到,那么就简单调用一下setMeasuredDimensionRaw方法,将从缓存中读到的量算结果保存到成员变量mMeasuredWidth和mMeasuredHeight中。
如果不能从mMeasureCache中读到缓存过的量算结果,那么这次View就真的不能再偷懒了,只能乖乖地调用onMeasure方法去完成实际的量算工作,并且将尺寸限制条件widthMeasureSpec和heightMeasureSpec传递给onMeasure方法
<=============================
onMeasure
------------------------------->
setMeasuredDimension ->getDefaultSize -> getSuggestedMinimumWidth/Height
如果没有给View设置背景,那么就返回View本身的最小宽度mMinWidth
如果给View设置了背景,那么就取View本身最小宽度mMinWidth和背景的最小宽度的最大值
那你可能有疑问,View中保存的最小宽度mMinWidth的值是从哪来的呢?实际上有两种办法给View设置最小宽度。
<===================
我们在onMeasure方法中发现,onMeasure会执行以下两行代码:getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
我们已经研究了getSuggestedMinimumWidth/Height,知道其会返回View的最小宽度和高度,现在我们开始研究getDefaultSize方法。Android会将View想要的尺寸以及其父控件对其尺寸限制信息measureSpec传递给getDefaultSize方法,该方法要根据这些综合信息计算最终的量算的尺寸。
getDefaultSize
------------------------------------------------------->
首先根据measuredSpec解析出对应的specMode和specSize
当mode是UNSPECIFIED时,View就直接用自己想要的尺寸size作为量算的结果
当mode是AT_MOST或EXACTLY时,View就用其父ViewGroup指定的尺寸作为量算的结果
最终,View会根据measuredSpec限制条件,得到最终的量算的尺寸。
这样在onMeasure方法中,
当执行getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)时,我们就得到了最终量算到的宽度值;
当执行getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)时,我们就得到了最终量算到的高度值。
<==================================
全部测量完了, 再把值保存到view的相关成员变量中
setMeasuredDimension
------------------------------------------------------------>
在前面我们研究onMeasure方法时就已经看到setMeasuredDimension会调用getDefaultSize方法,会将已经量算到的宽度值和高度值作为参数传递给setMeasuredDimension方法,
将量算完成的宽度measuredWidth保存到View的成员变量mMeasuredWidth中
将量算完成的高度measuredHeight保存到View的成员变量mMeasuredHeight中
最后将View的状态位mPrivateFlags设置为已量算状态
<====================================
6.viewgroup的读取量算结果的
为此,View提供了三组方法,分别是:
1. getMeasuredWidth和getMeasuredHeight方法: 返回测量的结果
2. getMeasuredWidthAndState和getMeasuredHeightAndState方法: 返回测量结果和状态
3. getMeasuredState方法: 返回测量状态
子view返回的mMeasuredWidth
-------------------------------------------------------->
其高位的第一个字节为第一部分,用于标记量算完的尺寸是不是达到了View想要的宽度,我们称该信息为量算的state信息。其低位的三个字节为第二部分,用于存储实际的量算到的宽度。
由此我们可以看出Android真是物尽其用,一个变量能包含两个信息,这个有点类似于measureSpec的道理,但是二者又有不同:
measureSpec是将限制条件mode从ViewGroup传递给其子View。mMeasuredWidth、mMeasuredHeight是将带有量算结果的state标志位信息从View传递给其父ViewGroup。
<==================================
resolveSizeAndState
-------------------------------------------------------->
子view向父view表达自己对父view限制的不满
当specMode为AT_MOST,并且父控件指定的尺寸specSize小于View自己想要的尺寸时,我们就会用掩码MEASURED_STATE_TOO_SMALL向量算结果加入尺寸太小的标记,这样其父ViewGroup就可以通过该标记其给子View的尺寸太小了,然后可能分配更大一点的尺寸给子View。
getDefaultSize方法只是onMeasure方法中获取最终尺寸的默认实现,其返回的信息比resolveSizeAndState要少,那么什么时候才会调用resolveSizeAndState方法呢?主要有两种情况:
Android中的许多layout类都调用了resolveSizeAndState方法,比如LinearLayout在量算过程中会调用resolveSizeAndState方法而非getDefaultSize方法。 我们自己在实现自定义的View或ViewGroup时,我们可以重写onMeasure方法,并在该方法内调用resolveSizeAndState方法。
<======================================