Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一)

原创 2015年04月13日 18:02:25


转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45027641


        自定义view/viewgroup要重写的几个方法:onMeasure(),onLayout(),onDraw()。(不熟悉的话可以查看专栏的前几篇文章:Android自定义控件系列二:自定义开关按钮(一))。


        今天的任务就是详细研究一下protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法。


        如果只是说要重写什么方法有什么用的话,还是不太清楚。先去源码中看看为什么要重写onMeasure()方法,这个方法是在哪里调用的:


一、源码中的measure/onMeasure方法:


    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

        实际上是在View这个类中的public final void measure(int widthMeasureSpec, int heightMeasureSpec)方法中被调用的:


public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...

onMeasure(widthMeasureSpec, heightMeasureSpec);
...

}

1、measure()

可以看到,measure()这个方法是一个由final来修饰的方法,意味着不能够被子类重写.measure()方法的作用是:测量出一个View的实际大小,而实际性的测量工作,Android系统却并没有帮我们完成,因为这个工作交给了onMeasure()来作,所以我们需要在自定义View的时候按照自己的需求,重写onMeasure方法.而子控件又分为view和viewGroup两种情况,那么测量的流程是怎样的呢,看一下下面这个图你就明白了:





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


二、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传递过来的模式和大小最终确定自身的大小.


三、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);去除标题栏的影响,只看层级关系。


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${relativePackage}.${activityClass}" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

</RelativeLayout>

package com.example.hello;

import android.app.Activity;
import android.os.Bundle;
import android.view.Window;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_main);
	}
}



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,传递关系可以由下图示意:



        




好了,原理将到这里,下一篇将看到利用onMeasure测量一个自定义一个ImageView,使其能够自动填满屏幕的宽度,且能通过measure测量高度,自适应的调整高度,永远不出拉伸/压缩变形的情况,敬请关注,谢谢。


Android自定义控件系列八:详解onMeasure()(二)--利用onMeasure测量来实现图片拉伸永不变形,解决屏幕适配问题


转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45027641



Android自定义View(三、深入解析控件测量onMeasure)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51468648 本文出自:【openXu的博客】 目录:onMeasure什么...
  • u010163442
  • u010163442
  • 2016年05月24日 14:59
  • 19199

自定义View之 onMeasure() view的高度自适应wrap_content view的测量

在很多自定义view之后,控件的高度需要自适应,即使使用wrap_content没有作用还是match_parent的效果,这时就需要重写onMeasure()方法来实现,view类的onMeasur...
  • mxiaoyem
  • mxiaoyem
  • 2016年04月06日 18:44
  • 4140

android自定义控件(七) onMeasure() 测量尺寸

上次讲的自定义控件刷新点屏幕的任意地方都会刷新,而且在xml里自定义控件下面放一个textview的话,这个TextView是显示不出来的,不只这个,以前的几个自定义控件都是 为什么呢?今天来讲下o...
  • ethan_xue
  • ethan_xue
  • 2012年03月27日 23:42
  • 27807

自定义View关于measure流程的基本思路整理

在《MeasureSpec的简单说明》这篇文章中对android中View的MeasureSpec测量方法做了详细的说明,今天这篇博文就Measure的测量做一个简单的总结。本文着重点在于怎么将and...
  • chunqiuwei
  • chunqiuwei
  • 2016年02月23日 13:30
  • 3033

自定义控件 继承View 使用OnMeasure定义控件宽高

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension...
  • u013597998
  • u013597998
  • 2015年12月21日 12:30
  • 839

Android自定义控件系列一:如何测量控件尺寸

测量控件尺寸(宽度、高度)是开发自定义控件的第一步,只有确定尺寸后才能开始画(利用canvas在画布上画,我们所使用的控件实际上都是这样画上去的)。当然,这个尺寸是需要根据控件的各个部分计算出来的,比...
  • shimiso
  • shimiso
  • 2015年10月27日 15:25
  • 7326

自定义控件大小的确定

前端时间又看了些自定义控件的小demo,但是要知道demo始终是demo,只是用来做某个功能的演示或讲解的,所以我发现多数将自定义控件的demo就是没有关注在实际使用中这个控件合适的大小问题,很多de...
  • zhouhang421
  • zhouhang421
  • 2016年11月04日 17:09
  • 477

Android自定义控件ImageViwe(一)——依据控件的大小来设置缩放图片显示

We all have moments of desperation ,But if we can face them head on,that's when we find out just how...
  • zl18603543572
  • zl18603543572
  • 2016年03月06日 02:45
  • 2996

浅谈自定义View的宽高获取

自定义View的时候经常少不了获取View的宽高信息,当然不一定是自定义View的时候才会需要获取宽高信息,其他情况下我们也会有这样的需求,获取方式和获取的时机也十分讲究.下面分别从这几个api讲起:...
  • mChenys
  • mChenys
  • 2016年11月25日 17:32
  • 11720

Android自定义控件无法通过代码修改大小、高宽,setMinimumHeight无效的问题

首先在理解我为什么这么写之前,请阅读一篇前人的文章:http://blog.csdn.net/a396901990/article/details/36475213 这篇文章详细的分析了一下onMea...
  • zhu071011
  • zhu071011
  • 2016年04月08日 16:36
  • 4296
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一)
举报原因:
原因补充:

(最多只允许输入30个字)