在Android開發(fā)中,自定義View可以說是從業(yè)人員從初級到中級的一個標(biāo)志性過渡,不論你是老鳥還是新人,不論你是工作還是面試,自定義View都如同一個守關(guān)的BOSS一樣,無法回避,《心花路放》里張儷( 那個大長腿)說過,“遇到問題,正面面對?!?,好吧,躲不起我還打不過么我?擼起袖子,干!
Activity的組成結(jié)構(gòu)粗講
首先,我們來看一下一個Activity包含哪些組件結(jié)構(gòu):

可以看到,每一個Activity都包含了PhoneWindow,PhoneWindow又包含了一個DecorView,而DecorView中包含了TitleView和一個ContentView,TitleView就是我們開發(fā)過程中需要設(shè)置的ActionBar和StatusBar相關(guān),而界面的主體結(jié)構(gòu)都在這個ContentView中設(shè)置了,所以我們每次給Activity設(shè)置布局文件的必須調(diào)用
setContentVIew(int layoutResID)方法來設(shè)置。
而ContentView中就包含了一個或多個ViewGroup和View,關(guān)于ViewGroup和View,整個ContentView的視圖結(jié)構(gòu):最頂層是ViewGroup,ViewGroup下可能有多個ViewGroup和View,就好像布局文件來說,我們寫布局文件的時候,最外層一般都是一個ViewGroup(LinearLayout或者是RelativeLayout等),之后這個ViewGroup內(nèi)部可能還是一個ViewGroup(LinearLayout或者是RelativeLayout等)或者直接放置了一個或者多個View(如TextView或者是Button等)。
這里需要說明一下,這里所說的ViewGroup包含很多View是說的View的視圖結(jié)構(gòu),而從View的架構(gòu)來說,所有的ViewGroup其本質(zhì)還是View的子類,這里要面試的朋友一定要注意了,如果問到你View的架構(gòu),千萬不要和View的視圖結(jié)構(gòu)弄混,否則你來一句View繼承自ViewGroup那你這次面試基本上就會以失敗告終了。
這里就不細(xì)講整個布局是如何誕生的了,關(guān)鍵是大家要對Activity有一個全局的認(rèn)識,從而知道我們自定義View在Activity中的具體定位。
自定義View的工作流程
自定義View的工作流程主要指的是Mesusre,Layout和Draw,Measure用來測量View的寬高,Layout用來確定View的位置,Draw則用來繪制View。
雖然話是這么說,但是就好像一個類要運(yùn)行一定要有一個Main方法一樣,一個View的繪制也一定要有一個開始調(diào)用的地方才是,那么這個開始調(diào)用的方法就是ViewRootImpl類的performTraversales()方法,在這個方法中,這個方法會通過判斷來分析這個View是否要執(zhí)行相關(guān)的繪制流程。
單一View的測量過程簡述
View的Measure過程根據(jù)View的類型分為兩種情況,一種是單一View的測量過程,還有一種是ViewGroup的測量過程,這兩種測量過程是不同的。
首先先看單一View的測量過程:

整個的測量過程基本如上圖所示,用文字描述一遍就是:
ViewRootImpl調(diào)用performMeasure方法后該方法中的DecorView又調(diào)用了measure方法,而DecorView的本質(zhì)其實(shí)是集成自FrameLayout,那么本質(zhì)就是一個ViewGroup,但是在FrameLayout和ViewGroup的源碼中都沒發(fā)現(xiàn)measure方法, 這就用到剛才的知識點(diǎn)了,ViewGroup本身也是View的子類,我們就去View的源碼中查看一下,果然,measure方法赫然其中,在measure方法中,View又調(diào)用了其自身的方法onMeasure,而起自身的onMeasure方法中又調(diào)用setMeasureDimension方法,該方法有兩個參數(shù),這兩個參數(shù)分別代表要設(shè)置的寬和高,而這兩個參數(shù)都是是通過getDefaultSize方法來計算的,而getDefaultSize方法中有一個參數(shù)是根據(jù)getSuggestedMinimumWidth或getSuggestedMinimumHeight獲得的。
之所以沒貼源碼就直接先說測量過程,是希望大家可以先了解一下View的加載過程,不管是在工作中遇到了問題,或者是面試的時候被人提問,根據(jù)這個過程你可以自己分析問題的切入點(diǎn)或者是給面試官描述一下大體的流程,告訴他你已經(jīng)對View的測量過程有了一個大體的認(rèn)知了,最起碼不會讓人家覺得你完全沒了解這里的知識。
ViewGroup的測量過程簡述
先看ViewGroup的測量過程:

整個測量過程基本如上圖所示,其原理就是:
通過遍歷所有的子View進(jìn)行子View的測量,然后將所有子View的尺寸進(jìn)行合并,最終得到ViewGroup父視圖的測量值。
測量中的超級尺子——MeasureSpec
在看整個view的加載過程的源碼前,我們先來了解一下View中的一個靜態(tài)內(nèi)部類MeasureSpec,有人叫它測量規(guī)格,我更喜歡把它描述成為測量過程中必不可少的工具——尺子。
這個尺子有兩種用法,橫著用就叫做widthMeasureSpec,用來測量寬度,豎著用就叫做heightMeasureSpec,用來測量高度的,不管你的自定義View是什么千奇百怪的形狀,他都是要放在一個矩形中進(jìn)行包裹展示的,那么為什么會有這兩個測量方式也就不難理解了。
這個尺子有兩個重要的功能,第一個功能自然是測量值了(Size),第二個功能是測量的模式(Mode),這兩個參數(shù)通過二進(jìn)制將其打包成一個int(32位)值來減少對內(nèi)存的分配,其高2位(31,32位)存放的是測量模式,而低30位則存儲的是其測量值。
測量模式(specMode)
測量模式分為三種:
- UNSPECIFIED模式:本質(zhì)就是不限制模式,父視圖不對子View進(jìn)行任何約束,View想要多大要多大,想要多長要多長,這個在我們寫自定義View中的時候非常少見,一般都是系統(tǒng)內(nèi)部在設(shè)置ListView或者是ScrollView的時候才會用到。
- EXACTLY模式:該模式其實(shí)對應(yīng)的場景就是match_parent或者是一個具體的數(shù)據(jù)(50dp或80px),父視圖為子View指定一個確切的大小,無論子View的值設(shè)置多大,都不能超出父視圖的范圍。
- AT_MOST模式:這個模式對應(yīng)的場景就是wrap_content,其內(nèi)容就是父視圖給子View設(shè)置一個最大尺寸,子View只要不超過這個尺寸即可。
看完這三個模式,大家可能會有一個疑問,AT_MOST模式不是和EXACTLY模式一樣了嗎,都是給個最大值來限制View的范圍?其實(shí)不一樣,EXACTLY模式是一個固定的模式,也就是說它是沒得商量的,你只能按照這個模式既有的規(guī)律來執(zhí)行而AT_MOST模式是可以根據(jù)開發(fā)者自己的需求來定制的,我們寫自定義View的時候所寫的測量其實(shí)也就是在寫這個模式的測量邏輯,他是根據(jù)子控件來靈活的測量尺寸的。
可能你還是懵逼,那請你跟我一起打開IDE,做這么一個簡單的測試:
請先寫一個TestView,繼承View:

不用重寫任何父類的方法,只要隨便弄兩個構(gòu)造方法即可。
然后在布局文件中寫入如下代碼:

這代碼太簡單了,你想象中的運(yùn)行效果肯定是這個樣子的:

沒錯,實(shí)際效果和你想的一樣,但是如果我們將match_parent改成wrap_content會怎樣呢?
好了不用想了,我們來看實(shí)際效果:

效果沒有變化!是的,通過這個測試我們知道一個原理,如果不重寫onMeasure的AT_MOST模式的測量邏輯,那么match_parent和wrap_content的效果是一樣的。
請帶著這個疑問繼續(xù)看下去。
MeasureSpec確定值
子View的MeasureSpec值是根據(jù)子View的布局參數(shù)(LayoutParams)和父容器的MeasureSpec至計算而來的,其具體邏輯封裝在了getChildMeasureSpec()方法中,現(xiàn)在來分析一下這個方法:
//從上面我們知道spec 是parent的MeasureSpec,padding是
//已被使用的大小,childDimension為child的大小
public static int getChildMeasureSpec(
int spec, int padding, int childDimension) {
//1、獲取parent的specMode
int specMode = MeasureSpec.getMode(spec);
//2、獲取parent的specSize
int specSize = MeasureSpec.getSize(spec);
//3、size=剩余的可用大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//4、通過switch語句判斷parent的集中mode,分別處理
switch (specMode) {
// 5、parent為MeasureSpec.EXACTLY時
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//5.1、當(dāng)childDimension大于0時,表示child的大小是
//明確指出的,如layout_width= "100dp";
// 此時child的大小= childDimension,
resultSize = childDimension;
//child的測量模式= MeasureSpec.EXACTLY
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//5.2、此時為LayoutParams.MATCH_PARENT
//也就是 android:layout_width="match_parent"
//因為parent的大小是明確的,child要匹配parent的大小
//那么我們就直接讓child=parent的大小就好
resultSize = size;
//同樣,child的測量模式= MeasureSpec.EXACTLY
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//5.3、此時為LayoutParams.WRAP_CONTENT
//也就是 android:layout_width="wrap_content"
// 這個模式需要特別對待,child說我要的大小剛好夠放
//需要展示的內(nèi)容就好,而此時我們并不知道child的內(nèi)容
//需要多大的地方,暫時先把parent的size給他
resultSize = size;
//自然,child的mode就是MeasureSpec.AT_MOST的了
resultMode = MeasureSpec.AT_MOST;
}
break;
// 5、parent為AT_MOST,此時child最大不能超過parent
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
//同樣child大小明確時,
//大小直接時指定的childDimension
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// child要跟parent一樣大,resultSize=可用大小
resultSize = size;
//因為parent是AT_MOST,child的大小也還是未定的,
//所以也是MeasureSpec.AT_MOST
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//又是特殊情況,先給child可用的大小
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 這種模式是很少用的,我們也看下吧
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// 與前面同樣的處理
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//通過傳入resultSize和resultMode生成一個MeasureSpec.返回
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
代碼挺長的 總結(jié)一下:
- 當(dāng)子View采用具體數(shù)值(dp / px)時
無論父容器的測量模式是什么,子View的測量模式都是EXACTLY且大小等于設(shè)置的具體數(shù)值; - 當(dāng)子View采用match_parent時
- 子View的測量模式與父容器的測量模式一致
- 若測量模式為EXACTLY,則子View的大小為父容器的剩余空間;若測量模式為AT_MOST,則子View的大小不超過父容器的剩余空間
- 當(dāng)子View采用wrap_parent時
如果父容器測量模式為UNSPECIFIED,子View也為UNSPECIFIED,否則子View為AT_MOST且大小不超過父容器的剩余空間。
單一View的測量過程詳解

還是回顧一下剛才這張圖,我們一個步驟一個步驟的來看:
performMeasure()
這個圖其實(shí)不是那么嚴(yán)謹(jǐn),因為在加載好系統(tǒng)布局資源后,會觸發(fā)ViewRootImpl的performTraversals()方法,該方法內(nèi)容會開始執(zhí)行測量、布局和繪制的工作,我們來看這個方法的源碼關(guān)鍵部分:
private void performTraversals() {
...
if (!mStopped) {
//1、獲取頂層布局的childWidthMeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
//2、獲取頂層布局的childHeightMeasureSpec
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//3、測量開始測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
if (didLayout) {
//4、執(zhí)行布局方法
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
}
if (!cancelDraw && !newSurface) {
...
//5、開始繪制了哦
performDraw();
}
}
...
}
可以看到,整個方法內(nèi)部其實(shí)就是做了一些基礎(chǔ)的判斷后,再順序的調(diào)用測量、布局和繪制的相關(guān)方法,從而完成自定義View的整個工作流程。
現(xiàn)在看performTraversals方法的第一個注釋和第二個注釋處,使用的是getRootMeasureSpec方法來獲取子View的MeasureSpec,根據(jù)一開始我們了解的知識,整個Activity的頂層View其實(shí)就是一個DecorView,所以這里獲取的其實(shí)是DeorView的MeasureSpec,然后將其傳入performMeasure方法中去開始測量,現(xiàn)在看看PerformMeasure方法:
private void performMeasure(int childWidthMeasureSpec,
int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//1、mView其實(shí)就是我們的頂層DecorView,從DecorView開始測量
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
measure()
根據(jù)剛才描述的單一View的加載過程,我們在View的源碼中找到了measure方法:
public final void measure(int widthMeasureSpec,
int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
這里有一個細(xì)節(jié)要注意以下
首先,ViewGroup中并沒有重寫View的onMeasure方法,但是我們的四大布局FrameLayout LinearLayout RelativeLayout AbsoluteLayout都是通過繼承ViewGroup來實(shí)現(xiàn)的,而且里面也重寫onMeasure方法,所以onMeasure方法是分為兩個方向的,單一View的測量是View.onMeasure而ViewGroup的測量則是XXLayout.onMeasure,這兩種onMeasure方法的實(shí)現(xiàn)是不同的。
View.onMeasure()
其實(shí)這個方法的關(guān)鍵作用有三個:
第一,獲取一個建議最小值。
第二,調(diào)用getDefaultSize方法定義對View尺寸的測量邏輯。
第三,調(diào)用setMeasureDimension()儲存測量后的View寬/高。
源碼如下:
protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(),
widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(),
heightMeasureSpec));
}
這里我們逆向思維,從最內(nèi)層開始說起:
getSuggestedMinimumWidth() / getSuggestedMinimumHeight()
源碼:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
代碼很簡單,是一個三目運(yùn)算,意思就是當(dāng)前View是否有背景?沒有就返回android:minWidth設(shè)置的值:有就返回android:minWidth和mBackground.getMinimumWidth()中較大的那個值。
那么,mBackground.getMinimumWidth()方法有是什么呢?
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
//返回背景圖Drawable的原始寬度
return intrinsicWidth > 0 ? intrinsicWidth :0 ;
}
該處邏輯一樣是三目運(yùn)算,getIntrinsicWidth()獲取的是背景圖的原始寬度,背景圖是BitmapDrawable則有原始寬度,在沒有原始寬度的情況下則返回0。
getDefaultSize()
這個方法其實(shí)是一個靜態(tài)工具方法:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
//1、獲得MeasureSpec的mode
int specMode = MeasureSpec.getMode(measureSpec);
//2、獲得MeasureSpec的specSize
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
//這個我們先不看他
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
//3、可以看到,最終返回的size就是我們MeasureSpec中測量得到的size
result = specSize;
break;
}
return result;
}
大家應(yīng)該注意到了,不論測量模式是AT_MOST還是EXACTLY,最終返回的Size是一樣的,現(xiàn)在我們再回想之前的問題,為什么我們設(shè)置wrap_content時match_parent效果一樣呢?
原因其實(shí)就在上面的代碼中,因為我們并沒有重寫子View采用wrap_content時的測量方法,所以自定義View不論父視圖的布局模式是EXACTLY(非固定值模式)還是AT_MOST也不論子視圖的布局模式是match_parent還是wrap_content(只要不是具體的值),系統(tǒng)都會將子View的大小(寬或高)都臨時或永久設(shè)置成Size(剩余的可用大小),所以在這種情況下,這兩種模式所形成的效果就會一樣了。
所以在實(shí)際開發(fā)中,我們必須要處理wrap_content這個情況,否則wrap_content和match_parent的效果就是一樣的了。
setMeasureDimension
該方法是儲存測量后的View的寬和高的,在自定義View的時候,我們自己重寫的onMeasure方法最后一定要調(diào)用這個方法,否則會報錯。
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
//1、判斷是否使用視覺邊界布局
boolean optical = isLayoutModeOptical(this);
//2、判斷view和parentView使用的視覺邊界布局是否一致
if (optical != isLayoutModeOptical(mParent)) {
//不一致時要做一些邊界的處理
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
//3、重點(diǎn)來了,經(jīng)過過濾之后調(diào)用了setMeasuredDimensionRaw方法,看來應(yīng)該是這個方法設(shè)置我們的view的大小
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
繼續(xù)看setMeasureDimensionRaw方法:
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
//最終將測量好的大小存儲到mMeasuredWidth和mMeasuredHeight上,所以在測量之后
//我們可以通過調(diào)用getMeasuredWidth獲得測量的寬、getMeasuredHeight獲得高
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
以上就是一個單一View的測量過程了,其順序為:
performTraversals->performMeasure->measure->onMeasure-> setMeasuredDimension-> setMeasuredDimensionRaw,由setMeasuredDimensionRaw最終保存測量的數(shù)據(jù)。
ViewGroup的測量過程詳解

根據(jù)上圖,我們還是一個步驟一個步驟的進(jìn)行分析:
measureChildren()
其作用就是遍歷子View并調(diào)用measureChild()進(jìn)行下一步測量
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
//參數(shù)說明:父視圖的測量規(guī)格(MeasureSpec)
final int size = mChildrenCount;
final View[] children = mChildren;
//遍歷所有的子view
for (int i = 0; i < size; ++i) {
final View child = children[i];
//如果View的狀態(tài)不是GONE就調(diào)用measureChild()去進(jìn)行下一步的測量
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
measureChild()
其作用就是計算單個子View的MeasureSpec,調(diào)用子View的measure進(jìn)行每個子View最后的寬、高的測量
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// 獲取子視圖的布局參數(shù)
final LayoutParams lp = child.getLayoutParams();
// 調(diào)用getChildMeasureSpec(),根據(jù)父視圖的MeasureSpec & 布局參數(shù)LayoutParams,計算單個子View的MeasureSpec
// getChildMeasureSpec()請回看上面的解析
// 獲取 ChildView 的 widthMeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
// 獲取 ChildView 的 heightMeasureSpec
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 將計算好的子View的MeasureSpec值傳入measure(),進(jìn)行最后的測量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measure()
與單一View的measure一致
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// 調(diào)用onMeasure()計算視圖大小
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
...
}
XXXLayout.onMeasure()
仔細(xì)看過文章前面的朋友應(yīng)該記得我在4.2 measure() 中提到過,ViewGroup的onMeasure和View的onMeasure是不同的,究其原因其實(shí)是因為ViewGroup是一個抽象類,所以即便它繼承了View也不用必須實(shí)現(xiàn)View中的onMeasure方法,而它的子類不具備通用的布局特性,這導(dǎo)致他們的子View的測量方法各不相同,因此,ViewGroup無法對onMeasure()做統(tǒng)一的實(shí)現(xiàn)。
這里以FrameLayout為例,看看它的onMeasure是如何實(shí)現(xiàn)的:
//這里的widthMeasureSpec、heightMeasureSpec
//其實(shí)就是我們frameLayout可用的widthMeasureSpec 、
//heightMeasureSpec
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//1、獲得frameLayout下childView的個數(shù)
int count = getChildCount();
//2、看這里的代碼我們可以根據(jù)前面的Measure圖來進(jìn)行分析,因為只要parent
//不是EXACTLY模式,以frameLayout為例,假設(shè)frameLayout本身還不是EXACTL模式,
// 那么表示他的大小此時還是不確定的,從表得知,此時frameLayout的大小是根據(jù)
//childView的最大值來設(shè)置的,這樣就很好理解了,也就是childView測量好后還要再
//測量一次,因為此時frameLayout的值已經(jīng)可以算出來了,對于child為MATCH_PARENT
//的,child的大小也就確定了,理解了這里,后面的代碼就很 容易看懂了
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
//3、清理存儲模式為MATCH_PARENT的child的隊列
mMatchParentChildren.clear();
//4、下面三個值最終會用來設(shè)置frameLayout的大小
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
//5、開始便利frameLayout下的所有child
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//6、小發(fā)現(xiàn)哦,只要mMeasureAllChildren是true,就算child是GONE也會被測量哦,
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//7、開始測量childView
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
//8、下面代碼是獲取child中的width 和height的最大值,后面用來重新設(shè)置frameLayout,有需要的話
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
//9、如果frameLayout不是EXACTLY,
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
//10、存儲LayoutParams.MATCH_PARENT的child,因為現(xiàn)在還不知道frameLayout大小,
//也就無法設(shè)置child的大小,后面需重新測量
mMatchParentChildren.add(child);
}
}
}
}
....
//11、這里開始設(shè)置frameLayout的大小
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
//12、frameLayout大小確認(rèn)了,我們就需要對寬或高為LayoutParams.MATCH_PARENTchild重新測量,設(shè)置大小
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
//13、注意這里,為child是EXACTLY類型的childWidthMeasureSpec,
//也就是大小已經(jīng)測量出來了不需要再測量了
//通過MeasureSpec.makeMeasureSpec生成相應(yīng)的MeasureSpec
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
//14、如果不是,說明此時的child的MeasureSpec是EXACTLY的,直接獲取child的MeasureSpec,
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
// 這里是對高做處理,與寬類似
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
//最終,再次測量child
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
總結(jié)
通過本文的講解,不知道你對自定義View和ViewGroup的測量過程有沒有一個清楚的認(rèn)識呢?其實(shí)我們在學(xué)習(xí)的過程中,不必要完全了解源碼中的每個細(xì)節(jié),而是應(yīng)當(dāng)了解源碼的邏輯順序并將其大致過程記錄下來,這樣不論是在工作還是面試的過程中,我們根據(jù)大致的邏輯流程來分析問題,從而達(dá)到學(xué)以致用的目的。
最后我付一個Android自定義View的面試題答案,我認(rèn)為講的還是比較不錯的,希望大家看完后再結(jié)合之前文章中提到的內(nèi)容進(jìn)行記憶整理。
- ViewRootImpl 會調(diào)用 performTraversals(), 其內(nèi)部會調(diào)用performMeasure()、performLayout、performDraw()。
- performMeasure() 會調(diào)用最外層的 ViewGroup的measure()-->onMeasure(), ViewGroup 的 onMeasure() 是抽象方法,但其提供了measureChildren(),這之中會遍歷子View然后循環(huán)調(diào)用measureChild() 這之中會用 getChildMeasureSpec()+父View的MeasureSpec+子View的LayoutParam一起獲取本View的MeasureSpec,然后調(diào)用子View的measure()到View的onMeasure()-->setMeasureDimension(getDefaultSize(),getDefaultSize()),getDefaultSize()默認(rèn)返回measureSpec的測量數(shù)值,所以繼承View進(jìn)行自定義的wrap_content需要重寫。
3 .performLayout()會調(diào)用最外層的ViewGroup的layout(l,t,r,b),本View在其中使用setFrame()設(shè)置本View的四個頂點(diǎn)位置。在onLayout(抽象方法)中確定子View的位置,如LinearLayout會遍歷子View,循環(huán)調(diào)用setChildFrame()-->子View.layout()。 - performDraw() 會調(diào)用最外層 ViewGroup的draw():其中會先后調(diào)用background.draw()(繪制背景)、onDraw()(繪制自己)、dispatchDraw()(繪制子View)、onDrawScrollBars()(繪制裝飾)。
- MeasureSpec由2位SpecMode(UNSPECIFIED、EXACTLY(對應(yīng)精確值和match_parent)、AT_MOST(對應(yīng)warp_content))和30位SpecSize組成一個int,DecorView的MeasureSpec由窗口大小和其LayoutParams決定,其他View由父View的MeasureSpec和本View的LayoutParams決定。ViewGroup中有g(shù)etChildMeasureSpec()來獲取子View的MeasureSpec。
- 三種方式獲取measure()后的寬高:
1.Activity#onWindowFocusChange()中調(diào)用獲取
2.view.post(Runnable)將獲取的代碼投遞到消息隊列的尾部。
3.ViewTreeObservable.