view的測量過程
之所以先講view的測量過程,是因為ViewGroup測量的時候是先把他的所有子view測量完成后才能測量viewgroup自身,view的measure是viewGroup來調(diào)用的厦幅。在view的測量過程中,parentView首先會調(diào)用getChildMeasureSpec(int spec, int padding, int childDimension)
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* 這個方法將根據(jù)父容器的MeasureSpec和子View LayoutParams中的寬/高
* 為子View生成最合適的MeasureSpec
*
* @param spec 父容器的MeasureSpec
* @param padding 父容器的內(nèi)間距(padding)加上子View的外間距(margin)
* @param childDimension 子View的LayoutParams中封裝的width/height
* @return 子View的MeasureSpec
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// ① 對父容器的MeasureSpec進行解包
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// ② 減去間距,得到最大可用空間
int size = Math.max(0, specSize - padding);
// 記錄子View最終的大小和測量模式
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// ③ 父容器是精準(zhǔn)測量模式
case MeasureSpec.EXACTLY:
if (childDimension >= 0) { //如果子view在LayoutParam中指定了大小,那么子view的resultSize 就是該大小滑频,模式是EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) { //如果子view中LayoutParams是MATCH_PARENT,則view的大小等于最大可用大小唤冈,測量模式是EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) { //如果子view中LayoutParams是WRAP_CONTENT峡迷,則view的大小等于最大可用大小,測量模式是AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// ④ 父容器指定了一個最大可用的空間
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// ⑤ 父容器不對子View的大小作出限制
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
// ⑥ 將最終的size和mode打包為子View需要的MeasureSpec
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
計算該view的WidthMeasureSpec和HeightMeasureSpec你虹,然后調(diào)用view的measure();
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
measure方法又調(diào)用了onMeasure方法凉当,view.OnMeasure()才是真正完成了view的測量。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension( //設(shè)置測量的結(jié)果
getDefaultSize(getSuggestedMinimumWidth(),
widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(),
heightMeasureSpec));
}
在view.onMeasure中調(diào)用setMeasuredDimension來保存測量的結(jié)果售葡,那么測量結(jié)果是怎么來的呢?我們繼續(xù)看getDefaultSize()忠藤;
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;
}
在getDefaultSize中傳入了兩個參數(shù)贮缅,一個是最小大小,一個是MeasureSpec介却。首先獲得MeasureSpec中的測量模式和暫定大小谴供,然后根據(jù)測量模式來返回不同的測量結(jié)果。如果測量模式是UNSPECIFIED齿坷,則測量的結(jié)果就是最小的大小桂肌,如果測量模式是AT_MOST或者EXACTLY,測量的大小都是MeasureSpec中的大小永淌。
而MeasureSpec中的大小是viewgroup調(diào)用getChildMeasureSpec生成的崎场,查看getChildMeasureSpec的邏輯我們會看出,如果子view的layoutParem是warp_content遂蛀,那么測量結(jié)果的測量模式就是AT_MOST谭跨,測量大小就是父容器的最大可用空間,所以我們在繼承view自定義view的時候李滴,如果重寫onMeasure螃宙,那么設(shè)置LayoutParam設(shè)置warp_content是不能用的,大小始終是可用的最大大小所坯。
那么getDefaultSize的最小大小是怎么來的呢谆扎?是通過getSuggestedMinimumWidth()和getSuggestedMinimumHeight()得到,我們繼續(xù)看getSuggestedMinimumWidth包竹。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
在getSuggestedMinimumWidth中燕酷,首先判斷該view有沒有背景,如果沒有背景周瞎,返回android:minWidth(如果沒有設(shè)置android:minWidth苗缩,就默認是0),如果有背景声诸,就返回背景大小和android:minWidth中的最大值酱讶。
最后,onMeasure講測量的結(jié)果調(diào)用
getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),
保存起來彼乌,這樣一個view就算測量完成了泻肯。
畫個圖來總結(jié)一下。
首先viewGroup會計算子view的MeasureSpec慰照,然后講所計算的MeasureSpec傳入子view的measure中灶挟,子view開始測量自身。measure又會調(diào)用onMeasure來完成真正的測量過程毒租。onMeasure會調(diào)用setMeasureDemension來保存測量的結(jié)果稚铣,測量結(jié)果是通過getDefaultSize來計算的,getDefaultSize通過比較最小的大小和MeasureSpec中的暫定大小,最終確定測量大小惕医。
viewGroup的測量過程
viewGroup中耕漱,首先會測量所有子view的大小,然后根據(jù)子view的大小來確定viewGroup的大小抬伺。由于不同的ViewGroup的測量結(jié)果不一樣(LinearLayout的height是所有view的height之和螟够,而FrameLayout的Height是所有子view的最大的height),所以viewGroup中沒有實現(xiàn)具體的onMeasure()峡钓;我們以FrameLayout來舉例妓笙。
首先還是measure()方法調(diào)用onMeaure方法,具體的測量實現(xiàn)我們看onMeasure()即可
//這里的widthMeasureSpec椒楣、heightMeasureSpec
//其實就是我們frameLayout可用的widthMeasureSpec 给郊、
//heightMeasureSpec
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//1、獲得frameLayout下childView的個數(shù)
int count = getChildCount();
//2捧灰、看這里的代碼我們可以根據(jù)前面的Measure圖來進行分析淆九,因為只要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大小確認了原在,我們就需要對寬或高為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);
}
}
}
protected void measureChildWithMargins(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()病涨,進行最后的測量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
從上述代碼我們可以將viewGroup的測量過程分為一下幾個步驟
1.初始化變量
獲取childCount,清理存儲模式為MATCH_PARENT的child的隊列璧坟,初始化最大寬度和最大高度既穆。
2.遍歷所有子view,獲取子view的MeasureSpec雀鹃,測量子view的大小幻工,獲得所有子view的最大寬度和最大高度
通過for循環(huán)遍歷所有的子view,對于每一個子view褐澎,調(diào)用measureChildWithMargins会钝,measureChildWithMargins中首先獲得view的MeasureSpec,然后調(diào)用子view的measure完成子view的測量工三。
3. 根據(jù)自身MeasureSpec和子view的最大寬度和最大高度迁酸,確定自身大小
4. 重新確定LayoutParam==MATCH_PARENT的子view
由于LayoutParam==MATCH_PARENT的子view的大小還沒有確定,根據(jù)已經(jīng)確定的Viewgroup的大小俭正,重新計算子view的精確大小奸鬓。