[轉(zhuǎn)] 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是什么意思浪册,看了很多資料后豁然開朗扫腺,總結(jié)如下

首先要知道

ViewGroup是繼承View的,后面的解釋跟View有關(guān)议经。ViewGourp可以包含很多個View斧账,View就是它的孩子,比如

LinearLayout布局是一個ViewGroup煞肾,在布局內(nèi)可以放TextEdit咧织、ImageView等等常用的控件,這些叫子View籍救,當然不

限于這個固定的控件习绢。

onMeasure → onLayout → DispatchDraw:onMeasure負責測量這個ViewGroup和子View的大小,onLayout負責設(shè)置子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和它的內(nèi)容決定寬度和高度。

說實在的奇颠,官方文檔說測量我剛開始很疑惑败去,onMeasure翻譯過來是測量,根本不知道它的意圖烈拒,其實它有兩方面作用:①獲得ViewGroup和子View的寬和高 ②設(shè)置子ViewGroup的寬和高圆裕,注意,只是寬和高荆几。其實吓妆,追蹤onMeasure方法會發(fā)現(xiàn),它繼承自View吨铸。

典型的onMeasure的一個實現(xiàn)

[java]view plaincopy

@Override

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec)?{

intwidth?=?MeasureSpec.getSize(widthMeasureSpec);//獲取ViewGroup寬度

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

setMeasuredDimension(width,?height);//設(shè)置ViewGroup的寬高

intchildCount?=?getChildCount();//獲得子View的個數(shù)行拢,下面遍歷這些子View設(shè)置寬高

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

View?child?=?getChildAt(i);

child.measure(viewWidth,?viewHeight);//設(shè)置子View寬高

}

}

很明顯,先獲取到了寬高再設(shè)置诞吱。順序是先設(shè)置ViewGroup的舟奠,再設(shè)置子View。

其中房维,設(shè)置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調(diào)用,上上的代碼剛好是在onMeasure中調(diào)用握巢,所以才符合要求。那設(shè)置的這個寬高保存在哪里呢松却?源代碼中也可以看出暴浦,

它保存在ViewGroup中:mMeasuredWidth溅话,mMeasuredHeight是View這個類中的變量。

接下來是設(shè)置子View的寬高歌焦,每個子View都會分別設(shè)置飞几,這個寬高當然是自己定義的。child.measure(viewWidth,

viewHeight);調(diào)用的是measure方法独撇,注意這個方法是屬于子View的方法屑墨,那設(shè)置的高度保存在哪里呢?對了纷铣,就是每個子View中卵史,而不是ViewGroup中,這點要分清楚搜立。

再來看看measure的實現(xiàn)

[java]view plaincopy

publicfinalvoidmeasure(intwidthMeasureSpec,intheightMeasureSpec)?{

??.........

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

onMeasure(widthMeasureSpec,?heightMeasureSpec);

??..........

}

其實它又調(diào)用了View類中的onMeasure方法以躯,在看View.class的onMeasure方法

[java]view plaincopy

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec)?{

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),?widthMeasureSpec),

getDefaultSize(getSuggestedMinimumHeight(),?heightMeasureSpec));

}

很奇怪吧,又繞回了原來的setMeasureDimension方法啄踊,說到底忧设,真正設(shè)置ViewGroup和子View寬高的都是setMeasureDimension方法,但是為什么上面child.measure(viewWidth,

viewHeight);不直接調(diào)用child.setMeasureDimension(viewWidth,viewHeight)呢颠通,多方便啊址晕。因為setMeasureDimension()只能由onMeasure()方法調(diào)用。

所以onMeasure沒什么神奇之處顿锰,就是測量(Measure)和設(shè)置(determine)寬高谨垃,現(xiàn)在終于理解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.

它才是設(shè)置子View的大小和位置撵儿。onMeasure只是獲得寬高并且存儲在它各自的View中乘客,這時ViewGroup根本就不知道子View的大小,onLayout告訴ViewGroup淀歇,子View在它里面中的大小和應(yīng)該放在哪里易核。注意兩個的區(qū)別,我當時也被搞得一頭霧水浪默。

參數(shù)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。阔加。饵史。

好了,現(xiàn)在的理解只能是這樣

ADD:關(guān)于MeasureSpec

MeasureSpec是View中的一個內(nèi)部類,A

MeasureSpec encapsulates the layout requirements passed from parent to child. 即封裝了布局傳遞的參數(shù)胳喷。它代表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);//設(shè)置ViewGroup的寬高

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

getChildAt(i).measure(widthMeasureSpec,?heightMeasureSpec);//遍歷孩子設(shè)置寬高

}

}

為什么onMeasure的參數(shù)widthMeasureSpec和heightMeasure要經(jīng)過getSize()方法才得到真實的寬高 ,既然參數(shù)是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方法,他是利用傳遞進來的參數(shù)來解析的讲竿。其實直接這么看會很暈泥兰,根本不知所云,所以回頭看看onMeasure方法题禀,調(diào)試onMeasure方法鞋诗,里面的widthMeasureSpec、heightMeasureSpec和解析出來的值width投剥、height的值如下:

發(fā)現(xiàn)解析前后

的值差很遠师脂,再結(jié)合源代碼 widthMeasureSpec & ~

MODE_MASK,運算后剛好匹配得到width江锨。運算方法:0x3=0011, 它向左移位30位吃警,得到1100 0000

.....(1后面一共有30個0.) ~取反后就是0011 1111……(0后面有30個1).

上面的widthMeasureSpec是1073742304,轉(zhuǎn)換成二進制是 0100

0000 0000 0000 0000 0001 1110 0000啄育,和前面那個 ~MODE_MASK

&之后(注意MODE_MASK要先取反再與widthMeasureSpec)酌心,最前面那個2個1就去掉了,widthMeasureSpec

只留下了后面一段有1挑豌,即得到0000 …(省略16個0)… 0001 1110 0000安券,得到的值轉(zhuǎn)換成

十進制剛好是480,完美氓英,轉(zhuǎn)換后得到了真實的width侯勉。手機的屏幕剛好是480*854,這是小米1的屏幕铝阐。

PS:但是為什么要費這么大的周折呢址貌?為什么要移位向左移位30呢?

仔細看getMode()方法徘键,它也是使用和getSize()同樣的參數(shù)來解析练对,其實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參數(shù)就得到了兩個值下隧,一個是具體的大小值绊谭,一個是模式的值,再看看官方文檔的解釋汪拥,終于明白為什么叫encapsulates

了,不得不說Google工程師牛篙耗。

我們通常在XML布局中使用的layout_width或layout_height可以指定兩種值迫筑,一種是具體的,比如100dp宗弯,一種是Math_Parent之類的脯燃,就是封裝到這里來了... 對應(yīng)兩個值哦~

回到前面的為什么要左移30位的問題。因為int類型是32位蒙保,原始值0x3左移30位后使用最高兩位來表示MODE值辕棚,我們傳遞的measureSpec是正數(shù)的時候,怎么也不會用到最高兩位表示getSize要解析的真實值邓厕。也就是即使真實值使用到了3221225471逝嚎,也可以正確解析出真實值,不用擔心真實值會溢出详恼。地球上也沒那么大分辨率的屏幕哦补君!不過這是我個人的猜測而已。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昧互,一起剝皮案震驚了整個濱河市挽铁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌敞掘,老刑警劉巖叽掘,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異玖雁,居然都是意外死亡更扁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門茄菊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疯潭,“玉大人,你說我怎么就攤上這事面殖∈ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵脊僚,是天一觀的道長相叁。 經(jīng)常有香客問我遵绰,道長,這世上最難降的妖魔是什么增淹? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任椿访,我火速辦了婚禮,結(jié)果婚禮上虑润,老公的妹妹穿的比我還像新娘成玫。我一直安慰自己,他們只是感情好拳喻,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布哭当。 她就那樣靜靜地躺著,像睡著了一般冗澈。 火紅的嫁衣襯著肌膚如雪钦勘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天亚亲,我揣著相機與錄音彻采,去河邊找鬼。 笑死捌归,一個胖子當著我的面吹牛肛响,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惜索,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼终惑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了门扇?” 一聲冷哼從身側(cè)響起雹有,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎臼寄,沒想到半個月后霸奕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡吉拳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年质帅,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片留攒。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡煤惩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出炼邀,到底是詐尸還是另有隱情魄揉,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布拭宁,位于F島的核電站洛退,受9級特大地震影響瓣俯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜兵怯,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一彩匕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧媒区,春花似錦驼仪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至噪服,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胜茧,已是汗流浹背粘优。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留呻顽,地道東北人雹顺。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像廊遍,于是被迫代替她去往敵國和親嬉愧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容