[轉(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,當(dāng)然不

限于這個固定的控件。

onMeasure → onLayout → DispatchDraw:onMeasure負(fù)責(zé)測量這個ViewGroup和子View的大小,onLayout負(fù)責(zé)設(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è)置,這個寬高當(dāng)然是自己定義的。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ū)別,我當(dāng)時也被搞得一頭霧水。

參數(shù)int l, int t, int r, int b不用多說,就是ViewGroup在屏幕的位置。

[java]view plaincopy

@Override

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

intmTotalHeight?=0;

//?當(dāng)然,也是遍歷子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類型為什么不直接傳遞真實寬高,其實這暗藏玄機。我們當(dāng)然是直接找到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)解析前后

的值差很遠(yuǎ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呢?

仔細(xì)看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,也可以正確解析出真實值,不用擔(dān)心真實值會溢出。地球上也沒那么大分辨率的屏幕哦!不過這是我個人的猜測而已。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容