在项目开发中,可能经常遇到嵌套ListView、ScrollView的问题,百度一搜,都是现成的代码,而且都是一样的,就是重写onMeasure方法,但是为什么要那么写,估计就没多少人知道了,这里进行深入的剖析一下下,重点看onMeasure方法,代码如下:
<code class="hljs java has-numbering"><span class="hljs-javadoc">/** * Created by hailonghan on 15/5/28. */</span> <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExpandListView</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ListView</span> {</span> <span class="hljs-keyword">public</span> <span class="hljs-title">ExpandListView</span>(Context context) { <span class="hljs-keyword">super</span>(context); } <span class="hljs-keyword">public</span> <span class="hljs-title">ExpandListView</span>(Context context, AttributeSet attrs) { <span class="hljs-keyword">super</span>(context, attrs); } <span class="hljs-keyword">public</span> <span class="hljs-title">ExpandListView</span>(Context context, AttributeSet attrs, <span class="hljs-keyword">int</span> defStyleAttr) { <span class="hljs-keyword">super</span>(context, attrs, defStyleAttr); } <span class="hljs-annotation">@TargetApi</span>(Build.VERSION_CODES.LOLLIPOP) <span class="hljs-keyword">public</span> <span class="hljs-title">ExpandListView</span>(Context context, AttributeSet attrs, <span class="hljs-keyword">int</span> defStyleAttr, <span class="hljs-keyword">int</span> defStyleRes) { <span class="hljs-keyword">super</span>(context, attrs, defStyleAttr, defStyleRes); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onMeasure</span>(<span class="hljs-keyword">int</span> widthMeasureSpec, <span class="hljs-keyword">int</span> heightMeasureSpec) { <span class="hljs-keyword">int</span> expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> <span class="hljs-number">2</span> , MeasureSpec.AT_MOST); <span class="hljs-keyword">super</span>.onMeasure(widthMeasureSpec, expandSpec); } }</code>
看onMeasure()方法,很多同学很疑惑,那个Integer.MAX_VALUE >> 2是咋个意思,MeasureSpec.AT_MOST又是咋个意思,总之就迷迷糊糊把代码拷到项目中了,一运行,好使,立马高兴的不得了,但是原理搞不懂,等别人问的时候,为什么这样写,就是讲不出来。
要搞明白原理,就要搞懂MeasureSpec这个类,这里我贴一下MeasureSpec类的源码,如下
<code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MeasureSpec</span> {</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> MODE_SHIFT = <span class="hljs-number">30</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> MODE_MASK = <span class="hljs-number">0x3</span> << MODE_SHIFT; <span class="hljs-javadoc">/** * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> UNSPECIFIED = <span class="hljs-number">0</span> << MODE_SHIFT; <span class="hljs-javadoc">/** * Measure specification mode: The parent has determined an exact size * for the child. The child is going to be given those bounds regardless * of how big it wants to be. */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> EXACTLY = <span class="hljs-number">1</span> << MODE_SHIFT; <span class="hljs-javadoc">/** * Measure specification mode: The child can be as large as it wants up * to the specified size. */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> AT_MOST = <span class="hljs-number">2</span> << MODE_SHIFT; <span class="hljs-javadoc">/** * Creates a measure specification based on the supplied size and mode. * * The mode must always be one of the following: * <ul> * <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li> * <li>{@link android.view.View.MeasureSpec#EXACTLY}</li> * <li>{@link android.view.View.MeasureSpec#AT_MOST}</li> * </ul> * * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's * implementation was such that the order of arguments did not matter * and overflow in either value could impact the resulting MeasureSpec. * {@link android.widget.RelativeLayout} was affected by this bug. * Apps targeting API levels greater than 17 will get the fixed, more strict * behavior.</p> * *<span class="hljs-javadoctag"> @param</span> size the size of the measure specification *<span class="hljs-javadoctag"> @param</span> mode the mode of the measure specification *<span class="hljs-javadoctag"> @return</span> the measure specification based on size and mode */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">makeMeasureSpec</span>(<span class="hljs-keyword">int</span> size, <span class="hljs-keyword">int</span> mode) { <span class="hljs-keyword">if</span> (sUseBrokenMakeMeasureSpec) { <span class="hljs-keyword">return</span> size + mode; } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> (size & ~MODE_MASK) | (mode & MODE_MASK); } } <span class="hljs-javadoc">/** * Extracts the mode from the supplied measure specification. * *<span class="hljs-javadoctag"> @param</span> measureSpec the measure specification to extract the mode from *<span class="hljs-javadoctag"> @return</span> {@link android.view.View.MeasureSpec#UNSPECIFIED}, * {@link android.view.View.MeasureSpec#AT_MOST} or * {@link android.view.View.MeasureSpec#EXACTLY} */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getMode</span>(<span class="hljs-keyword">int</span> measureSpec) { <span class="hljs-keyword">return</span> (measureSpec & MODE_MASK); } <span class="hljs-javadoc">/** * Extracts the size from the supplied measure specification. * *<span class="hljs-javadoctag"> @param</span> measureSpec the measure specification to extract the size from *<span class="hljs-javadoctag"> @return</span> the size in pixels defined in the supplied measure specification */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getSize</span>(<span class="hljs-keyword">int</span> measureSpec) { <span class="hljs-keyword">return</span> (measureSpec & ~MODE_MASK); } <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> adjust(<span class="hljs-keyword">int</span> measureSpec, <span class="hljs-keyword">int</span> delta) { <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> mode = getMode(measureSpec); <span class="hljs-keyword">if</span> (mode == UNSPECIFIED) { <span class="hljs-comment">// No need to adjust size for UNSPECIFIED mode.</span> <span class="hljs-keyword">return</span> makeMeasureSpec(<span class="hljs-number">0</span>, UNSPECIFIED); } <span class="hljs-keyword">int</span> size = getSize(measureSpec) + delta; <span class="hljs-keyword">if</span> (size < <span class="hljs-number">0</span>) { Log.e(VIEW_LOG_TAG, <span class="hljs-string">"MeasureSpec.adjust: new size would be negative! ("</span> + size + <span class="hljs-string">") spec: "</span> + toString(measureSpec) + <span class="hljs-string">" delta: "</span> + delta); size = <span class="hljs-number">0</span>; } <span class="hljs-keyword">return</span> makeMeasureSpec(size, mode); } <span class="hljs-javadoc">/** * Returns a String representation of the specified measure * specification. * *<span class="hljs-javadoctag"> @param</span> measureSpec the measure specification to convert to a String *<span class="hljs-javadoctag"> @return</span> a String with the following format: "MeasureSpec: MODE SIZE" */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">toString</span>(<span class="hljs-keyword">int</span> measureSpec) { <span class="hljs-keyword">int</span> mode = getMode(measureSpec); <span class="hljs-keyword">int</span> size = getSize(measureSpec); StringBuilder sb = <span class="hljs-keyword">new</span> StringBuilder(<span class="hljs-string">"MeasureSpec: "</span>); <span class="hljs-keyword">if</span> (mode == UNSPECIFIED) sb.append(<span class="hljs-string">"UNSPECIFIED "</span>); <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (mode == EXACTLY) sb.append(<span class="hljs-string">"EXACTLY "</span>); <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (mode == AT_MOST) sb.append(<span class="hljs-string">"AT_MOST "</span>); <span class="hljs-keyword">else</span> sb.append(mode).append(<span class="hljs-string">" "</span>); sb.append(size); <span class="hljs-keyword">return</span> sb.toString(); } }</code>
代码不是很长,其实里面最重要的是3种模式和3个方法
3种模式
- UNSPECIFIED模式,官方意思是:父布局没有给子布局强加任何约束,子布局想要多大就要多大,说白了就是不确定大小
- EXACTLY模式,官方意思是:父布局给子布局限定了准确的大小,子布局的大小就是精确的,父亲给多大就是多大
- AT_MOST模式,官方意思是:父布局给定了一个最大的值,子布局的大小不能超过这个值,当然可以比这个值小
3个方法
1.public static int makeMeasureSpec(int size, int mode) ,这个方法的作用是根据大小和模式来生成一个int值,这个int值封装了模式和大小信息
2.public static int getMode(int measureSpec),这个方法的作用是通过一个int值来获取里面的模式信息
3.public static int getSize(int measureSpec),这个方法的作用是通过一个int值来获取里面的大小信息
在Android里面,一个控件所占的模式和大小是通过一个整数int来表示的,这里很多同学就疑惑了,一个int值是怎么来表示模式的大小的,这里来看一张图片:
原来,Android里面把int的最高2两位来表示模式,最低30位来表示大小
private static final int MODE_SHIFT = 30;
- public static final int UNSPECIFIED = 0 << MODE_SHIFT;
- public static final int EXACTLY = 1 << MODE_SHIFT;
-
public static final int AT_MOST = 2 << MODE_SHIFT;
不确定模式是0左移30位,也就是int类型的最高两位是00
精确模式是1左移30位,也就是int类型的最高两位是01
最大模式是是2左移30位,也就是int类型的最高两位是10
现在在回头看我们之前的代码,如下:
<code class="hljs java has-numbering"><span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onMeasure</span>(<span class="hljs-keyword">int</span> widthMeasureSpec, <span class="hljs-keyword">int</span> heightMeasureSpec) { <span class="hljs-keyword">int</span> expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> <span class="hljs-number">2</span> , MeasureSpec.AT_MOST); <span class="hljs-keyword">super</span>.onMeasure(widthMeasureSpec, expandSpec); }</code>
讲到这里很多同学就明白了,我们调用了makeMeasureSpec方法,这个方法是用来生成一个带有模式和大小信息的int值的,第一个参数Integer.MAX_VALUE >> 2,这个参数是传的一个大小值,为什么是这个值呢,我们现在已经知道了,我们要生成的控件,它的大小最大值是int的最低30位的最大值,我们先取Integer.MAX_VALUE来获取int值的最大值,然后左移2位就得到这个临界值最大值了
当然,我们在手机上的控件的大小不可能那么大,极限值就那么大,实际肯定比那个小,所以这个模式就得选择MeasureSpec.AT_MOST了,最后将生成的这个大小传递给父控件就可以了,super.onMeasure(widthMeasureSpec, expandSpec),这个函数只改变的是控件的高度,宽度没有改变,实际开发当中不管listview有多少条数据,都能一次性展现出来