【Android小品】从使用出发完全理解View(ViewGroup)测量机制,并分析部分源码(修复图片)
2016-11-29 17:06
483 查看
在学习过程中在自定义View、ViewGroup的时候经常会碰到尺寸测量方面的问题。开始我以为已经理解了测量机制,实际上发现理解是错误的,Android的View测量机制真的有点“迷”。市面上把onMeasure()和onLayout()分开来分析的文章有很多,但是把二者结合起来,并且梳理整套逻辑的文章比较少。因此我打算自己动手,把View的测量机制搞清楚!
1. wrap_content
尺寸应该由内容的大小决定(例如,TextView的高度可以由内部的文本行数、字号决定,可以理解为,View自己根据内容的大小确定自己的大小)
2. match_parent(API 8 以前老版本叫做FILL_PARENT)
尺寸由它的父亲决定(例如,TextView的宽度我们往往希望和整个屏幕宽度对齐)
3. 具体的数字(可以带单位,不过系统在读取时会自动转换为像素值)
这个很好理解,指定多大就多大
这些XML属性实际上对应的是LayoutParms类
根据文档:
概念一、LayoutParms是view用来告诉上一级Viewgroup,View自己想要怎么进行测量。(或者说使用View的开发者想要怎么测量)
MeasureSpec包含了父空间对子控件的测量要求。每个对象都包含了两方面信息,尺寸(宽度、高度)、模式(UNSPECIFIED、EXACTLY、AT_MOST)
UNSPECIFIED
父亲没有指定任何约束
EXACTLY
父亲已经指定了具体的尺寸
AT_MOST
子View像多大就多大,但是要小于最大值
概念二、MeasureSpec只是一个容器对象,可以通过它的getxxx(…)传入一个int型变量(key),获取一些信息(value)
我们等会儿也顺便看一下MeasureSpec是如何实现的
onMeasure是让view自己测量出自己的width、height。在复写本方法之后,必须调用**setMeasuredDimension(测量完毕的宽,测量完毕的高)** ,否则会报异常。子view应该保证测量出来的宽高分别大于**getSuggestedMinimumWidth()**、**getSuggestedMinimumHeight()**
![这里写图片描述](https://img-blog.csdn.net/20161126204631536)
顾名思义,记录了上次测量后的结果
![这里写图片描述](https://img-blog.csdn.net/20161126204850261)
返回view的真实大小
设置view的真实边界
当系统需要本ViewGroup指定View的位置时被调用
(为了让大家看的更清楚,代码中剔除了一些不太重要的代码,大家可以看注释来大概理解一下代码就好了,没什么特别)
ViewGroup调用了measureChildren
View: onMeasure被调用 宽度模式: WRAP_CONTENT 高度模式:WRAP_CONTENT
ViewGroup: onMeasure被调用 宽度模式:MATCH_PARENT或者精确值 高度模式:MATCH_PARENT或者精确值
ViewGroup调用了measureChildren
View: onMeasure被调用 宽度模式:WRAP_CONTENT 高度模式:WRAP_CONTENT
ViewGroup调用了onLayout
ViewGroup: onMeasure被调用 宽度模式:MATCH_PARENT或者精确值 高度模式:MATCH_PARENT或者精确值
ViewGroup调用了measureChildren
View: onMeasure被调用 宽度模式:WRAP_CONTENT 高度模式:WRAP_CONTENT
ViewGroup调用了onLayout
View width: 100
View MeasuredWidth: 984
布局文件给view的宽高设置的是WRAP_CONTENT,但是我们在Layout类中可指定的是EXACTLY。根据输出来看,View最终收到的参数还是WRAP_CONTENT。那么为什么在measureChildren(…)传入的效果没有达到效果呢,设置来有什么用?
getWidth返回100,getMeasuredWidth返回是984,二者区别是什么
viewgroup似乎能够通过layout来“指定”view的大小(因为我们虽然其他地方都没有传入宽度、高度,只是在onLayout中调用了view.layout(0,0,100,100),view的宽度就真的变成100了,说明viewGroup最终通过调用每一个子view的layout(…)方法决定子view的大小),决定view自己本身的因素到底是谁?
LayoutParm、MeasureSpec对应关系到底是什么
通过以上几个问题就可以感受到View(ViewGroup)测量机制并非那么简单,我们带着这些问题来阅读源码看看具体实现。
这里onMeasure的60行是Layout类的onMeasure中我们自己写的那一句,所以从这一句开始找就好了。
measureChildren(…)对所有子view进行遍历并且调用对每个view调用measureChild(…)而且传入了ViewGroup的MeasureSpec int类型的key
下面看measureChild(…)
这里面出现了LayoutParms,调用了一个private函数getChildMeasureSpec(ViewGroup传入的MeasureSpec相关int型key+子view的LayoutParm内存放的值)。注意,这里的lp.width,lp.height不一定必须是精确值,也可以是WRAP_CONTENT(-2)或者MATCH_PARENT(-1)。所以说相当于传入了LayoutParm。这里相当于是根据几个数据算出来的MeasureSpec。
这里先不着急看child.measure(…),先看如何算出MeasureSpec的
由于代码太长,不方便截图了,下面是官方源码+我自己写的注释,分析都在里边。如果觉得看着头大就多看几遍。
分析了这么多我们可以从这个重要的函数中了解到很多东西:
1.我们发现LayoutParms和测量模式之间是有很大关系的。可以这样说,是父view的LayoutParms和子view的LayoutParms共同决定子view的测量模式!
2.子view应该是哪种测量模式呢?,我们用以下表格来表示
3.从上表可以得出LayoutParms和测量模式的对应关系(父亲的测量模式不为UNDEFINED)
4.从代码中看到,需要(int)xxxxMeasureSpec的地方绝对不能传什么MeasureSpec.WRAP_CONTENT这种。只能用系统传进来的。否则会造成测量错误。如果实在需要自己构造,那么请使用MeasureSpec.makeMeasureSpec(size, mode)
5.最终决定View如何测量的是测量模式,而不是LayoutParm
在了解了getChildMeasureSpec(…)之后,我们来继续回去
分析一下child.measure(…);
可以看到,主要是为了调用onMeasure做一些准备。其中比较重要的就是出现了一些旗标,如果没有调用setMeasuredDimension(…)就会报异常
那么setMeasuredDimension(…)又干了什么呢?
不言自明,可以看到执行完这个之后,就可以调用getMeasuredxxx(…)了。
A1.可以看到,在(view)v.layout(…)函数中有调用onMeasure的行为。这个我们可以这样去看。因为measureChildren(…)是可以随便去调的。当layout有需要的时候就应该去调用。父view实际上甚至可以直接指定子view的测量模式为undefined,以达到让所有子view都measure一遍的效果(因为不管子view是什么模式,都会被指定测量模式为UNSPECIFIED,会一直传递下去)。也给我们自定义view提了个醒,那就是UNSPECIFIED应该根据内容返回大小,和AT_MOST差不多,只不过无法获取父亲的值而已。
Q2. 布局文件给view的宽高设置的是WRAP_CONTENT,但是我们在Layout类中可指定的是EXACTLY。根据输出来看,View最终收到的参数还是WRAP_CONTENT。那么为什么在measureChildren(…)传入的效果没有达到效果呢,设置来有什么用?
A2.这个答案已经在上面的分析中了
Q3. getWidth返回100,getMeasuredWidth返回是984,二者区别是什么
这个我们需要看一下layout方法的源码
Q4. viewgroup似乎能够通过layout来“指定”view的大小(因为我们虽然其他地方都没有传入宽度、高度,只是在onLayout中调用了view.layout(0,0,100,100),view的宽度就真的变成100了,说明viewGroup最终通过调用每一个子view的layout(…)方法决定子view的大小),决定view自己本身的因素到底是谁?
A4.最终决定权在ViewGroup的onLayout传入的矩形尺寸。getWidth()/getHeight()就是最终onLayout()中获得的实际值。而onMeasuredxxx(…)获取的是测量值。
Q5. LayoutParm、MeasureSpec对应关系到底是什么
A5.文中已经提到
邮箱:gammapi@qq.com
保留版权,抄袭必究
希望您能在评论区指出宝贵意见,也欢迎关注我的微信号与我交流互动
一、梳理背景知识
1.LayoutParms
我们知道,在XML文件里面,所有的View和ViewGroup都可以(其实是必须)指定下面两个属性。根据官方文档来看总共有三种情况android:layout_width="xxx" android:layout_height="xxx"
1. wrap_content
尺寸应该由内容的大小决定(例如,TextView的高度可以由内部的文本行数、字号决定,可以理解为,View自己根据内容的大小确定自己的大小)
2. match_parent(API 8 以前老版本叫做FILL_PARENT)
尺寸由它的父亲决定(例如,TextView的宽度我们往往希望和整个屏幕宽度对齐)
3. 具体的数字(可以带单位,不过系统在读取时会自动转换为像素值)
这个很好理解,指定多大就多大
这些XML属性实际上对应的是LayoutParms类
根据文档:
概念一、LayoutParms是view用来告诉上一级Viewgroup,View自己想要怎么进行测量。(或者说使用View的开发者想要怎么测量)
2.MeasureSpec
MeasureSpec包含了父空间对子控件的测量要求。每个对象都包含了两方面信息,尺寸(宽度、高度)、模式(UNSPECIFIED、EXACTLY、AT_MOST)
UNSPECIFIED
父亲没有指定任何约束
EXACTLY
父亲已经指定了具体的尺寸
AT_MOST
子View像多大就多大,但是要小于最大值
概念二、MeasureSpec只是一个容器对象,可以通过它的getxxx(…)传入一个int型变量(key),获取一些信息(value)
我们等会儿也顺便看一下MeasureSpec是如何实现的
3.与开发者相关的一些函数
注意:这里只是罗列,之后会具体的分析这些函数的作用和差异,现在只需要大概了解即可,无需深究View
(View)v.onMeasure(…)
![这里写图片描述](https://img-blog.csdn.net/20161126203328535)onMeasure是让view自己测量出自己的width、height。在复写本方法之后,必须调用**setMeasuredDimension(测量完毕的宽,测量完毕的高)** ,否则会报异常。子view应该保证测量出来的宽高分别大于**getSuggestedMinimumWidth()**、**getSuggestedMinimumHeight()**
(View)v.getMeasured[Width/Height](…)
![这里写图片描述](https://img-blog.csdn.net/20161126204339328)![这里写图片描述](https://img-blog.csdn.net/20161126204631536)
顾名思义,记录了上次测量后的结果
(View)v.get[Width/Height](…)
![这里写图片描述](https://img-blog.csdn.net/20161126204945855)![这里写图片描述](https://img-blog.csdn.net/20161126204850261)
返回view的真实大小
(View)v.layout(…)
![这里写图片描述](https://img-blog.csdn.net/20161128201554571)设置view的真实边界
ViewGroup
(ViewGroup)onLayout(…)
![这里写图片描述](https://img-blog.csdn.net/20161128135504903)当系统需要本ViewGroup指定View的位置时被调用
(ViewGroup)measureChild(…)
![这里写图片描述](https://img-blog.csdn.net/20161128135428715)(ViewGroup)measureChildren(…)
![这里写图片描述](https://img-blog.csdn.net/20161128135535498)(ViewGroup)measureChildWithMargins(…)
![这里写图片描述](https://img-blog.csdn.net/20161128135700938)二、Log跟踪测量流程
我们自己实现两个类,复写一些我们感兴趣的方法打Log来试试看。(为了让大家看的更清楚,代码中剔除了一些不太重要的代码,大家可以看注释来大概理解一下代码就好了,没什么特别)
布局文件
<?xml version="1.0" encoding="utf-8"?> <com.xttech.onmeasureonlayouttest.Layout android:layout_width="match_parent" android:layout_height="match_parent"> <com.xttech.onmeasureonlayouttest.View android:layout_width="wrap_content" android:layout_height="wrap_content" /> </com.xttech.onmeasureonlayouttest.Layout>
(ViewGroup)Layout
package com.xttech.onmeasureonlayouttest; /* * 这个是自定义的布局 */public class Layout extends ViewGroup { /* * onMeasure方法 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { /* * 直到下一行注释的代码的作用是从MeasureSpec中获取给本view传入的测量模式 * 1.如果是AT_MOST,就说明指定的是WRAP_CONTENT * 2.如果是EXACTLY,就说明是MATCH_PARENT或者精确值 */ String widthMode = null; String heightMode = null; switch (MeasureSpec.getMode(widthMeasureSpec)) { case MeasureSpec.AT_MOST: widthMode = "WRAP_CONTENT"; break; case MeasureSpec.EXACTLY: widthMode = "MATCH_PARENT或者精确值"; break; case MeasureSpec.UNSPECIFIED: widthMode = "UNSPECIFIED"; break; } switch (MeasureSpec.getMode(heightMeasureSpec)) { case MeasureSpec.AT_MOST: heightMode = "WRAP_CONTENT"; break; case MeasureSpec.EXACTLY: heightMode = "MATCH_PARENT或者精确值"; break; case MeasureSpec.UNSPECIFIED: heightMode = "UNSPECIFIED"; break; } //打印onMeasure被调用、以及宽度模式的信息 Log.d(TAG, "ViewGroup: onMeasure被调用 宽度模式:" + widthMode + " 高度模式:" + heightMode); //调用measureChildren方法,传入两个EXACTLY作为参数 Log.d(TAG, "ViewGroup调用了measureChildren"); measureChildren(widthMeasureSpec, heightMeasureSpec); //告诉系统测量的结果(仍然不变) setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //给子view指定边界 Log.d(TAG, "ViewGroup调用了onLayout"); getChildAt(0).layout(0, 0, 100, 100); } }
(View)View
package com.xttech.onmeasureonlayouttest; //自定义view public class View extends android.view.View { @Override //onMeasure protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { /* * 直到下一行注释的代码的作用是从MeasureSpec中获取给本view传入的测量模式 * 1.如果是AT_MOST,就说明指定的是WRAP_CONTENT * 2.如果是EXACTLY,就说明是MATCH_PARENT或者精确值 */ String widthMode = null; String heightMode = null; switch (MeasureSpec.getMode(widthMeasureSpec)) { case MeasureSpec.AT_MOST: widthMode = "WRAP_CONTENT"; break; case MeasureSpec.EXACTLY: widthMode = "MATCH_PARENT或者精确值"; break; case MeasureSpec.UNSPECIFIED: widthMode = "UNSPECIFIED"; break; } switch (MeasureSpec.getMode(heightMeasureSpec)) { case MeasureSpec.AT_MOST: heightMode = "WRAP_CONTENT"; break; case MeasureSpec.EXACTLY: heightMode = "MATCH_PARENT或者精确值"; break; case MeasureSpec.UNSPECIFIED: heightMode = "UNSPECIFIED"; break; } //打印onMeasure被调用、以及宽度模式的信息 Log.d(TAG, "View: onMeasure被调用 宽度模式:" + widthMode + " 高度模式:" + heightMode); //告诉系统测量的结果(仍然不变) setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)); } //onDraw @Override protected void onDraw(Canvas canvas) { //用getWidth()获取view的width Log.d(TAG, "View width: " + getWidth()); //用getMeasuredWidth()来获取view的width Log.d(TAG, "View MeasuredWidth: " + getMeasuredWidth()); } }
输出结果
ViewGroup: onMeasure被调用 宽度模式:MATCH_PARENT或者精确值 高度模式:MATCH_PARENT或者精确值ViewGroup调用了measureChildren
View: onMeasure被调用 宽度模式: WRAP_CONTENT 高度模式:WRAP_CONTENT
ViewGroup: onMeasure被调用 宽度模式:MATCH_PARENT或者精确值 高度模式:MATCH_PARENT或者精确值
ViewGroup调用了measureChildren
View: onMeasure被调用 宽度模式:WRAP_CONTENT 高度模式:WRAP_CONTENT
ViewGroup调用了onLayout
ViewGroup: onMeasure被调用 宽度模式:MATCH_PARENT或者精确值 高度模式:MATCH_PARENT或者精确值
ViewGroup调用了measureChildren
View: onMeasure被调用 宽度模式:WRAP_CONTENT 高度模式:WRAP_CONTENT
ViewGroup调用了onLayout
View width: 100
View MeasuredWidth: 984
一些疑问
上面的输出结果我进行了分段,可以发现在view的onDraw被调用之前一共进行了三次测量,为什么会进行这么多次呢?布局文件给view的宽高设置的是WRAP_CONTENT,但是我们在Layout类中可指定的是EXACTLY。根据输出来看,View最终收到的参数还是WRAP_CONTENT。那么为什么在measureChildren(…)传入的效果没有达到效果呢,设置来有什么用?
getWidth返回100,getMeasuredWidth返回是984,二者区别是什么
viewgroup似乎能够通过layout来“指定”view的大小(因为我们虽然其他地方都没有传入宽度、高度,只是在onLayout中调用了view.layout(0,0,100,100),view的宽度就真的变成100了,说明viewGroup最终通过调用每一个子view的layout(…)方法决定子view的大小),决定view自己本身的因素到底是谁?
LayoutParm、MeasureSpec对应关系到底是什么
通过以上几个问题就可以感受到View(ViewGroup)测量机制并非那么简单,我们带着这些问题来阅读源码看看具体实现。
三、追踪源码
跟踪onMeasure
首先,我们从onMeasure入手,看看系统是如何调用onMeasure的,然后分析相关的源码。这里onMeasure的60行是Layout类的onMeasure中我们自己写的那一句,所以从这一句开始找就好了。
measureChildren(MeasureSpec.EXACTLY, MeasureSpec.EXACTLY);
下面我们来看源码
measureChildren(…)对所有子view进行遍历并且调用对每个view调用measureChild(…)而且传入了ViewGroup的MeasureSpec int类型的key
下面看measureChild(…)
这里面出现了LayoutParms,调用了一个private函数getChildMeasureSpec(ViewGroup传入的MeasureSpec相关int型key+子view的LayoutParm内存放的值)。注意,这里的lp.width,lp.height不一定必须是精确值,也可以是WRAP_CONTENT(-2)或者MATCH_PARENT(-1)。所以说相当于传入了LayoutParm。这里相当于是根据几个数据算出来的MeasureSpec。
这里先不着急看child.measure(…),先看如何算出MeasureSpec的
由于代码太长,不方便截图了,下面是官方源码+我自己写的注释,分析都在里边。如果觉得看着头大就多看几遍。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { //ViewGroup传入自己的信息(测量模式、以及具体尺寸) int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); //如果说ViewGroup传入的尺寸-内边框<0,那么就取0,因为尺寸不可能为负值 //特别注意,不要在measureChild里面传入MeasureSpec.WRAP_CONTENT这样的值 //因为这个int是一个key而不是一个mode,如果传入MeasureSpec.WRAP_CONTENT这样的值,系统就找不到对应的MeasureSpec //实际上这个spec是自己本身(viewGroup)的LayoutParms决定的 int size = Math.max(0, specSize - padding); //计算出来的结果量 int resultSize = 0; int resultMode = 0; switch (specMode) { // 自己本身(viewGroup)的测量模式是Exactly(代表自己的layoutParm是match_parent或者精确值) case MeasureSpec.EXACTLY: if (childDimension >= 0) { //如果子view的layoutParm是精确值,那么子view的测量模式应该是EXACTLY! //尺寸就是子view LayoutParam指定的尺寸 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { //如果子view的LayoutParm是MATCH_PARENT,那么子view的测量模式应该是EXACTLY! //尺寸就是子view的父view(也就是自己本身)的尺寸 //这就解释了为什么LayoutParm设置为match_parent测量模式是Exactly,因为实际 //上在进行测量模式计算的时候会给size赋值,只不过这个值是根据子View的父view //的大小确定的 resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { //如果子view的LayoutParm是WRAP_CONTENT,那么子view的测量模式应该是AT_MOST! //这个时候实际上view的尺寸是自己确定的。想想看如果我们在子view的onMeasure中碰到了 //AT_MOST测量模式,那么我们会忽略掉size,直接自顾自的进行测量。 //实际上,子view的大小应该是要小于父view的大小的。而在AT_MOST时,通过MeasureSpec的size //就可以获取到父亲的大小 resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; //上面一个case代表自己本身(viewGroup)的尺寸是确定的。要么是在layout文件中设置了具体的尺寸 //要么就是因为在layout文件中设置了match_parent的再上一级父viewgroup为自己指定的 //但是如果自己也是wrap_content怎么办呢? case MeasureSpec.AT_MOST: if (childDimension >= 0) { //如果子view的layoutParm是精确值,那么子view的测量模式应该是EXACTLY! //尺寸就是子view LayoutParam指定的尺寸 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { //如果子view的LayoutParm是MATCH_PARENT,我们以为子view的测量模式应该是EXACTLY! //但是这样是有问题的。因为自己本身(viewGroup)的尺寸就还没有确定,怎么给子view提供值呢? //所以只能让子view的测量模式设置为AT_MOST(相当于把子view的layout_parm改为了wrap_content) resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { //如果子view的LayoutParm是WRAP_CONTENT,那么子view的测量模式应该是AT_MOST! //这个时候实际上view的尺寸是自己确定的。想想看如果我们在子view的onMeasure中碰到了 //AT_MOST测量模式,那么我们会忽略掉size,直接自顾自的进行测量。 //实际上,子view的大小应该是要小于父view的大小的。而在AT_MOST时,通过MeasureSpec的size //就可以获取到父亲的大小 resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // 竟然没有指定LayoutParms,就连new一个view出来添加到布局中去 // LayoutParams默认就是wrap_content...... // 所以只有自己调用MeasureSpec.makeMeasureSpec(...)强制指定一个才可以! case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { //如果子view的layoutParm是精确值,那么子view的测量模式应该是EXACTLY! //尺寸就是子view LayoutParam指定的尺寸 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { //如果子view的LayoutParm是MATCH_PARENT,我们以为子view的测量模式应该是EXACTLY! //但是这样是有问题的。因为自己本身(viewGroup)的尺寸就还没有确定,怎么给子view提供值呢? //这里比较特殊,直接给子view设置了UNSPECIFIED resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { //如果子view的LayoutParm是WRAP_CONTENT,那么子view的测量模式应该是AT_MOST! //这里比较特殊,直接给子view设置了UNSPECIFIED resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } //最后构造了一个新的MeasureSpec return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
分析了这么多我们可以从这个重要的函数中了解到很多东西:
1.我们发现LayoutParms和测量模式之间是有很大关系的。可以这样说,是父view的LayoutParms和子view的LayoutParms共同决定子view的测量模式!
2.子view应该是哪种测量模式呢?,我们用以下表格来表示
父 LayoutParm | 子LayoutParm | 子测量模式 | 子测量得到的尺寸 |
---|---|---|---|
精确值 | 精确值 | EXACTLY | 子LayoutParm定义的精确值 |
精确值 | match_parent | EXACTLY | 父LayoutParm的测量值 |
精确值 | wrap_content | AT_MOST | 父LayoutParm的测量值 |
match_parent | 精确值 | EXACTLY | 子LayoutParm定义的精确值 |
match_parent | match_parent | EXACTLY | 父的父 提供的值 |
match_parent | wrap_content | AT_MOST | 父的父 提供的值 |
wrap_content | 精确值 | EXACTLY | 子LayoutParm定义的精确值 |
wrap_content | match_parent | AT_MOST | 父自己测量的值 |
wrap_content | wrap_content | AT_MOST | 父自己测量的值 |
View的LayoutParm | 测量模式 |
---|---|
精确值 | EXACTLY |
match_parent(父不为wrap_content) | EXACTLY |
match_parent(父不为wrap_content) | AT_MOST |
wrap_content | AT_MOST |
5.最终决定View如何测量的是测量模式,而不是LayoutParm
在了解了getChildMeasureSpec(…)之后,我们来继续回去
分析一下child.measure(…);
可以看到,主要是为了调用onMeasure做一些准备。其中比较重要的就是出现了一些旗标,如果没有调用setMeasuredDimension(…)就会报异常
那么setMeasuredDimension(…)又干了什么呢?
不言自明,可以看到执行完这个之后,就可以调用getMeasuredxxx(…)了。
四、问题解答
Q1. 上面的输出结果我进行了分段,可以发现在view的onDraw被调用之前一共进行了三次测量,为什么会进行这么多次呢?A1.可以看到,在(view)v.layout(…)函数中有调用onMeasure的行为。这个我们可以这样去看。因为measureChildren(…)是可以随便去调的。当layout有需要的时候就应该去调用。父view实际上甚至可以直接指定子view的测量模式为undefined,以达到让所有子view都measure一遍的效果(因为不管子view是什么模式,都会被指定测量模式为UNSPECIFIED,会一直传递下去)。也给我们自定义view提了个醒,那就是UNSPECIFIED应该根据内容返回大小,和AT_MOST差不多,只不过无法获取父亲的值而已。
Q2. 布局文件给view的宽高设置的是WRAP_CONTENT,但是我们在Layout类中可指定的是EXACTLY。根据输出来看,View最终收到的参数还是WRAP_CONTENT。那么为什么在measureChildren(…)传入的效果没有达到效果呢,设置来有什么用?
A2.这个答案已经在上面的分析中了
Q3. getWidth返回100,getMeasuredWidth返回是984,二者区别是什么
这个我们需要看一下layout方法的源码
Q4. viewgroup似乎能够通过layout来“指定”view的大小(因为我们虽然其他地方都没有传入宽度、高度,只是在onLayout中调用了view.layout(0,0,100,100),view的宽度就真的变成100了,说明viewGroup最终通过调用每一个子view的layout(…)方法决定子view的大小),决定view自己本身的因素到底是谁?
A4.最终决定权在ViewGroup的onLayout传入的矩形尺寸。getWidth()/getHeight()就是最终onLayout()中获得的实际值。而onMeasuredxxx(…)获取的是测量值。
Q5. LayoutParm、MeasureSpec对应关系到底是什么
A5.文中已经提到
关注作者
作者:Steven邮箱:gammapi@qq.com
保留版权,抄袭必究
希望您能在评论区指出宝贵意见,也欢迎关注我的微信号与我交流互动
更新进程
日期 | 日志 | 作者 |
---|---|---|
2016年11月28日 | 创建文档 | Steven |
相关文章推荐
- Android触摸屏ViewGroup事件派发机制详解与源码分析
- Android中ViewGroup、View事件分发机制源码分析总结(雷惊风)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下,ViewGroup篇)
- View 相关 Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android应用开发原理之从ViewGroup源码分析ViewGroup的事件分发机制
- Android事件分发机制源码分析下----ViewGroup事件分发分析
- Android ViewGroup 触摸屏事件派发机制和源码分析
- View相关 Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android View和ViewGroup事件分发机制源码分析
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上,view)
- Android触摸事件派发机制源码分析之ViewGroup
- Android中View(视图)绘制不同状态背景图片原理深入分析以及StateListDrawable使用详解
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下) 。
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)