原文地址: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逝嚎,也可以正確解析出真實值,不用擔心真實值會溢出详恼。地球上也沒那么大分辨率的屏幕哦补君!不過這是我個人的猜測而已。