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



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

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension...
  • u013597998
  • u013597998
  • 2015-12-21 12:30:54
  • 1069

浅谈自定义View的宽高获取

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

Android 自定义View 中的OnMeasure的用法

作用:
  • a739697044
  • a739697044
  • 2014-06-12 12:43:13
  • 10174

自定义View-onMeasure()方法

在android自定义View的时候经常需要复写View的onMeasure()方法,但是有些时候却并不需要。了解其中的原因有助于我们深刻掌握自定义view。 首先看一下View中onMeasure...
  • cfp6523052
  • cfp6523052
  • 2015-09-18 14:06:39
  • 364

android 自定义View之View的测量(onMeasure()方法)

在自定义控件的过程中,系统在绘制View前,必须对View进行测量,已使后面的onLayout(设置View的放置位置)能够顺利进行。而对VIew的测量的过程则是在onMeasure()中进行的。可能...
  • gongzhiyao3739124
  • gongzhiyao3739124
  • 2016-09-14 19:12:13
  • 2003

android中对View的onMeasure()方法的理解

在android开发中,很多人对自定义View是望而生畏,我也一样,但这又是向高级进阶的必经之路,主要是对View里面的很多方法不知道怎么理解,其中一个就是onMeasure()方法,网上有很多这样解...
  • lovexieyuan520
  • lovexieyuan520
  • 2016-01-31 15:44:13
  • 13356

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

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

Android自定义控件之矩形View(长宽比例限制)

遇到这种布局,为了做好适配,最好是将图片控件ImageView长宽比例限制好。public class RectangleFrameLayout extends FrameLayout { p...
  • confusing_awakening
  • confusing_awakening
  • 2017-10-25 09:32:41
  • 274

Android中测量控件的宽和高

前言:在Android开发中,有时候我们需要测量控件的宽和高,而直接调用getHeight(),getWidth(),getMeasureHeight(),getMeasureWidth()方法, ...
  • listeners_Gao
  • listeners_Gao
  • 2016-03-28 02:19:18
  • 3607

android获取控件宽高和屏幕宽高

一、获取屏幕宽高 要获取屏幕宽高,我们可以先从android的界面构成了解 android的界面主要由三部分构成:1、状态栏 2、标题栏 3、内容区域 1、状态栏 2、标题栏 android中标...
  • littlewhitetree
  • littlewhitetree
  • 2016-06-15 13:46:10
  • 6908
收藏助手
不良信息举报
您举报文章:Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一)
举报原因:
原因补充:

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