1.为什么要进行Measure?
替人做了原本应该做的工作。在写xml的时候,布局参数如 wrap_content,match_parent,weight 等等给我们开发界面的时候带来方便,但是机器可是死的,最终绘制界面的时候需要的是明确数字意义上的宽高数据
总之:measure过程类似执行一套算法,将整个View的宽高值给最终确定下来。
2.Measure从根开始
每个页面的视图结构就像View的树,从根开始产生很多分支,分支又产生分支。
最低层的View 是DecorView .然后再到其子组件,然后再衍生下去。
3.不得不说的工具类:MeasureSpec
A MeasureSpec encapsulates the layout requirements passed from parent to child。
在父视图组件即将要让子组件测量的时候,并且要传给子组件测量所需要的数据。这些数据都是用这个工具类中的方法合成的,解析的时候同样要用到它。
3.1.数据结构是什么?
- 外表开起来是一个int型,
- 0~29位都是size,为宽/高尺寸。
- 30~31位是mode.
3.2mode的三种类型及含义
3.2.1.EXACTLY
3.2.2.AT_MOST
3.2.3.UNSPECIFIED
场景是什么?
ScrollView中的子View们,他们在高度上是没有限制的, ScrollView在高度上面会赋予他们UNSPECIFIED,自身有多高就显示多高。
4.View测量的第一步:View的measure()方法
在视图界里,万物皆由View衍生。并且它的measure()是final的,所以所有的子类都会原封不动的使用该方法。可以说理解它是View测量过程的第一步。
4.1.measure方法的入参
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
以下是官方解释:
The parent supplies constraint information in the width and height parameters
4.1.1.为什么父View要为子View计算尺寸约束呢?
这些尺寸约束是子测量自身所必要的参数:
1.match_parent,wrap_content并非具体尺寸值。比如子View的布局参数是match_parent ,这并非一个具体的尺寸大小,不让父给传尺寸下来,它自己是无法得到. 见【表一】,很多场景下子View自身size就是parentSize
2.mode模式也是需要结合父View的布局模式及子View的一起来决定的,比如子View的布局参数是match_parent , 如果父View的布局Mode是EXACTLY ,子View的也就是EXACTLY ,如果父View的布局Mode是AT_MOST, 则子View也是AT_MOST。 见【表一】
4.1.2.把父的参数传给子不就得了,为什么不让子View自己来计算这些尺寸约束呢?
这个其实就是代码设计的一种思路了,这个主要看看开发这部分功能跟父View牵涉的多一些还是跟子View多一些。交给子View的是一种尺寸约束(有的时候并不是最终很明确的尺寸数据,比如在AT_MOST时),父View这里起到类似资源分配者的作用,它需要根据自己的空间资源情况及子View的诉求来得出来约束值。那么父View一方面需要了解自身的padding ,marging,布局参数以及其他子View已占用的空间,显然牵涉父View的内容会更多一些。
4.1.3.父View下发的尺寸约束值,子View为之奈何?
场景一。如ViewGroup,如其布局参数为wrap_content,那么他在计算得到自己的尺寸后,是不能超过父View的尺寸限制值,这就是限制值的威力。
场景二。同样上面的ViewGroup,如其布局参数为match_parent,那么它的尺寸是定死了的,就是父View赋予的尺寸。
4.2.measure方法里基本上干了什么?
下面是绝大部分代码:
总体来说很简单,但是需要将几个重要的角色了解清楚
4.2.1.mMeasureCache
它是一个key是Long,值也是Long的Map.
key的前32位是入参widthMeasureSpec,低32位是入参heightMeasureSpec。其代表了measure过程的输入
value的前32位测量出来的结果mMeasuredWidth,低32位是mMeasuredHeight。其代表了measure过程的输出
那么mMeasureCache的使命是?
如果某种测量曾经出现过了(入参和之前的相同),那么就不用再折腾了,测量结果在内存里还存着呢,毕竟测量工作还是很耗费资源的。直接从map里取出测量结果然后调用setMeasuredDimensionRaw设置即可。问题: 那对于ViewGroup怎么办呢?
4.2.2.mOldWidthMeasureSpec
上次measure()方法的入参。
它的使命就是: 防止重复测量,对于和上次相同的入参就没必要再测量了。
小结
measure()方法本身没有多少和测量相关的实质操作,其一堆代码的作用就是“防止重复测量”,而测量的核心工作在onMeasure()里做的。
5.测量的核心一步:onMeasure()方法
onMeasure()里进行了实际的测量工作,View的子类一般都会对该方法进行覆写。
该从哪里开始呢?View树结构中最基层的根部就是一个垂直方向的LinearLayout,那我们就从他开始吧。
5.1.LinearLayout
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
我们研究垂直方向就从measureVertical方法开始吧,measureVertical的代码非常的长!
主体上有两次对其包含的子组件的遍历及测量 那为什么会有两次测量呢?
5.1.1.第一次是正常的对子组件进行测量。
看看红框的条件,在条件满足的时候的子组件是不执行第一次测量的。
5.1.2.第二次是在有权重设置的时候对子组件根据权重值大小的再次分配。
看代码块A中条件
heightSize是线性布局实际拥有的高度。
mTotalLength则是完成对所有子组件测量
后需要的总高度,delta 就是“实际”和“期望”的差值,这个差值有可能为正,也可能
为负。
只有满足A中的条件才会进行第二次绘制。
再看看代码块B:
childExtra 子组件的weight值
weightSum 是线性布局的权重总和,如果没有设置,则会将所有子组件的权重加起来。
share = (childExtra /weightSum)*delta
上面公式说明,权重分割的不是整个线性布局的高度,而是分割的“实际”和“期望”的差值
代码块C :share会更新到对应子组件的新高度,并对子组件重新测量。
5.2.发散:为什么说应该在onLayout时getMeasuredWidth/Height?
在包含权重的线性布局中会测量两次,而第二次的宽高值是经过权重重新分配的,显然如果我们拿第一次的测量结果的话,那就是不对的。所以最好是等待所有的测量结束后,而当进行onLayout时,测量过程都结束了。
6.说了这么多,measure的结果是什么?
换句话说,经过了Measure流程后,产生的影响或输出是什么?我们得到了什么?
经过measure流程后,得到的输出如下:
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
在View中mMeasuredWidth,mMeasuredHeight相应得到测量的宽高值。
6.1.我们如何获取测量的宽高值呢?
getMeasuredWidth();getMeasuredHeight();
6.2.getMeasuredHeight和getMeasuredWidthAndState的区别是什么?
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
原始的mMeasuredWidth的第24到31位是用来存储状态,这个状态有
public static final int MEASURED_STATE_TOO_SMALL = 0x01000000;
它的意义是什么呢?
如上图,size 为View自身想要的尺寸大小,而specSize是父View所能提供的最大尺寸,现在尴尬的情况是View想要的尺寸大小超过了父View所能提供的最大的尺寸,这个时候只能限定View的尺寸为specSize,结果是该View会显示不正常,因为挤得。现在了解它的作用了吧。
6.2.1.getMeasuredState可以判断ViewGroup中的子View是否挤不下了。
public final int getMeasuredState() {
return (mMeasuredWidth&MEASURED_STATE_MASK)
| ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}
这样做的结果就是总共32位的int结果值,前4个字节用来存储width的测量状态。后4个字节用来存储height的测量状态。
检测有很大限制,那LinearLayout来说,它要求其为wrap_content,并且子View的尺寸是Exactly的超过最大尺寸。
7.如何在Activity中正确的拿到某View的测量宽高值呢?
7.1.ViewTreeObserver
@Override
protected void onStart() {
super.onStart();
final ViewTreeObserver observer = textView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
observer.removeOnGlobalLayoutListener(this);
int width = textView.getMeasuredWidth();
int height = textView.getMeasuredHeight();
}
});
}
7.2.post()
@Override
protected void onStart() {
super.onStart();
textView.post(new Runnable() {
@Override
public void run() {
int width = textView.getMeasuredWidth();
int height = textView.getMeasuredHeight();
}
});
}
7.3.Activity.onWindowFocusChanged
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(hasFocus) {
int width = textView.getMeasuredWidth();
int height = textView.getMeasuredHeight();
}
}