Android中自定义ViewGroup最重要的就是onMeasure和onLayout方法,都需要重写这两个方法,ViewGroup绘制 的过程是这样的:onMeasure → onLayout → DispatchDraw

  1. 其实我觉得官方文档解释有大大的问题,刚开始一直很疑惑onMeasure和onLayout是什么意思,看了很多资料后豁然开朗,总结如下

首先要知道ViewGroup是继承View的,后面的解释跟View有关。ViewGourp可以包含很多个View,View就是它的孩子,比如LinearLayout布局是一个ViewGroup,在布局内可以放TextEdit、ImageView等等常用的控件,这些叫子View,当然不限于这个固定的控件。

onMeasure → onLayout → DispatchDraw:onMeasure负责测量这个ViewGroup和子View的大小,onLayout负责设置子View的布局,DispatchDraw就是真正画上去了。

onMeasure

官方解释:

protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)

Measure the view and its content to determine the measured width and the measured height. 即 测量View和它的内容决定宽度和高度。
说实在的,官方文档说测量我刚开始很疑惑,onMeasure翻译过来是测量,根本不知道它的意图,其实它有两方面作用:①获得ViewGroup和子View的宽和高 ②设置子ViewGroup的宽和高,注意,只是宽和高。其实,追踪onMeasure方法会发现,它继承自View。
典型的onMeasure的一个实现
  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. int width = MeasureSpec.getSize(widthMeasureSpec);   //获取ViewGroup宽度
  4. int height = MeasureSpec.getSize(heightMeasureSpec);  //获取ViewGroup高度
  5. setMeasuredDimension(width, height);    //设置ViewGroup的宽高
  6. int childCount = getChildCount();   //获得子View的个数,下面遍历这些子View设置宽高
  7. for (int i = 0; i < childCount; i++) {
  8. View child = getChildAt(i);
  9. child.measure(viewWidth, viewHeight);  //设置子View宽高
  10. }
  11. }
很明显,先获取到了宽高再设置。顺序是先设置ViewGroup的,再设置子View。
其中,设置ViewGroup宽高的方法是 setMeasureDimension(),查看这个方法的源代码,它在view.class下
  1. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
  2. boolean optical = isLayoutModeOptical(this);
  3. if (optical != isLayoutModeOptical(mParent)) {
  4. Insets insets = getOpticalInsets();
  5. int opticalWidth  = insets.left + insets.right;
  6. int opticalHeight = insets.top  + insets.bottom;
  7. measuredWidth  += optical ? opticalWidth  : -opticalWidth;
  8. measuredHeight += optical ? opticalHeight : -opticalHeight;
  9. }
  10. mMeasuredWidth = measuredWidth;  //这就是保存到类变量
  11. mMeasuredHeight = measuredHeight;
  12. mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
  13. }
setMeasureDimension方法必须由onMeasure调用,上上的代码刚好是在onMeasure中调用,所以才符合要求。那设置的这个宽高保存在哪里呢?源代码中也可以看出,它保存在ViewGroup中:mMeasuredWidth,mMeasuredHeight是View这个类中的变量。
接下来是设置子View的宽高,每个子View都会分别设置,这个宽高当然是自己定义的。child.measure(viewWidth, viewHeight);调用的是measure方法,注意这个方法是属于子View的方法,那设置的高度保存在哪里呢?对了,就是每个子View中,而不是ViewGroup中,这点要分清楚。
再来看看measure的实现
  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  2. <span style="white-space:pre">  </span>.........
  3. <span style="white-space:pre">  </span>// measure ourselves, this should set the measured dimension flag back
  4. onMeasure(widthMeasureSpec, heightMeasureSpec);
  5. <span style="white-space:pre">  </span>..........
  6. }
其实它又调用了View类中的onMeasure方法,在看View.class的onMeasure方法
  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2. setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
  3. getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  4. }
很奇怪吧,又绕回了原来的setMeasureDimension方法,说到底,真正设置ViewGroup和子View宽高的都是setMeasureDimension方法,但是为什么上面child.measure(viewWidth, viewHeight);不直接调用child.setMeasureDimension(viewWidth,viewHeight)呢,多方便啊。因为setMeasureDimension()只能由onMeasure()方法调用。
所以onMeasure没什么神奇之处,就是测量(Measure)和设置(determine)宽高,现在终于理解API文档所解释的。
 
onLayout
官方解释 
protected abstract void onLayout (boolean changed, int l, int t, int r, int b)
Called from layout when this view should assign a size and position to each of its children. 
它才是设置子View的大小和位置。onMeasure只是获得宽高并且存储在它各自的View中,这时ViewGroup根本就不知道子View的大小,onLayout告诉ViewGroup,子View在它里面中的大小和应该放在哪里。注意两个的区别,我当时也被搞得一头雾水。
参数int l, int t, int r, int b不用多说,就是ViewGroup在屏幕的位置。
  1. @Override
  2. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  3. int mTotalHeight = 0;
  4. // 当然,也是遍历子View,每个都要告诉ViewGroup
  5. int childCount = getChildCount();
  6. for (int i = 0; i < childCount; i++) {
  7. View childView = getChildAt(i);
  8. // 获取在onMeasure中计算的视图尺寸
  9. int measureHeight = childView.getMeasuredHeight();
  10. int measuredWidth = childView.getMeasuredWidth();
  11. childView.layout(left, mTotalHeight, measuredWidth, mTotalHeight + measureHeight);
  12. mTotalHeight += measureHeight;
  13. }
  14. }
接下来就是DispatchDraw。。。
好了,现在的理解只能是这样
 
ADD:关于MeasureSpec
MeasureSpec是View中的一个内部类,A MeasureSpec encapsulates the layout requirements passed from parent to child. 即封装了布局传递的参数。它代表Height和Width,先贴一段使用情况的代码:
  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  4. int width = MeasureSpec.getSize(widthMeasureSpec);  //获取真实width
  5. int height = MeasureSpec.getSize(heightMeasureSpec);   //获取真实height
  6. setMeasuredDimension(width, height);   //设置ViewGroup的宽高
  7. for (int i = 0; i < getChildCount(); i++) {
  8. getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);    //遍历孩子设置宽高
  9. }
  10. }
为什么onMeasure的参数widthMeasureSpec和heightMeasure要经过getSize()方法才得到真实的宽高 ,既然参数是int类型为什么不直接传递真实宽高,其实这暗藏玄机。我们当然是直接找到MeasureSpec的源码来看咯
  1. public static class MeasureSpec {
  2. private static final int MODE_SHIFT = 30;    //
  3. private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
  4. public static int getMode(int measureSpec) {
  5. return (measureSpec & MODE_MASK);
  6. }
  7. public static int getSize(int measureSpec) {
  8. return (measureSpec & ~MODE_MASK);
  9. }
  10. }
看getSize方法,他是利用传递进来的参数来解析的。其实直接这么看会很晕,根本不知所云,所以回头看看onMeasure方法,调试onMeasure方法,里面的widthMeasureSpec、heightMeasureSpec和解析出来的值width、height的值如下:

发现解析前后的值差很远,再结合源代码 widthMeasureSpec & ~ MODE_MASK,运算后刚好匹配得到width。运算方法:0x3=0011, 它向左移位30位,得到1100 0000 .....(1后面一共有30个0.) ~取反后就是0011 1111……(0后面有30个1). 上面的widthMeasureSpec是1073742304,转换成二进制是 0100 0000 0000 0000 0000 0001 1110 0000,和前面那个 ~MODE_MASK &之后(注意MODE_MASK要先取反再与widthMeasureSpec),最前面那个2个1就去掉了,widthMeasureSpec只留下了后面一段有1,即得到0000 …(省略16个0)… 0001 1110 0000,得到的值转换成 十进制刚好是480,完美,转换后得到了真实的width。手机的屏幕刚好是480*854,这是小米1的屏幕。
 
PS:但是为什么要费这么大的周折呢?为什么要移位向左移位30呢?
仔细看getMode()方法,它也是使用和getSize()同样的参数来解析,其实getSize只是用了measureSpec中的一部分来代表width or height,剩下的高位用来代表getMode的值。
且看widthMeasureSpec的值,它左边最高两位是01,然后和MODE_MASK & 了之后,得到0100……(1后省了30个0),即0x40000000,查看MeasureSpec中的几个常量:
AT_MOST = 0x80000000
EXACTLY = 0x40000000
UPSPECIFIED = 0x00000000
 
getMode解析之后得到EXACTILY。所以说一个measureSpec参数就得到了两个值,一个是具体的大小值,一个是模式的值,再看看官方文档的解释,终于明白为什么叫encapsulates 了,不得不说Google工程师牛。
我们通常在XML布局中使用的layout_width或layout_height可以指定两种值,一种是具体的,比如100dp,一种是Math_Parent之类的,就是封装到这里来了... 对应两个值哦~
 
回到前面的为什么要左移30位的问题。因为int类型是32位,原始值0x3左移30位后使用最高两位来表示MODE值,我们传递的measureSpec是正数的时候,怎么也不会用到最高两位表示getSize要解析的真实值。也就是即使真实值使用到了3221225471,也可以正确解析出真实值,不用担心真实值会溢出。地球上也没那么大分辨率的屏幕哦!不过这是我个人的猜测而已。

Android的onMeasure和onLayout And MeasureSpec揭秘的更多相关文章

  1. 【转】ViewGroup的onMeasure和onLayout分析

    ViewGroup的onMeasure和onLayout分析 一个Viewgroup基本的继承类格式如下: 1 import android.content.Context; 2 import and ...

  2. ANDROID自己定义视图——onLayout源代码 流程 思路具体解释

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 简单介绍: 在自己定义view的时候.事实上非常easy.仅仅须要知道3步骤: 1.測量- ...

  3. 继承ViewGroup学习onMeasure()和onLayout()方法

    在继承ViewGroup类时,需要重写两个方法,分别是onMeasure和onLayout. 1,在方法onMeasure中调用setMeasuredDimension方法void android.v ...

  4. 通过重写ViewGroup学习onMeasure()和onLayout()方法

    在继承ViewGroup类时,需要重写两个方法,分别是onMeasure和onLayout. 1,在方法onMeasure中调用setMeasuredDimension方法 void android. ...

  5. [转]Android View.onMeasure方法的理解

    转自:http://blog.sina.com.cn/s/blog_61fbf8d10100zzoy.html Android View.onMeasure方法的理解 View在屏幕上显示出来要先经过 ...

  6. 自定义控件详解(五):onMeasure()、onLayout()

    前言: 自定义控件的三大方法: 测量: onMeasure(): 测量自己的大小,为正式布局提供建议 布局: onLayout(): 使用layout()函数对所有子控件布局 绘制: onDraw() ...

  7. Android下如何理解onMeasure,onLayout的过程

    在Android中view如何完成绘制这个过程介绍了很多,但是很多理论化的东西,最近重新整理一下,通俗的讲解一下. View绘制过程就好比你向银行贷款, 在执行onMeasure的时候,好比银行告诉你 ...

  8. Android onMeasure 方法的测量规范MeasureSpec

    一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求.一个MeasureSpec由大小和模式组成.它有三种模式:UNSPECIFIED(未 ...

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

    上次讲的自定义控件刷新点屏幕的任意地方都会刷新,而且在xml里自定义控件下面放一个textview的话,这个TextView是显示不出来的,不只这个,以前的几个自定义控件都是 为什么呢?今天来讲下on ...

随机推荐

  1. OpenResty+lua+GraphicsMagick生成缩略图

    1.安装GraphicsMagick 下载地址:http://sourceforge.net/projects/graphicsmagick/files/graphicsmagick/1.3.19/G ...

  2. ACM spiral grid

    spiral grid 时间限制:2000 ms  |  内存限制:65535 KB 难度:4   描述 Xiaod has recently discovered the grid named &q ...

  3. ACM: HDU 5285 wyh2000 and pupil-二分图判定

     HDU 5285  wyh2000 and pupil Time Limit:1500MS     Memory Limit:65536KB     64bit IO Format:%I64d &a ...

  4. ACM: 继续畅通工程-并查集-最小生成树-解题报告

    继续畅通工程 Time Limit:1000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u Submit Status Descri ...

  5. [BZOJ2795][Poi2012]A Horrible Poem

    2795: [Poi2012]A Horrible Poem Time Limit: 50 Sec  Memory Limit: 128 MBSubmit: 261  Solved: 150[Subm ...

  6. CF 66D. Petya and His Friends

    题目链接 java的水题,特判啊...依旧无法1Y. import java.util.*; import java.math.*; public class Main { public static ...

  7. CF 16C. Monitor

    题目链接 水题依旧无法1Y. #include <cstdio> #include <iostream> #include <cmath> using namesp ...

  8. 【BZOJ1901】Zju2112 Dynamic Rankings

    Description 给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是 ...

  9. thinkphp 联表查询,排序

    $info =M('productbase'); $info= $info->alias('a')->field('a.id,cid,title,address,protype,time, ...

  10. [IBM DB2] db2 terminate 和 db2 connect reset 有什么区别?

    [IBM DB2] db2 terminate 和 db2 connect reset 有什么区别?  总结:如果是退出编辑器 quit :如果是断开数据库连接释放资源 connect reset : ...