原文地址: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)心真實值會溢出。地球上也沒那么大分辨率的屏幕哦!不過這是我個人的猜測而已。