[转] Android的onMeasure和onLayout And MeasureSpec揭秘

原文地址:http://blog.csdn.net/yuliyige/article/details/12656751


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

[java]view plaincopy

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

首先要知道

ViewGroup是继承View的,后面的解释跟View有关。ViewGourp可以包含很多个View,View就是它的孩子,比如

LinearLayout布局是一个ViewGroup,在布局内可以放TextEdit、ImageView等等常用的控件,这些叫子View,当然不

限于这个固定的控件。

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

onMeasure

官方解释:

protected voidonMeasure(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的一个实现

[java]view plaincopy

@Override

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec) {

intwidth = MeasureSpec.getSize(widthMeasureSpec);//获取ViewGroup宽度

intheight = MeasureSpec.getSize(heightMeasureSpec);//获取ViewGroup高度

setMeasuredDimension(width, height);//设置ViewGroup的宽高

intchildCount = getChildCount();//获得子View的个数,下面遍历这些子View设置宽高

for(inti =0; i < childCount; i++) {

View child = getChildAt(i);

child.measure(viewWidth, viewHeight);//设置子View宽高

}

}

很明显,先获取到了宽高再设置。顺序是先设置ViewGroup的,再设置子View。

其中,设置ViewGroup宽高的方法是 setMeasureDimension(),查看这个方法的源代码,它在view.class下

[java]view plaincopy

protectedfinalvoidsetMeasuredDimension(intmeasuredWidth,intmeasuredHeight) {

booleanoptical = isLayoutModeOptical(this);

if(optical != isLayoutModeOptical(mParent)) {

Insets insets = getOpticalInsets();

intopticalWidth  = insets.left + insets.right;

intopticalHeight = insets.top  + insets.bottom;

measuredWidth  += optical ? opticalWidth  : -opticalWidth;

measuredHeight += optical ? opticalHeight : -opticalHeight;

}

mMeasuredWidth = measuredWidth;//这就是保存到类变量

mMeasuredHeight = measuredHeight;

mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;

}

setMeasureDimension

方法必须由onMeasure调用,上上的代码刚好是在onMeasure中调用,所以才符合要求。那设置的这个宽高保存在哪里呢?源代码中也可以看出,

它保存在ViewGroup中:mMeasuredWidth,mMeasuredHeight是View这个类中的变量。

接下来是设置子View的宽高,每个子View都会分别设置,这个宽高当然是自己定义的。child.measure(viewWidth,

viewHeight);调用的是measure方法,注意这个方法是属于子View的方法,那设置的高度保存在哪里呢?对了,就是每个子View中,而不是ViewGroup中,这点要分清楚。

再来看看measure的实现

[java]view plaincopy

publicfinalvoidmeasure(intwidthMeasureSpec,intheightMeasureSpec) {

  .........

  // measure ourselves, this should set the measured dimension flag back

onMeasure(widthMeasureSpec, heightMeasureSpec);

  ..........

}

其实它又调用了View类中的onMeasure方法,在看View.class的onMeasure方法

[java]view plaincopy

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec) {

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),

getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

}

很奇怪吧,又绕回了原来的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在屏幕的位置。

[java]view plaincopy

@Override

protectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom) {

intmTotalHeight =0;

// 当然,也是遍历子View,每个都要告诉ViewGroup

intchildCount = getChildCount();

for(inti =0; i < childCount; i++) {

View childView = getChildAt(i);

// 获取在onMeasure中计算的视图尺寸

intmeasureHeight = childView.getMeasuredHeight();

intmeasuredWidth = childView.getMeasuredWidth();

childView.layout(left, mTotalHeight, measuredWidth, mTotalHeight + measureHeight);

mTotalHeight += measureHeight;

}

}

接下来就是DispatchDraw。。。

好了,现在的理解只能是这样

ADD:关于MeasureSpec

MeasureSpec是View中的一个内部类,A

MeasureSpec encapsulates the layout requirements passed from parent to child. 即封装了布局传递的参数。它代表Height和Width,先贴一段使用情况的代码:

[java]view plaincopy

@Override

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

intwidth = MeasureSpec.getSize(widthMeasureSpec);//获取真实width

intheight = MeasureSpec.getSize(heightMeasureSpec);//获取真实height

setMeasuredDimension(width, height);//设置ViewGroup的宽高

for(inti =0; i < getChildCount(); i++) {

getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);//遍历孩子设置宽高

}

}

为什么onMeasure的参数widthMeasureSpec和heightMeasure要经过getSize()方法才得到真实的宽高 ,既然参数是int类型为什么不直接传递真实宽高,其实这暗藏玄机。我们当然是直接找到MeasureSpec的源码来看咯

[java]view plaincopy

publicstaticclassMeasureSpec {

privatestaticfinalintMODE_SHIFT =30;//

privatestaticfinalintMODE_MASK  =0x3<< MODE_SHIFT;

publicstaticintgetMode(intmeasureSpec) {

return(measureSpec & MODE_MASK);

}

publicstaticintgetSize(intmeasureSpec) {

return(measureSpec & ~MODE_MASK);

}

}

看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,也可以正确解析出真实值,不用担心真实值会溢出。地球上也没那么大分辨率的屏幕哦!不过这是我个人的猜测而已。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容