转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45027641
自定义view/viewgroup要重写的几个方法:onMeasure(),onLayout(),onDraw()。(不熟悉的话可以查看专栏的前几篇文章:Android自定义控件系列二:自定义开关按钮(一))。
今天的任务就是详细研究一下protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法。
如果只是说要重写什么方法有什么用的话,还是不太清楚。先去源码中看看为什么要重写onMeasure()方法,这个方法是在哪里调用的:
一、源码中的measure/onMeasure方法:#
**[java]** [view plain](http://blog.csdn.net/cyp331203/article/details/45027641#) [copy](http://blog.csdn.net/cyp331203/article/details/45027641#)
<div>
<embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1">
</embed>
</div>
<span class="tracking-ad" data-mod="popu_167">[](https://code.csdn.net/snippets/642862)</span><span class="tracking-ad" data-mod="popu_170">[](https://code.csdn.net/snippets/642862/fork)</span></div> </div>
- <span class="keyword">protected</span> <span class="keyword">void</span> onMeasure(<span class="keyword">int</span> widthMeasureSpec, <span class="keyword">int</span> heightMeasureSpec) {
- setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
- getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
- }
</div>
实际上是在View这个类中的public final void measure(int widthMeasureSpec, int heightMeasureSpec)方法中被调用的:
<div class="dp-highlighter bg_java">
<div class="bar">
<div class="tools">
**[java]** [view plain](http://blog.csdn.net/cyp331203/article/details/45027641#)<span class="tracking-ad" data-mod="popu_168"><span class="tracking-ad" data-mod="popu_168"> [copy](http://blog.csdn.net/cyp331203/article/details/45027641#)</span></span>
<div>
<embed id="ZeroClipboardMovie_2" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_2">
</embed>
</div>
<span class="tracking-ad" data-mod="popu_167">[](https://code.csdn.net/snippets/642862)</span><span class="tracking-ad" data-mod="popu_170">[](https://code.csdn.net/snippets/642862/fork)</span></div> </div>
- <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> measure(<span class="keyword">int</span> widthMeasureSpec, <span class="keyword">int</span> heightMeasureSpec) {
- …
-
- onMeasure(widthMeasureSpec, heightMeasureSpec);
- …
-
- }
</div>
## <a name="t1"></a>1、measure()
可以看到,measure()这个方法是一个由final来修饰的方法,意味着不能够被子类重写.measure()方法的作用是:测量出一个View的实际大小,而实际性的测量工作,Android系统却并没有帮我们完成,因为这个工作交给了onMeasure()来作,所以我们需要在自定义View的时候按照自己的需求,重写onMeasure方法.而子控件又分为view和viewGroup两种情况,那么测量的流程是怎样的呢,看一下下面这个图你就明白了:

## <a name="t2"></a>
## <a name="t3"></a>2、onMeasure
onMeasure(int widthMeasureSpec, int heightMeasureSpec)中,两个参数的作用: widthMeasureSpec和heightMeasureSpec这两个int类型的参数,看名字应该知道是跟宽和高有关系,但它们其实不是宽和高,而是由宽、高和各自方向上对应的模式来合成的一个值:其中,在int类型的32位二进制位中,31-30这两位表示模式,0~29这三十位表示宽和高的实际值.其中模式一共有三种,被定义在Android中的View类的一个内部类中:View.MeasureSpec:
①UNSPECIFIED:表示默认值,父控件没有给子view任何限制。——二进制表示:00
②EXACTLY:表示父控件给子view一个具体的值,子view要设置成这些值的大小。——二进制表示:01
③AT_MOST:表示父控件个子view一个最大的特定值,而子view不能超过这个值的大小。——二进制表示:10
# <a name="t4"></a>二、MeasureSpec
MeasureSpe描述了父View对子View大小的期望.里面包含了测量模式和大小.我们可以通过以下方式从MeasureSpec中提取模式和大小,该方法内部是采用位移计算.
int specMode = MeasureSpec.getMode(measureSpec);//得到模式
int specSize = MeasureSpec.getSize(measureSpec);//得到大小
也可以通过MeasureSpec的静态方法把大小和模式合成,该方法内部只是简单的相加.
MeasureSpec.makeMeasureSpec(specSize,specMode);
每个View都包含一个ViewGroup.LayoutParams类或者其派生类,LayoutParams中包含了View和它的父View之间的关系,而View大小正是View和它的父View共同决定的。
我们平常使用类似于RelativeLayout和LinearLayout的时候,在其内部添加view的时候,不管是布局文件中加入还是在代码中使用addView方法添加,实际上都会调用这个onMeasure方法,而measure和onMeasure中的两个参数,是由各级父控件往子控件/子view进行一层层传递的。我们可以在xml中定义Layout的宽和高的具体的值或宽高的填充方式:matchparent/wrapcontent,也可以在代码中使用LayoutParams设置,而实际上这里设置的值就会对应到上面的measure和onMeasure方法中的两个参数的模式,对应关系如下:
具体的值(如width=200dp)和matchparent/fillparent,对应模式中的MeasureSpec.EXACTLY
包裹内容(width=wrapcontent)则对应模式中的MeasureSpec.AT_MOST
系统调用measure方法,从父控件到子控件的heightMeasureSpec的传递是有一套对应的判断规则的,列表如下:

一个view的宽高尺寸,只有在测量之后才能得到,也就是measure方法被调用之后。大家都应该使用过View.getWidth()和View.getHeight()方法,这两个方法可以返回view的宽和高,但是它们也不是在一开始就可以得到的,比如oncreate方法中,因为这时候measure方法还没有被执行,测量还没有完成,我们可以来作一个简单的实验:自定义一个MyView,继承View类,然后在OnCreate方法中,将其new出来,通过addview方法,添加到现在的布局中。然后调用MyView对象的getWidth()和getHeight()方法,会发现得到的都是0。
onMeasure通过父View传递过来的大小和模式,以及自身的背景图片的大小得出自身最终的大小,然后通过setMeasuredDimension()方法设置给mMeasuredWidth和mMeasuredHeight.
普通View的onMeasure逻辑大同小异,基本都是测量自身内容和背景,然后根据父View传递过来的MeasureSpec进行最终的大小判定,例如TextView会根据文字的长度,文字的大小,文字行高,文字的行宽,显示方式,背景图片,以及父View传递过来的模式和大小最终确定自身的大小.
# <a name="t5"></a>三、ViewGroup的onMeasure
ViewGroup是个抽象类,本身没有实现onMeasure,但是他的子类都有各自的实现,通常他们都是通过measureChildWithMargins函数或者其他类似于measureChild的函数来遍历测量子View,被GONE的子View将不参与测量,当所有的子View都测量完毕后,才根据父View传递过来的模式和大小来最终决定自身的大小.
在测量子View时,会先获取子View的LayoutParams,从中取出宽高,如果是大于0,将会以精确的模式加上其值组合成MeasureSpec传递子View,如果是小于0,将会把自身的大小或者剩余的大小传递给子View,其模式判定在前面表中有对应关系.
ViewGroup一般都在测量完所有子View后才会调用setMeasuredDimension()设置自身大小,如第一张图所示.
可能看到现在,还是没搞清楚Android系统通过measure和onmeasure一层层传递参数的具体方法。在研究这个问题之前,先来看一下最简单的helloworld的UI层级关系图:
为了方便起见,这里我们使用requestWindowFeature(Window.FEATURE_NO_TITLE);去除标题栏的影响,只看层级关系。
<div class="dp-highlighter bg_html">
<div class="bar">
<div class="tools">
**[html]** [view plain](http://blog.csdn.net/cyp331203/article/details/45027641#)<span class="tracking-ad" data-mod="popu_168"><span class="tracking-ad" data-mod="popu_168"> [copy](http://blog.csdn.net/cyp331203/article/details/45027641#)</span></span>
<div>
<embed id="ZeroClipboardMovie_3" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_3">
</embed>
</div>
<span class="tracking-ad" data-mod="popu_167">[](https://code.csdn.net/snippets/642862)</span><span class="tracking-ad" data-mod="popu_170">[](https://code.csdn.net/snippets/642862/fork)</span></div> </div>
- <span class="tag"><</span><span class="tag-name">RelativeLayout</span> <span class="attribute">xmlns:android</span>=<span class="attribute-value">“http://schemas.android.com/apk/res/android”</span>
- <span class="attribute">xmlns:tools</span>=<span class="attribute-value">“http://schemas.android.com/tools”</span>
- <span class="attribute">android:layout_width</span>=<span class="attribute-value">“match_parent”</span>
- <span class="attribute">android:layout_height</span>=<span class="attribute-value">“match_parent”</span>
- <span class="attribute">tools:context</span>=<span class="attribute-value">“<span class="katex math inline">{relativePackage}.</span>{activityClass}”</span> <span class="tag">></span>
-
- <span class="tag"><</span><span class="tag-name">TextView</span>
- <span class="attribute">android:layout_width</span>=<span class="attribute-value">“wrap_content”</span>
- <span class="attribute">android:layout_height</span>=<span class="attribute-value">“wrap_content”</span>
- <span class="attribute">android:text</span>=<span class="attribute-value">“@string/hello_world”</span> <span class="tag">/></span>
-
- <span class="tag"></</span><span class="tag-name">RelativeLayout</span><span class="tag">></span>
</div>
<div class="dp-highlighter bg_java">
<div class="bar">
<div class="tools">
**[java]** [view plain](http://blog.csdn.net/cyp331203/article/details/45027641#)<span class="tracking-ad" data-mod="popu_168"><span class="tracking-ad" data-mod="popu_168"> [copy](http://blog.csdn.net/cyp331203/article/details/45027641#)</span></span>
<div>
<embed id="ZeroClipboardMovie_4" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_4">
</embed>
</div>
<span class="tracking-ad" data-mod="popu_167">[](https://code.csdn.net/snippets/642862)</span><span class="tracking-ad" data-mod="popu_170">[](https://code.csdn.net/snippets/642862/fork)</span></div> </div>
- <span class="keyword">package</span> com.example.hello;
-
- <span class="keyword">import</span> android.app.Activity;
- <span class="keyword">import</span> android.os.Bundle;
- <span class="keyword">import</span> android.view.Window;
-
- <span class="keyword">public</span> <span class="keyword">class</span> MainActivity <span class="keyword">extends</span> Activity {
-
- <span class="annotation">@Override</span>
- <span class="keyword">protected</span> <span class="keyword">void</span> onCreate(Bundle savedInstanceState) {
- <span class="keyword">super</span>.onCreate(savedInstanceState);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.activity_main);
- }
- }
</div>
UI层级关系图:

可以发现最简单的helloworld的层级关系图是这样的,最开始是一个PhoneWindow的内部类DecorView,这个DecorView实际上是系统最开始加载的最底层的一个viewGroup,它是FrameLayout的子类,然后加载了一个LinearLayout,然后在这个LinearLayout上加载了一个id为content的FrameLayout和一个ViewStub,这个实际上是原本为ActionBar的位置,由于我们使用了requestWindowFeature(Window.FEATURE_NO_TITLE),于是变成了空的ViewStub;然后在id为content的FrameLayout才加载了我们的布局XML文件中写的RelativeLayout和TextView。
那么measure方法在系统中传递尺寸和模式,必定是从DecorView这一层开始的,我们假定手机屏幕是320*480,那么DecorView最开始是从硬件的配置文件中读取手机的尺寸,然后设置measure的参数大小为320*480,而模式是EXCACTLY,传递关系可以由下图示意:

<div>
</div>
好了,原理将到这里,下一篇将看到利用onMeasure来测量一个自定义一个ImageView,使其能够自动填满屏幕的宽度,且能通过measure测量高度,自适应的调整高度,永远不出现拉伸/压缩变形的情况,敬请关注,谢谢。
[Android自定义控件系列八:详解onMeasure()(二)–利用onMeasure测量来实现图片拉伸永不变形,解决屏幕适配问题](http://blog.csdn.net/cyp331203/article/details/45038329)
**转载请注明出处:[http://blog.csdn.net/cyp331203/article/details/45027641](http://blog.csdn.net/cyp331203/article/details/45027641)**
💬 评论