本文探究测量的全过程,不是简单的介绍onMeasure()函数!
一、测量的目的
如果安卓的布局体系中全部都是精确的值,那就没有必要关心测量过程了。事实上,在布局文件中写的布局参数都是match_parent,wrap_parent或者精确值。
测量的目的就是为了将match_parent,wrap_parent这些相对大小,转换为具体的值。
二、测量的依据
应用开发者有三种方式表示长宽:match_parent,wrap_parent,精确值
测量体系内部有三种模式:EXACTILY,AT_MOST,UNSPECIFIED
其转换所对应的表格如下:
paretMeasureSpec | Lp.height | childMeasureSpec |
EXACTLY+SIZE | 具体值 | EXACTLY+具体值 |
同上 | WRAP_CONTENT | AT_MOST+size |
同上 | MATCH_PARENT | EXACTLY+size |
AT_MOST+SIZE | 具体值 | EXACTLY+size |
同上 | WRAP_CONTENT | AT_MOST+size |
同上 | MATCH_PARENT | AT_MOST+size |
UNSPECIFIED+SIZE | childDimension | EXACTLY+childDimension |
同上 | WRAP_CONTENT | UNSPECIFIED+0 |
同上 | MATCH_PARENT | UNSPECIFIED+0 |
测量规格即表中的MeasureSpec,其组成为:测量模式+大小。
测量模式有三种:
EXACTILIY:父视图期待子视图的最理想大小
AT_MOST:父视图允许子视图的最大值
UNSEPECFIED:好吧,我不知道这个做啥用的,基本没遇到过
上述表格意义解释:
paretMeasureSpec:这是测量体系传递下来的,由系统转换完成
Lp.height:对应XML文件中latyou_height = "XXXX",这里是由程序猿指定。
ChildMeasureSpec :经过表的转换后得出自己的测量模式和测量中值,如果当前测量控件是一个ViewGroup,这个值还会传递给子控件。
三、测量过程
整个的测量过程,根据个人的理解做了一个图,将就着看:
将视图分为两类:View和ViewGroup
1-> ViewGroup有两个动作:
动作一:measure(父控件给定的宽规格,父控件给定的高规格) 函数接受来自父控件指定的测量规格,然后结合自己的布局参数 (layout_width,layou_height)来测量自身的宽和高 ,测量模式。这个时候就用到了最上面的表格,这一步的时候,measure中会回调onMeasure();
这就是View中的onMeasure()函数中的参数是怎么来的了。
动作二:
根据padding等属性,综合进行考虑,给每个子控件分配测量规格
2-> View相对简单,只有一个操作:
只有动作一:根据父控件传递下来的宽高测量规格,得出自己的宽高。同样的也会回调onMeasure()方法。
在实际的使用过程,一遍的控件都有父控件,有父控件提供测量规格进行参考,那根视图呢?
根视图有单独的计算方法,其大小需要结合窗口的布局参数。一般情况,根视图的measure方法所获得测量规格为Exactly+窗口大小!
注意:
以上测量的过程都是系统完成,只有onMeasure()这一步应用程序可以参与。而且各种规格都只是建议,应用程序的代码可以通过 setMeasureDimension()来一票否决,直接指定当前控件的大小。
同时,不建议直接继承ViewGroup,通过上述的过程,应该知道,如果直接继承ViewGroup你就需要处理给子空间分配大小的过程,这个过程肯定麻烦。
大多数的开源控件中也很有直接继承ViewGroup,要么是继承的View,要么就是继承已有的VIewGroup子类,像LinearLayout之类的。
四、实际的运用过程。
一个窗口承载一个视图,视图最终会表现为一个View构成的树。每个节点(也就是View)必须拥有LayoutParam的属性。
来看看View的获得方式:
方式一:new 一个View
方式二:通过 LayoutInflater.from(this).inflate(R.layout.XXX,null);的方式来构造出一个View
如果你调用getLayoutParams(),会发现它们的布局参数都是空的。
方式一不奇怪,你本来就没有设置布局参数,自然没有
方式二奇怪的很,不是布局XML里面一般都有写么!其实是构造的过程最外层的ViewGroup的宽高属性是没有生效的,如果需要使得它生效,需要调用LayoutInfalter.from(this).infate的另外一种用法。
既然没有布局参数,它们最后都是怎么测量的?
其实,以上的view都是孤立的View,如果需要让它可见,一定要把通过addView的方法,加入主干的视图树中。查看addView这个方法会发现有个特点,如果你加入的View没有布局参数,它会生成一个默认的参数。如果有就使用子控件的布局参数。
这里有一点需要注意到 是,如果给一个LinearLayout通过addView的方式添加一个View,而这个View你通过setLayoutParams的方式指定了一个布局参数,这个指定的布局参数必须是LinearLayout.LayoutParams ,不然就会报类型错误。
如下图所示,每个VIew所对应的LayoutParams类型是是父母控件的类型的布局参数!
实际的运用就是,需要对控件本身定制大小,不受父母控件的干扰,就手动的设置精确布局参数,需要注意其类型,类型一旦确实就只能添加到某个指定的控件下了。