接著上一篇View繪制流程及源碼解析(一)——performTraversals()源碼分析,這一篇我們來具體看看三大流程的實(shí)現(xiàn)過程。
一. 從MeasureSpec說起
由于劇情的需要,我們不得不先說一下這個(gè)MeasureSpec势木。為什么要先說這個(gè)東西呢?再上一篇文章中,我們是否還記得三大流程正式開始的地方的代碼:
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
可見离唬,getRootMeasureSpec()是獲取根布局的MeasureSpec,而最終的performMeasure()正式執(zhí)行measure()的流程就是所傳遞就去的參數(shù)就是這個(gè)getRootMeasureSpec()所計(jì)算得到的值,因此我們先要搞清楚這個(gè)MeasureSpec是什么玩意划鸽。
??那么什么是MeasureSpec呢输莺?首先戚哎,他代表的是一個(gè)32位的int值。其次模闲,它是View類中的一個(gè)內(nèi)部類建瘫,所以我們?cè)俳酉聛淼奈恼路治鲋校赡軙?huì)用到它所代表的int值和它本身這兩種情況尸折,希望讀者再閱讀的時(shí)候可以加以區(qū)分啰脚。我們來看看它的源碼中的注釋(framewoks/base/core/java/android/view/View):
/**
* A MeasureSpec encapsulates the layout requirements passed from parent to child.
* Each MeasureSpec represents a requirement for either the width or the height.
* A MeasureSpec is comprised of a size and a mode. There are three possible
* modes:
* <dl>
* <dt>UNSPECIFIED</dt>
* <dd>
* The parent has not imposed any constraint on the child. It can be whatever size
* it wants.
* </dd>
*
* <dt>EXACTLY</dt>
* <dd>
* The parent has determined an exact size for the child. The child is going to be
* given those bounds regardless of how big it wants to be.
* </dd>
*
* <dt>AT_MOST</dt>
* <dd>
* The child can be as large as it wants up to the specified size.
* </dd>
* </dl>
*
* MeasureSpecs are implemented as ints to reduce object allocation. This class
* is provided to pack and unpack the <size, mode> tuple into the int.
*/
public static class MeasureSpec {
上面的注釋交代了四點(diǎn):
(1)MeasureSpec封裝了從父布局傳遞給子布局的的布局要求。
(2)每一個(gè)MeasureSpec代表了一個(gè)對(duì)寬度或著高度的要求实夹。
(3)一個(gè)MeasureSpec由一個(gè)size和一個(gè)mode組成,有三種可能:
??①UNSPECIFIED:父布局對(duì)于子布局/Veiw沒有任何約束橄浓,子布局可以是任何它想要的尺寸。根據(jù)主席的說法亮航,這中情況一般用于系統(tǒng)內(nèi)部反復(fù)測(cè)量控件大小荸实,我們?cè)儆玫倪^程中很少見到。
??②EXACTLY:
??父布局精確設(shè)定了子布局/View的大小缴淋,無論子布局想要多大的都將得到父布局給予的精確邊界准给。這種情況實(shí)際上就對(duì)應(yīng)的是我們?cè)趚ml文件或者LayoutParams中設(shè)置的xxdp/px或者MATH_PARENT這兩種情況,也就是精確的給予了布局邊界重抖。
??③AT_MOST:在確定的尺寸內(nèi)露氮,子布局/View可以盡可能的大。這個(gè)確定的尺寸钟沛,當(dāng)然指就值得就是父布局的大小畔规,畢竟子布局/View再大,也不能超出父布局的大小恨统。這種情況對(duì)應(yīng)的就是我們?cè)賦ml文件或者LayoutParams中設(shè)置的WRAP_CONTENT,子View可以隨著內(nèi)容的增加而申請(qǐng)更大的尺寸叁扫,但是不能超過父布局的大小。
(4)MeasureSpec將SpecMode和SpecSize打包成一個(gè)int值以便減少對(duì)象分配(的開支)畜埋。這個(gè)類提供了打包和解包size,mode的各中方法莫绣。
前面我們提到過,MeasureSpec是一個(gè)32位的int值悠鞍,通過上面一段我們又知道他是一個(gè)大小(Size)和模式(Mode)的組合值兔综,它的存在是為了減少對(duì)象分配的開支,那么我們可以做出進(jìn)一步解釋:
??①32位的int值中狞玛,高2位是SpecMode,代表測(cè)量模式软驰;低30位是SpecMode,代表再某種模式下得出的測(cè)量大小。
??②我們?cè)谏厦?1)(2)中我們將"要求"兩個(gè)字著重加黑心肪,就是為了強(qiáng)調(diào):這個(gè)MeasureSpec的值是"一個(gè)父?jìng)鬟f給子測(cè)量要求"锭亏,"怎么測(cè)量的要求",不是父對(duì)子強(qiáng)加的布局參數(shù)什么的,至于這個(gè)要求是什么硬鞍,怎么要求的慧瘤,后面我們會(huì)做進(jìn)一步說明戴已。
??③至于為什么要強(qiáng)調(diào)"傳遞"兩個(gè)字,主要是因?yàn)楣酰厦娴?測(cè)量要求"不是父強(qiáng)加給子的糖儡,而是兩個(gè)人一起商量著來的。具體的說怔匣,對(duì)于除了DecorView外的普通View握联,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定的,當(dāng)然從源碼來看還與View的margin和padding有關(guān)每瞒;而對(duì)于DecorView來說金闽,其MeasureSpec由它自身的LayoutParams決定(畢竟它已經(jīng)是根布局了,上面再?zèng)]人了)剿骨。
說了這么一大通代芜,下面我們從源碼的角度帶大家分析一下根布局和自布局確定MeasureSpec的過程。
(一).根View/DecorView的MeasureSpec
前面開篇代碼中的getRootMeasureSpec()看名字我們也知道他是獲得根布局的MeasureSpec的方法
(framewoks/base/core/java/android/view/ViewRootImpl):
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
通過上述代碼浓利,我們可以看到RootView(DecorView)的MeasureSpec創(chuàng)建的全過程挤庇,分為三種情況:
- LayoutParams.MATH_PARENT:精確模式,強(qiáng)制讓RootView的大小等于Window的大小。
- LayoutParams.WRAP_CONTENT:最大模式贷掖,限制了RootView的最大窗口(父布局/View的大小罚随。
- DEFAULT:指定大小,也就是在xml或者LayoutParams中指定View的寬度和高度的px/dp數(shù)羽资。
在這三種情況下,分別調(diào)用makeMeasureSpec()方法遵班,將模式和大小值打包成一個(gè)int值返回屠升。
(二).普通View的MeasureSpec
獲取普通View(除DecorView以外的View,大多數(shù)情況下指的是子布局/View)的MeasureSpec值的代碼在ViewGroup類中狭郑,相對(duì)復(fù)雜一點(diǎn)(framewoks/base/core/java/android/view/ViewGroup):
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//通過父View的MeasureSpec和子View的自己LayoutParams的計(jì)算腹暖,算出子View的MeasureSpec,然后父容器傳遞給
//子容器的然后讓子View用這個(gè)MeasureSpec(一個(gè)測(cè)量要求翰萨,比如不能超過多大)去測(cè)量自己脏答,如果子View是
//ViewGroup 那還會(huì)遞歸往下測(cè)量。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到亩鬼,和開篇代碼中root測(cè)量的代碼一樣殖告,這里的Child在測(cè)量之前,先得getChildMeasureSpec()雳锋。還記得我們上面說的嗎黄绩?對(duì)于除了DecorView外的普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定的玷过,當(dāng)然從源碼來看還與View的margin和padding有關(guān)爽丹,這里我們可以看源碼感受一下:
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
//這里的mWidth就是DecorView的寬度筑煮,lp是它的LayoutParams參數(shù)。
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
可以看到粤蝎,getRootMeasureSpec中真仲,只需要傳遞進(jìn)去DecorView自身的寬度和它的LayoutParams寬度設(shè)置這兩個(gè)參數(shù);而getChildMeasureSpec中則需要考慮父布局的MeasureSpec(parentWidthMeasureSpec),父View的Padding初澎,自身設(shè)置的Margin秸应,已經(jīng)用掉的空間大小(widthUsed),LayoutParams的寬度設(shè)置(lp.width)谤狡。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //父View的mode
int specSize = MeasureSpec.getSize(spec); //父View的size
//(父View的大小-自己的Padding+子View的Margin)灸眼,得到值才是子View的大小,所以這里的size并不是子View的大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
//父View強(qiáng)加了一個(gè)精確的值給我們墓懂,父布局的模式是EXACTLY焰宣。這種情況對(duì)應(yīng)于MATH_PARENT和確定的px/dp值這兩種情況。
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//子View的寬度和高度值為一個(gè)確定的dp/px值捕仔。
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
//子布局的LayoutParams為MATCH_PARENT匕积,那么最終得出的模式為EXACTLY。
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be bigger than us.
//子布局的LayoutParams設(shè)為WRAP_CONTENT榜跌,那么最終的布局模式為AT_MOST闪唆。
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
//父布局強(qiáng)加給我們一個(gè)最大值,即父布局的模式為AT_MOST.
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
//這個(gè)時(shí)候如果子布局/View想要一個(gè)精確的尺寸(xxdp/px)钓葫,那么最終的結(jié)果為EXACTLY悄蕾。
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
//如果子View的LayoutParams為MATH_PARENT,那么最終得到的結(jié)果為AT_MOST础浮。
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
//如果子View的LayoutParams為WRAP_CONTENT帆调,那么最終的結(jié)果模式為AT_MOST。
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
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;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
我們將上面的這段代碼中子View的模式和父View模式的對(duì)應(yīng)關(guān)系用一個(gè)表格表示一下:
這里的resultMode為最終得到的子VIew的SpecMode豆同,resultSize為最終得到的子View的大小番刊。而childeSize就是上面的childDimension,即精確對(duì)定義子View大小的時(shí)候我們?cè)贚ayotParams中所設(shè)定的子View大小影锈,parentSize則為父容器中目前剩余的可用大小芹务。
二.measure()過程
(一).View的measure()過程
好了,清楚了MeasureSpec這個(gè)值以及我們?nèi)绾蔚玫礁竀iew及View的MeasureSpec之后呢鸭廷,我們接下來就開始看三大流程的第一步:Measure(測(cè)量)過程(framewoks/base/core/java/android/view/View):
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if (cacheIndex < 0 || sIgnoreMeasureCache) {
......
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
可以看到枣抱,這個(gè)Measure()方法是final的,也就是說我們不能重寫辆床,但是由于具體的測(cè)量過程是在onMeasure()中完成的沃但,所以有需要的話我們可以重寫這個(gè)方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
......
顧名思義,這個(gè)setMeasuredDimension()方法是設(shè)置View寬/高的測(cè)量值佛吓,而它傳入的兩個(gè)參數(shù)getDefaultSize方法則是獲取到的寬/高值宵晚。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
首先我們來看看它的第一個(gè)參數(shù)getSuggestedMinimumWidth()垂攘,只有一句代碼,比較簡(jiǎn)單淤刃,他返回的是View的建議寬度/高度晒他。這個(gè)建議值是由View的背景尺寸和它的mMinWidth屬性決定的,mBackground就是背景逸贾,mMinWidth表示View的xml文件中android:minWidth屬性所指定的值陨仅,如果這個(gè)屬性沒有設(shè)置禽篱,那么就默認(rèn)為0究流。
??從源碼來看,這個(gè)方法中通過一個(gè)三目運(yùn)算符夺英,顯示判斷mBackground是否為null咪鲜,也就是有沒有狐赡。如果為ture也就是沒有背景,那么就返回mMinWidth的值疟丙,這個(gè)值可以為0颖侄;如果View指定了背景,那么返回的是mMinWidth和mBackground最小寬度兩個(gè)值中較大的那個(gè)享郊。這里指貼了getSuggestedMinimumWidth的源碼览祖,getSuggestedMinimumHeight()的源碼是一樣的。
??現(xiàn)在我們清楚了getDefaultSize()方法中第一個(gè)參數(shù)的意義炊琉,第二個(gè)參數(shù)measureSpec就是上面我們一直講的寬度/高度的MeasureSpec值≌沟伲現(xiàn)在我們來看看這個(gè)方法:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
可以看出這個(gè)方法的邏輯實(shí)際上比較簡(jiǎn)單,如果是UNSPECIFIED這種情況苔咪,返回值是上面getSuggestedMinimumWidth()得到的值锰悼。對(duì)于我們來說,只需要關(guān)注AT_MOST和EXACTLY這兩種情況就行了悼泌。在這兩種情況中,返回值也就是View的測(cè)量值都等于 MeasureSpec.getSize(measureSpec):
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
這個(gè)方法中做了什么呢?我們已經(jīng)知道夹界,這個(gè)measureSpec是一個(gè)封裝了View的SpecMode和SpecSize大小的int值馆里,其中高兩位是模式,低三十位是大小可柿。而這個(gè)MeasureSpec.getMode(measureSpec);
和MeasureSpec.getSize(measureSpec);
實(shí)際上就是分別將傳遞進(jìn)去的measureSpec中封裝的SpecMode和SpecSize剝離出來鸠踪。
??對(duì)于DecorView/View,measureSpec就是我們?cè)陂_篇最上面的代碼中計(jì)算出來的getRootMeasureSpec(mWidth, lp.width)复斥。所以這里的specSize(對(duì)于DecorView)也就是我們最上邊代碼中的mWidth营密,也就是DecorView的Window的寬度。
??而對(duì)于其他的一些View的派生類目锭,如TextView评汰、Button纷捞、ImageView等,它們的onMeasure方法系統(tǒng)了都做了重寫被去,不會(huì)這么簡(jiǎn)單直接拿 MeasureSpec 的size來當(dāng)大小主儡,而去會(huì)先去測(cè)量字符或者圖片的高度等,然后拿到View本身content這個(gè)高度(字符高度等)惨缆,如果MeasureSpec是AT_MOST糜值,而且View本身content的高度不超出MeasureSpec的size,那么可以直接用View本身content的高度(字符高度等)坯墨,而不是像View.java 直接用MeasureSpec的size做為View的大小寂汇。
??還有一點(diǎn)需要說明的是,上面我們強(qiáng)調(diào)View測(cè)量值的原因是捣染,View最終值是在Layout階段確定的骄瓣,雖然幾乎是所有情況下View測(cè)量大小和最終大小是一樣的。
(二).ViewGroup的measure()過程
對(duì)于ViewGroup來說液斜,出了完成自己的測(cè)量過程外累贤,還會(huì)去遍歷調(diào)用所有子View/ViewGroup的measure()方法,各個(gè)子元素再去遞歸執(zhí)行這個(gè)方法少漆。和View不同的是臼膏,ViewGroup是一個(gè)抽象類,他并沒有重寫真正實(shí)現(xiàn)測(cè)量自身的onMeasure()方法,而是將該方法下發(fā)到了各個(gè)子類去實(shí)現(xiàn)示损。為什么ViewGroup不像View一樣對(duì)其onMeasure()方法做同意的實(shí)現(xiàn)呢渗磅?主要是因?yàn)椴煌腣iewGroup子類有不同的布局特性,比如LinearLayout和RelativeLayout這兩者的布局特性顯然不同检访,因此ViewGroup無法做統(tǒng)一的實(shí)現(xiàn)始鱼。
??雖然ViewGroup測(cè)量自身的過程下放到了各個(gè)子類中去實(shí)現(xiàn),但是在該類中我們?nèi)稳豢梢钥吹皆擃愡f歸調(diào)用子類測(cè)量方法的邏輯:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
遍歷子Veiw脆贵,只要不是GONE的View都會(huì)調(diào)用医清。
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
measureChild()方法中做的事情就是卖氨,取出子View的LayoutParams值,然后通過getChildMeasureSpec()方法(這個(gè)方法上面講過)得出子元素的MeasureSpec柏腻,然后直接調(diào)用子View的measure(),之后所做的事情就和"(一).View的measure()過程"中的過程一樣了五嫂。
??上面就是子View的測(cè)量邏輯,下面我們需要著重看一下ViewGroup()自身的測(cè)量過程沃缘,這里我們選擇LinearLayout來做分析(待會(huì)實(shí)例分析會(huì)用到)(framewroks/base/core/java/android/widget/linearlayout):
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
我們只分析豎直方向的測(cè)量過程躯枢,水平方向的過程是一樣的:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0; //所有子View高度和
int maxWidth = 0; //所有子View中最大寬度值
......
final int count = getVirtualChildCount(); //豎直方向的子View總數(shù)
final int widthMode = MeasureSpec.getMode(widthMeasureSpec); //獲取父布局的寬/高的模式
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false; //是否跳過測(cè)量
......
int largestChildHeight = Integer.MIN_VALUE;
// See how tall everyone is. Also remember max width.獲取每一個(gè)子View的高度闺金,同時(shí)記錄最大的寬度
for (int i = 0; i < count; ++i) { //循環(huán)遍歷每一個(gè)子View
final View child = getVirtualChildAt(i); //一個(gè)一個(gè)的獲取子View
......
if (child.getVisibility() == View.GONE) { //如果這個(gè)子View的可見性為GONE
i += getChildrenSkipCount(child, i); //將這個(gè)子View連同他的序號(hào)一并計(jì)入getChildrenSkipCount()這個(gè)方法中
continue;
}
if (hasDividerBeforeChildAt(i)) { //如果這個(gè)子View帶有divider分割線
mTotalLength += mDividerHeight; //mTotalLength累加分割線的高度。
}//mDividerHeight對(duì)應(yīng)android:divider="@drawable/spacer_medium"這個(gè)屬性的高度讥巡,通常添加的是一個(gè)drawable圖片
//獲取子View的LayoutParams屬性
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight; //totalWeight累加LayoutParams中的weight屬性值
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
//如果這個(gè)子View高度的heightMode為EXACTLY槽棍,并且高度為0炼七,并且比重大于0布持。對(duì)照上文的表格可知按傅,當(dāng)父mode為EXACTLY
//時(shí)胧卤,此時(shí)子View的高度為一個(gè)確定的值(這里為0),那么最終的高度就為childSize也就是0.顯然這個(gè)時(shí)候沒有可見的View况芒。
final int totalLength = mTotalLength;
//注意這里的totalLength/mTotalLength表示的是之前測(cè)量過的子View的高度和(以及如果本次測(cè)量的View有divider分
//割線的話就加上本次測(cè)量View的分割線圖片高度)
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
//此時(shí)總高度(本次+測(cè)過的)就是totalLength和(totalLength+本次測(cè)量View的上下Margin)兩個(gè)值中較大的那個(gè).
skippedMeasure = true; //跳過測(cè)量的標(biāo)志位(畢竟這個(gè)高度是0绝骚,也就是沒有可見的View皮壁,就沒有測(cè)量的意義了)
} else {
int oldHeight = Integer.MIN_VALUE; //oldHeight設(shè)為負(fù)無窮
if (lp.height == 0 && lp.weight > 0) {
//else代表著此時(shí)父View的模式heightMode為UNSPECIFIED或者AT_MOST虑瀑,并且這個(gè)子View想延伸以填充剩余的可用
//空間(雖然它的高度為0,但是它的比重不為0扔水,說明他有填充剩余可用空間的“欲望”)魔市,此時(shí)我們將它的模式轉(zhuǎn)變成
//WRAP_CONTENT以便它最終的高度不為0(同樣對(duì)照上文的表格克制此時(shí)子View的大小為父View剩余空間的大小,相當(dāng)于
//match_parent,當(dāng)然這里暫時(shí)不考慮UNSPECIFIED這種情況待德。)
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
}
//最終決定子View大小的方法
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec, ①
totalWeight == 0 ? mTotalLength : 0);
if (oldHeight != Integer.MIN_VALUE) {//如果oldHeight不為負(fù)無窮,說明經(jīng)歷了上段代碼中else的情況
lp.height = oldHeight; //此時(shí)height=oldHeight=0
}
final int childHeight = child.getMeasuredHeight(); //獲取當(dāng)前子View的測(cè)量高度
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
//mTotalLength + childHeight +lp.topMargin +lp.bottomMargin + getNextLocationOffset(child)
//表示之前測(cè)量過的子View的總高度+本次測(cè)量的子View的高度+本次測(cè)量的子Veiw的上下Margin,最后一個(gè)方法返
//回值為0较坛,可以直接忽略丑勤。
//那么此時(shí)總高度(之前的子View+本次測(cè)量的子View)就等于之前測(cè)量的子View的總高度和上面一串串值确封,兩者中的
//較大者爪喘,之所以這么干是因?yàn)閙argin+height的值可能是負(fù)的,不能因?yàn)閙argin的影響就縮小了總高度的測(cè)量和侦鹏,
//這樣做顯然得出的不是真實(shí)的高度.
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
//這里的largestChildHeight表示高度最大的子Veiw
}
}
......
final int margin = lp.leftMargin + lp.rightMargin; //左右Margin
final int measuredWidth = child.getMeasuredWidth() + margin; //本次測(cè)量的子View的寬度+左右Magin
maxWidth = Math.max(maxWidth, measuredWidth); //獲取總的子View中最大的Veiw寬度
......
}
上面這段代碼就是循環(huán)遍歷測(cè)量LinearLayout中所有子View的代碼略水,主要獲取兩個(gè)值:每個(gè)子View的高度和所有子View中最大的寬度渊涝。其中高度和寬度均是加上上下/左右Margin之后的結(jié)果跨释。注釋已經(jīng)寫的很清楚了岁疼,這里不在鰲述捷绒。
??當(dāng)LinearLayout測(cè)量完了所有子View之后疙驾,就需要開始測(cè)量自身了:
......
mTotalLength += mPaddingTop + mPaddingBottom; //上面的for循環(huán)已經(jīng)計(jì)算完了所有的子Veiw的高度它碎,這里開始計(jì)算
//LinearLayout自己的高度扳肛,也就是在所有子View高度之和的基礎(chǔ)之上加上上下Padding
int heightSize = mTotalLength;
// 將高度值再做一遍比較,即之前的高度值和getSuggestedMinimumHeight()較大的那個(gè)套腹,getSuggestedMinimumHeight()前面講過
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); ②
heightSize = heightSizeAndState & MEASURED_SIZE_MASK; //最終得出的LinearLayout總高度
......(省略針對(duì)不同情況縮放子View的代碼)
maxWidth += mPaddingLeft + mPaddingRight; //LinearLayout總寬度
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); //LinearLayout總寬度
//所有的子View測(cè)量之后电禀,經(jīng)過一系類的計(jì)算之后通過setMeasuredDimension()設(shè)置自己的寬高尖飞,對(duì)于LinearLayout,可能是高度的
//累加沮明,對(duì)于FrameLayout 可能用最大的字View的大小荐健,
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}
當(dāng)子元素測(cè)量完之后摧扇,LinearLayout就會(huì)根據(jù)子元素的測(cè)量情況來測(cè)量自己的大小。針對(duì)豎直方向的LinearLayout在张,它的寬度測(cè)量實(shí)際上就是View的測(cè)量過程(最大的子View寬度+左右margin+左右padding)帮匾,具體到代碼中為:
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
maxWidth += mPaddingLeft + mPaddingRight;
而LinearLayout的高度測(cè)量稍微復(fù)雜一點(diǎn)瘟斜,具體來說是指,如果它的布局中高度采用的是math_parent或者具體的數(shù)值蛇尚,那么它的測(cè)量過程和View一致取劫,即高度為sizeSpec谱邪;如果它的布局中高度采用的是wrap_content,那么它的高度是所有子元素占用的高度總和虾标,但是仍然不能超過它父容器的剩余空間,當(dāng)然它的最終高度還是要考慮再豎直方向得到padding.(這段結(jié)論取自《Android開發(fā)藝術(shù)探索》,筆者在分析中并沒有找到支持該結(jié)論的相關(guān)源碼蘸吓,但是相信主席一回库继,如果有人找到了歡迎指教)
??最終決定它的高度的是上面②處的resolveSizeAndState()方法(framewoks/base/core/java/android/view/View):
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
這個(gè)方法主要的作用是在父布局的MeasureSpec約束下艺谆,協(xié)調(diào)所需要的大小和狀態(tài)静汤。其中size是當(dāng)前LinearLayout所需要的寬度(最大寬度),measureSpec是父布局的MeasureSpec抹估,childMeasuredState為子View的測(cè)量狀態(tài)药蜻。上面的代碼已經(jīng)很清楚了谷暮,這里不再加以分析。
這里還要說明的一點(diǎn)就是上段代碼中①處的measureChildBeforeLayout()方法:
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到,改方法最終調(diào)用了ViewGroup類中的measureChildWithMargins()方法班利,最終調(diào)用了View類中的measure()方法罗标。
(三)舉例說明
進(jìn)過上面的過程我們的measure過程的源碼就分析完了闯割,下面我們通過一個(gè)例子來具體說明下這個(gè)過程。首先呢谢澈,我們要回顧一下淺談Activity從建立到顯示(setContentView源碼淺析)中的一些內(nèi)容:這片文章中锥忿,我們
為了說明開發(fā)中我們自己寫的xml文件在DecorView的層次關(guān)系淹朋,用到了一個(gè)系統(tǒng)定義的xml文件:R.layout.screen_simple,這個(gè)布局是我們的theme設(shè)置為NoTitleBar時(shí)的布局(SDK/platforms/android-23/data/res/layout/screen_simple.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
這個(gè)時(shí)候我們定義了一個(gè)activity_main.xml文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:paddingBottom="50dp"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hellow word"
android:textSize="20sp"/>
<View
android:id="@+id/selfView"
android:layout_width="match_parent"
android:layout_height="100dp" />
</LinearLayout>
并且畫出了兩者的層級(jí)關(guān)系圖:
OK,相信通過上一篇文章的分析你已經(jīng)清楚上面這張途中各部分之間的關(guān)系了春感,下面我們對(duì)上面布局中的一些元素做一下必要的說明:
??①上面我們說過鲫懒,這個(gè)R.layout.screen_simple是我們將系統(tǒng)的theme設(shè)置為NoTitleBar時(shí)的布局甲献。NoTitleBar也就意味著沒有titlebar或者actionbar的干擾晃洒,整個(gè)content區(qū)域只有我們的activity_main.xml,這樣就方便了我們之后對(duì)于測(cè)量流程的分析球及。
??②圖中id為statusBarBackgroud的Veiw,顧名思義就是狀態(tài)欄背景镊尺。為了抓住主要矛盾鹅心,在之后的分析中我們會(huì)跳過狀態(tài)欄的測(cè)量分析颅筋。
??③screen_simple下屬的ViewStub,我們對(duì)這個(gè)東西做一下說明:ViewStub是View的子類议泵;它不可見(可見性為GONE),寬高均為0先口;它用來延遲加載布局資源(惰性加載),常用于布局優(yōu)化谐宙。顯然凡蜻,ViewStub在這里是用來惰性加載titlebar/actionbar的,關(guān)于什么是惰性加載忠荞,讀者可以自行搜索钻洒;另外,由于它的可見性為GONE,并且寬高都是0头遭,因此再measure()的過程中系統(tǒng)也會(huì)自動(dòng)跳過计维。
OK,下面我們正式開始測(cè)量流程的分析。首先欠母,我們要回到三大流程開始的地方踩寇,也就是ViewRootImpl類中的performTraversals()方法俺孙,不清楚的同學(xué)可以看View繪制流程及源碼解析(一)——performTraversals()源碼分析這片文章睛榄。再這片文章的"第六段代碼"中,我們看到了measure()過程的開始方法——performMeasure():
......
//mStopped的注釋:Set to true if the owner of this window is in the stopped state.如果此窗口的所有者
//(Activity)處于停止?fàn)顟B(tài)票罐,則為ture.
if (!mStopped || mReportNextDraw) { //mReportNextDraw Window上報(bào)下一次繪制.
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
//mWidth != host.getMeasuredWidth() 表示frame的寬不等于初始DecorView寬.
//getMeasuredWidth()方法可以獲取View測(cè)量后的寬高该押,host上面說過為DecorView根布局.
//獲得view寬高的測(cè)量規(guī)格烟具,lp.width和lp.height表示DecorView根布局寬和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//開始執(zhí)行測(cè)量操作 ①
第一步,DecorVeiw的測(cè)量
??通過上篇文章之前的分析可知,這里的getRootMeasureSpec()就是獲取DecorView的MeasureSpec,也就是說這里進(jìn)行的是頂層View——DecorView的測(cè)量。在performMeasure()方法中冀痕,我們調(diào)用了DecorView的measure()方法:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); //mView就是DecorView
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
而DecorView是一個(gè)FrameLayout,因此我們需要區(qū)看FrameLayout類的onMeaure()方法,這個(gè)方法相對(duì)LinearLayout的簡(jiǎn)單多了(早知道在上面我就直接分析FrameLayout了,mdzz):
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
.......
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 循環(huán)遍歷的子View,只要不是GONE的都會(huì)參與測(cè)量
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
......
}
}
......
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
......
}
需要說明的一點(diǎn)是婿斥,由于DecorView是最頂層的View,因此它的寬高M(jìn)easureSpec是由它自己的寬度和LayoutParams決定的getRootMeasureSpec(mWidth, lp.width);
和getRootMeasureSpec(mHeight, lp.height);
携龟。再者,由于DecorView是最頂層View的緣故蕊蝗,通常情況下它可以代表整個(gè)屏幕蓬戚,因此mWidth和mHeight既是DecorView的寬高也是整個(gè)屏幕的寬高。lp是WindowManager.LayoutParams幢泼,它的lp.width和lp.height的默認(rèn)值是MATCH_PARENT,所以對(duì)于DecorView通過getRootMeasureSpec 生成的測(cè)量規(guī)格MeasureSpec 的mode是MATCH_PARENT,size是屏幕的高寬招驴,這里我們假設(shè)有一個(gè)1920x1080像素的手機(jī)那么size就對(duì)應(yīng)這兩個(gè)值别厘。再根據(jù)文章最開頭第一大點(diǎn)中的第(一)小點(diǎn)"根布局的MeasureSpec獲取"可知拥诡,當(dāng)根布局的LayoutParams為Match_parent時(shí)触趴,它的MeasureSpec.mode為EXACTLY,用圖像表示為:
第二步袋倔,DecorView到screen_simple
??在FrmeLayout的onMeasure()方法中雕蔽,for循環(huán)遍歷的子View只有兩個(gè):screen_simple,xml和stautsbarbackdroud,前面我們已經(jīng)說過會(huì)跳過狀態(tài)欄的分析,所以這里只看screen_simple,xml對(duì)應(yīng)的View批狐。measureChildWithMargins()中第一個(gè)參數(shù)child代表的就是screen_simple.xml對(duì)應(yīng)的View,可以看到最終調(diào)用了該View的measure()方法,并傳遞進(jìn)去了DecorView的寬高M(jìn)easureSpec嚣艇。
??我們看看此時(shí)screen_simple對(duì)應(yīng)的View的MeasureSpec的兩個(gè)參數(shù)承冰。由于該View是系統(tǒng)的View,它的LayoutParams默認(rèn)都是match_parent食零,又因?yàn)樗母覆季?DecorVeiw)的mode為EXACTLY,根據(jù)我們文章開頭LayoutParams和MeasureSpec的對(duì)應(yīng)關(guān)系可知困乒,screen_simple的size為父布局剩余的空間大小,mode為EXACTLY贰谣;又因?yàn)镈ecorView是FrameLayout,statusbar是疊在screen_simple上面的娜搂,因此相當(dāng)于狀態(tài)欄不占用底下一層的DecorView的空間——綜上,screen_simple的size等于DecorView的size為1920x1080,mode為EXACTLY吱抚。用圖表示:
算出screen_simple.xml對(duì)應(yīng)的View的MeasureSpec之后呢百宇,就開始計(jì)算調(diào)用measure()方法計(jì)算它的大小。由上面層次圖及xml文件可知screen_simple.xml對(duì)應(yīng)的View是一個(gè)方向?yàn)関ertical的LinearLayout秘豹,因此child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
最終會(huì)調(diào)用LinearLayout的onMeasure()方法携御,(這個(gè)過程在上文是有詳細(xì)的講解的,如果不記得的話可以翻上去看一下)最終會(huì)調(diào)用LinearLayout的measureChildBeforeLayout()并在該方法中又一次調(diào)用measureChildWithMargins(child, widthMeasureSpec, totalWidth,heightMeasureSpec, totalHeight);
方法既绕,開始計(jì)算screen_simple.xml當(dāng)中各個(gè)子元素的大小(子元素測(cè)量完之后再測(cè)量自身的大小)啄刹。
??這里還要注意的一點(diǎn)是screen_simple.xml對(duì)應(yīng)的View有一個(gè)padding值:mPaddingTop=100px,這個(gè)可能和狀態(tài)欄的高度有關(guān)凄贩,我們測(cè)量的最后會(huì)發(fā)現(xiàn)id/statusBarBackground的View的高度剛好等于100px誓军。
第三步,從screen_simple到R.id.content
??這個(gè)時(shí)候我們進(jìn)入了screen_simple.xml對(duì)應(yīng)的View中怎炊,該View有兩個(gè)子元素:R.id.content和VeiwStub谭企。ViewStub上面我們已經(jīng)說過廓译,它的可見性為GONE评肆,且寬高均為0,所以測(cè)量的話會(huì)系統(tǒng)會(huì)直接跳過非区。那么我們就只剩下R.id.content了,通過上面的結(jié)構(gòu)層次圖瓜挽,我們知道這個(gè)R.id.content實(shí)際上就是我們寫的activity_main.xml文件。
??這里一樣征绸,我們首先需要根據(jù)screen_simple.xml對(duì)應(yīng)View的MeasureSpec(size:1980x1080,mode:EXACTLY)和R.id.content的LayoutParams(match_parent x match_parent)來計(jì)算出它的MeasureSpec,當(dāng)然這里的計(jì)算也需要遵循ViewGroup的measureChildWithMargins()方法久橙,這個(gè)方法我們之前講過,這里我們?cè)儋N一遍源碼:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
**resultSize = size;**
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
......
}
這里我們只看高度管怠,之前提到過淆衷,screen_simple.xml對(duì)應(yīng)的View有一個(gè)padding值:mPaddingBottom=100px,因此getChildMeasureSpec(int spec, int padding, int childDimension)
中第二個(gè)參數(shù)的值為100px渤弛,第三個(gè)參數(shù)值為match_parent祝拯。由int size = Math.max(0, specSize - padding);
可知這里的size變量為(1920-100)x1080 = 1820x1080,又父布局screen_simple.xml的specMode為EXACTLY,childDimension == LayoutParams.MATCH_PARENT,因此最終的結(jié)果resultSize = size = 1820x1080佳头;resultMode = MeasureSpec.EXACTLY鹰贵。用圖表示為:
第四步,activity_main.xml測(cè)量規(guī)格MeasureSpec分析
??R.id.content是一個(gè)FrameLayout康嘉,下面就一個(gè)activity_main.xml子元素碉输,這個(gè)時(shí)候我們同樣先要確定activity_main.xml這個(gè)LinearLayout(id/linearLayout)的MeasureSpec值,這里我們?cè)儋N一下布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:paddingBottom="50dp"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hellow word" />
<View
android:id="@+id/selfView"
android:layout_width="match_parent"
android:layout_height="100dp" />
</LinearLayout>
注意id/linearLayout的android:layout_width
亭珍,android:layout_height
敷钾,這幾個(gè)參數(shù)的值。父布局screen_simple.xml的MeasureSpec.heightMode = EXACTLY,id/linearLayout的android:layout_height為wrap_content肄梨,對(duì)照上文的LayoutParams與MeasureSpec可知闰非,id/linearLayout的MeasureSpec.heightMode為AT_MOST;同理峭范,它的MeasureSpec.weightMode為EXACTLY财松;
??下面我們來看它的specSize,注意:android:layout_marginTop="50dp"
纱控,這里我們需要說明一下Android設(shè)備中px/dp的換算——dp x 基準(zhǔn)比例 = px辆毡; 基準(zhǔn)比例 = 系統(tǒng) DPI / 160 ;一般在1920*1080 分辨率的手機(jī)上 默認(rèn)就使用 480 的 dpi ,不管的你的尺寸是多大都是這樣甜害,除非廠家手動(dòng)修改了配置文件舶掖,因此這里可以得到——基準(zhǔn)比例=480 / 160 = 3;px = dp x 基準(zhǔn)比例 = 50 x 3 = 150px尔店。(關(guān)于更多這方面的知識(shí)推薦一片文章[Android] Android開發(fā)中dip眨攘,dpi,density嚣州,px等詳解感興趣的瀆職可自行參閱)鲫售。也就是說,id/linearLayout在高度上少了150px该肴,根據(jù)上面getChildMeasureSpec()方法中第二個(gè)參數(shù):padding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed可知padding = 150px,又int size = Math.max(0, specSize - padding) = 1670 x 1080情竹。
??所以我們現(xiàn)在可以得出id/linearLayout的MeasureSpec,用圖表示:
第五步匀哄,TextView和View的測(cè)量分析
1.TextView
??接上面秦效,先看TextView:
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hellow word" />
由它的父布局id/linearLayout的MeasureSpec.mode可知,TextView的MeasureSpec.heightMode為AT_MOST涎嚼;MeasureSpec.weightMode為EXACTLY阱州;至于它的大小,由于id/linearLayout中有android:paddingBottom="50dp"
,它的子元素的生存空間又被壓縮了50dp也就是150px法梯,所以TextView的Measure.specSize為(1670-150)x1080=1520x1080(注意這個(gè)是對(duì)TextView的寬高約大小束苔货,可不是正真的大小,不要忘了MeasureSpecd的正真含義)。算出id/textView 的MeasureSpec 后蒲赂,接下來我們來計(jì)算textView的正真大小:textView.measure(childWidthMeasureSpec, childHeightMeasureSpec);跳轉(zhuǎn)到TextView.onMeasure()方法中(frameworks/base/core/java/android/widget/TextView):
if (heightMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
height = heightSize;
mDesiredHeightAtMeasure = -1;
} else {
int desired = getDesiredHeight(); //desired = 107px
height = desired;
mDesiredHeightAtMeasure = desired;
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desired, heightSize); //heightSize = 1520px
}
}
TextView字符的高度(也就是TextView的content高度[wrap_content])測(cè)出來等于107px阱冶,107px 并沒有超過1980px(允許的最大高度),所以實(shí)際測(cè)量出來TextView的高度是107px滥嘴。(這個(gè)筆者并沒有親自測(cè)木蹬,直接拿來主義,但是差距不會(huì)太大)
??最終算出id/text 的mMeasureWidth=1080,mMeasureHeight=107px若皱。
2.View
??接下來我們來看這個(gè)id/selfView的子View:
<View
android:id="@+id/selfView"
android:layout_width="match_parent"
android:layout_height="100dp" />
由于這里指明了它的高度為100dp也就是300px镊叁,加上它的父布局id/linearLayout的MeasureSpec.heightMode為AT_MOST,因此它的MeasureSpec.heightMode為EXACTLY,MeasureSpec.weightMode也為EXACTLY走触,heightSpecSize為自己的尺寸300px,所以它的specSize為300 x 1080晦譬,然后計(jì)算View的大小:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
最終算出id/selfView的mMeasureWidth=1080px,mMeasureHeight=300px。
第六步互广,activity_main.xml自身大小測(cè)量分析
??id/linearLayout 的子View的高度都計(jì)算完畢了敛腌,接下來id/linearLayout就通過所有子View的測(cè)量結(jié)果計(jì)算自己的高寬,之前我們著重分析過LinearLayout的測(cè)量過程惫皱,這里我們?cè)儋N一下關(guān)鍵代碼:
mTotalLength += mPaddingTop + mPaddingBottom;
簡(jiǎn)單理解就是子View的高度的累積+自己的Padding,也就是107px(TextView) + 300px(View) + 150px(paddingBottom="50dp") = 557px最終算出id/linearLayout的mMeasureWidth=1080px,mMeasureHeight=557px像樊。
第七步,從activity到R.id.content旅敷,計(jì)算R.id.content自身的大小
第八步生棍,從R.id.content到screen_simple,計(jì)算screen_simple自身的大小
第九步媳谁,從screen_simple到DecotView,計(jì)算DeorView自身的大小
后面三步和第六步同理涂滴,邏輯都是計(jì)算完了子元素的大小加上自身的padding,計(jì)算出自身的大小晴音。
站在巨人的肩膀上摘蘋果:
①Android View的繪制流程
②《Android開發(fā)藝術(shù)探索》