View展示出來一共有三個過程秩命,大致為:測量每個View大邪暗痢(measure
)-->把每個View放置到相應(yīng)的位置(layout
)-->繪制每個View(draw
)
今天本文主要詳解的是測量每個View大小(measure
)這個過程
主要先從如下流程圖開始,從上往下一路詳解其主要的方法原理
主要方法簡介
handleLaunchActivity
該方法會執(zhí)行很多方法,這個是入口,簡單來說會創(chuàng)建Activity對象
- 調(diào)用其啟動生命周期吃型,
attach
、onCreate
僚楞、onStart
勤晚、onResume
枉层,以及添加到WindowManager中 - 我們在Activity的
attach
函數(shù)中新建了PhoneWindow
對象,在PhoneWindow
的setContentView
函數(shù)中會調(diào)用installDector
來創(chuàng)建DecorView
對象
handleResumeActivity
進入到繪制界面后赐写,會走到handleResumeActivity方法鸟蜡,通過performResumeActivity調(diào)用activity的onResume
方法
如下是進入繪制界面的主要步驟
- 第一步通過ViewManager wm = a.getWindowManager(),綁定WindowManager獲取其對象挺邀。
- 第二步
wm.addView(decor, l)
將decorView傳入揉忘,而WindowManager的實現(xiàn)類是WindowManagerImpl,而它則是通過WindowManagerGlobal代理實現(xiàn)addView的
WindowManagerGlobal
WindowManagerImpl這種工作模式是典型的橋接模式端铛,將所有的操作委托給WindowManagerGlobal來實現(xiàn),如下是其實現(xiàn)的addView方法
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
//ViewRootImpl開始繪制view
root.setView(view, wparams, panelParentView);
...
}
ViewRootImpl.setView
- 先調(diào)用
requestLayout()
泣矛,完成第一次layout布局過程,以確保在收到任何系統(tǒng)事件后面重新布局禾蚕。 - 接著會通過WindowSession最終來完成Window的添加過程您朽。在下面的代碼中mWindowSession類型是IWindowSession,它是一個
Binder
對象夕膀,真正的實現(xiàn)類是Session,也就是說這其實是一次IPC
過程美侦,遠程調(diào)用了Session中的addToDisPlay
方法产舞。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
//requestLayout最終會調(diào)用performTraversals方法來完成View的繪制
requestLayout();
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
} finally {
if (restore) {
attrs.restore();
}
}
}
performTraversals
ViewRootImpl調(diào)用performTraversals方法開始對view的測量布局繪制
關(guān)鍵方法有三個
- performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)
- performLayout(lp, desiredWindowWidth, desiredWindowHeight)
- performDraw()
其中當窗口的最新尺寸與ViewRootImpl中的現(xiàn)有尺寸不同時
layoutRequested
會設(shè)置會true,這個時候會要求進行預測量即measureHierarchy
measureHierarchy方法
該方法用于測量整個控件樹菠剩,通常針對懸浮彈框布局易猫,通過預測量來調(diào)整彈框的顯示大小,達到最好的視覺顯示效果(如下圖右邊)具壮。
一共有兩次協(xié)商測量機會(兩次協(xié)商測量僅在其width等于wrap_content下進行准颓,因為match_parent及設(shè)置大小均沒必要進行協(xié)商測量,因為一個是填充滿父view棺妓,另一個是已設(shè)定好的大小)攘已,第一次協(xié)商使用系統(tǒng)資源預設(shè)好的大小配置,若設(shè)置后仍不滿意則進入到第二次協(xié)商即 (baseSize+desiredWindowWidth)/2
怜跑,若仍不滿意样勃,則放棄所有限制進入最終測量
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;// 合成后的用于描述寬度的MeasureSpec
int childHeightMeasureSpec;// 合成后的用于描述高度的MeasureSpec
boolean windowSizeMayChange = false;// 表示測量結(jié)果是否可能導致窗口的尺寸發(fā)生變化
boolean goodMeasure = false;// 表示了測量是否能滿足控件樹充分顯示內(nèi)容的要求
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
//預測量只在wrap_content中進行
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
//第一次協(xié)商是通過使用它最期望的寬度限制進行測量。這一寬度限制定義為一個系統(tǒng)資源
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//第一次測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//通過獲取view的測量結(jié)果來判斷性芬,如果滿足條件則認為是測量滿意否則進入第二次協(xié)商
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
// 第二次協(xié)商
baseSize = (baseSize+desiredWindowWidth)/2;
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
// 第二次測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) Log.v(mTag, "Good!");
goodMeasure = true;
}
}
}
}
if (!goodMeasure) {
// 最終測量峡眶。當控件樹對上述兩次協(xié)商的結(jié)果都不滿意時,measureHierarchy()放棄所有限制
// 做最終測量植锉。這一次將不再檢查控件樹是否滿意了辫樱,因為即便其不滿意,measurehierarchy()也沒有更多的空間供其使用了
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
//如果測量結(jié)果與viewrootimpl的高寬不一致則需要進行調(diào)整
windowSizeMayChange = true;
}
}
return windowSizeMayChange;
}
onMeasure()
從上面所說的當我們進入到performTraversals后俊庇,會執(zhí)行performMeasure來進入view的測量即onMeasure
下圖是view測量的流程圖
注意:FrameLayout(id/content)往下走的view為自定義的layout布局
從上圖可知view的根view是DecorView
狮暑,DecorView由TitleView
和ContentView
構(gòu)成鸡挠,而ContentView就是我們啟動activity時setContentView進去的xml布局,也就是說當我們xml中的textview組件需要繪制的時候心例,必須先從DecorView開始宵凌,由外層view一路到最內(nèi)層view的繪制過程。
MeasureSpec
MeasureSpec類封裝了一個View的規(guī)格尺寸止后,包括View的寬和高的信息瞎惫,但是要注意,MeasureSpec并不是指View的測量寬高译株,這是不同的瓜喇,是根據(jù)MeasueSpec而測出測量寬高。
在系統(tǒng)中組件的大小模式有三種:
精確模式(
MeasureSpec.EXACTLY
)
在這種模式下歉糜,尺寸的值是多少乘寒,那么這個組件的長或?qū)捑褪嵌嗌佟?/p>最大模式(
MeasureSpec.AT_MOST
)
特指當前組件的寬或高大小只能在父組件給出的最大空間里定義,不得超出未指定模式(
MeasureSpec.UNSPECIFIED
)
父控件對子控件不加任何束縛匪补,子元素可以得到任意想要的大小伞辛,這種MeasureSpec一般是由父控件自身的特性決定的。比如ScrollView夯缺,它的子View可以隨意設(shè)置大小蚤氏,無論多高,都能滾動顯示踊兜,這個時候竿滨,size一般就沒什么意義。
一個int型整數(shù)表示兩個東西(大小模式和大小的值)捏境,一個int類型我們知道有32位于游。而模式有三種,要表示三種狀態(tài)垫言,至少得2位二進制位贰剥。于是系統(tǒng)采用了最高的2位表示模式。如圖:
最高兩位是
00
的時候表示"未指定模式"筷频。即MeasureSpec.UNSPECIFIED
最高兩位是
01
的時候表示"'精確模式"鸠澈。即MeasureSpec.EXACTLY
最高兩位是
11
的時候表示"最大模式"。即MeasureSpec.AT_MOST
當然我們并不需要刻意的去記住截驮,因為MeasureSpec類已提供getSize笑陈、getMode等方法給我們,但我們應(yīng)該大概了解即可葵袭。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
//判斷當前布局的寬高是否是match_parent模式涵妥,如果是則置measureMatchParent為false.
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
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) {
// 該方法主要把margin 及 padding 也作為子視圖大小的一部分并返回MeasureSpec給到子view進行測繪
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
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());
if (measureMatchParentChildren) {
//子view為寬或高為LayoutParams.MATCH_PARENT模式則加入mMatchParentChildren
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
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();
//根據(jù)當前布局的寬高來測量模式為LayoutParams.MATCH_PARENT的子view
//子view可以覆蓋的范圍是FrameLayout的測量寬度,減去padding和margin后剩下的空間坡锡。
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
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);
}
//對于這部分的子View需要重新進行measure過程
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
measureChildWithMargins方法
該方法主要把margin 及 padding 也作為子視圖大小的一部分并返回MeasureSpec給到子view進行測繪
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);
}
getChildMeasureSpec方法
根據(jù)獲取的specMode蓬网、specSize來重新計算resultSize窒所、resultMode
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) {
case MeasureSpec.EXACTLY:
//在這種模式下,尺寸的值是多少帆锋,那么這個組件的長或?qū)捑褪嵌嗌? if (childDimension >= 0) {
//如果子view設(shè)置具體值吵取, 則取子view大小,mode設(shè)置為 MeasureSpec.EXACTLY(即match_parent)
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子view的大小是match_parent,則填充父view空間锯厢,size為父的空間大小減去子view的margin邊距(如果是計算子view的高度空間皮官,則減去頂部和底部margin,反之則左右margin)
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//與match_parent一致实辑,但是mode則為AT_MOST捺氢,說明希望子View的大小不要超過父View的大小
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
//這個也就是父組件,能夠給出的最大的空間剪撬,當前組件的長或?qū)捴荒茉谄涓附M件給出的范圍內(nèi)
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子View的大小為父View的size摄乒,但是mode則為AT_MOST,說明希望子View的大小不要超過父View的大小
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//與上面一致
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
//當前組件残黑,可以隨便用空間馍佑,不受限制。
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//大小自己設(shè)置
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
measureVertical方法
setContentView中的布局文件是一個以LinearLayout為根布局 其子view是TextView為例
從上面DecorView的onMeasure到其繼承的FrameLayout onMeasure一路遍歷其子view測量梨水,最后進入setContentView
LinearLayout的onMeasure一共有兩個方法拭荤,根據(jù)其布局屬性來執(zhí)行,分別為
- measureVertical(widthMeasureSpec, heightMeasureSpec)
- measureHorizontal(widthMeasureSpec, heightMeasureSpec)
以measureVertical為例
先獲取子view數(shù)量然后進行遍歷冰木,如果heightMode為Match_Parent且高度為0權(quán)重大于0 則統(tǒng)計當前Linearlayout總大小穷劈,并設(shè)置skippedMeasure為true笼恰,進入到權(quán)重測量(即根據(jù)weight來進行二次measure)踊沸,否則則進入measureChildBeforeLayout(關(guān)鍵方法)該方法點進去其實就是measureChildWithMargins用于把 margin 及 padding 也作為子視圖大小的一部分返回,最后進入child.measure計算測量社证,當子view測量完成后逼龟,再由父view設(shè)置setMeasuredDimension(widthSizeAndState
,heightSizeAndState
)決定當前容器大小
widthSizeAndState:
主要由兩種情況得出maxWidth
- maxWidth由所有子view的寬度+margin疊加
- 當不填充滿父view及父的widthMode != MeasureSpec.EXACTLY都滿足下
則取alternativeMaxWidth,alternativeMaxWidth及getSuggestedMinimumWidth通過max得出的值maxWidth
追葡,再由resolveSizeAndState(maxWidth
,widthMeasureSpec
,childState
)返回一個合適的大小即widthSizeAndState
其中weightedMaxWidth
:權(quán)重 的最大寬
其中alternativeMaxWidth
:改變本地最大寬度
關(guān)于alternativeMaxWidth
的獲取如下:
if (lp.weight > 0) {
//如果子view設(shè)置了weight
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
····
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}else{
····
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
}
heightSizeAndState:
主要由mTotalLength及getSuggestedMinimumHeight()通過max得出的值heightSize
腺律,再由resolveSizeAndState(heightSize
, heightMeasureSpec
, 0
)返回一個合適的大小即heightSizeAndState
其中mTotalLength
:包含所有子view大小、mDividerHeight
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;
int maxWidth = 0;
int childState = 0;
int alternativeMaxWidth = 0;
int weightedMaxWidth = 0;
boolean allFillParent = true;
float totalWeight = 0;
final int count = getVirtualChildCount();
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
if (hasDividerBeforeChildAt(i)) {//如果設(shè)置了divider宜肉,則加上divider高度
mTotalLength += mDividerHeight;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
//如果heightMode為Match_Parent且高度為0權(quán)重大于0 則統(tǒng)計當前Linearlayout總大小匀钧,并設(shè)置skippedMeasure為true,進入到權(quán)重測量
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
int oldHeight = Integer.MIN_VALUE;
if (lp.height == 0 && lp.weight > 0) {
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
}
//該方法主要是將LinearLayout大小傳入進去讓子view根據(jù)傳入的mTotalLength計算padding 因為是列表形式延伸子view谬返,所以widthUsed傳0 只需傳heightUsed之斯,若是縱向延伸子view,則傳widthUsed遣铝,而heightUsed為0佑刷,進入measureChildWithMargins方法(如上有詳解)
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);
if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
}
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
if (useLargestChild) {
//該屬性為true的時候, 所有帶權(quán)重的子元素都會具有最大子元素的最小尺寸莉擒。
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}
if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
mBaselineChildTop = mTotalLength;
}
boolean matchWidthLocally = false;
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
matchWidth = true;
matchWidthLocally = true;
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
// 合并子元素的測量狀態(tài)
childState = combineMeasuredStates(childState, child.getMeasuredState());
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}
i += getChildrenSkipCount(child, i);
}
if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
// Account for negative margins
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
}
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
int delta = heightSize - mTotalLength;
if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {
// Child said it could absorb extra space -- give him his share
int share = (int) (childExtra * delta / weightSum);
weightSum -= childExtra;
delta -= share;
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight +
lp.leftMargin + lp.rightMargin, lp.width);
if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
// child was measured once already above...
// base new measurement on stored values
int childHeight = child.getMeasuredHeight() + share;
if (childHeight < 0) {
childHeight = 0;
}
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
} else {
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
MeasureSpec.EXACTLY));
}
// Child may now not fit in vertical dimension.
childState = combineMeasuredStates(childState, child.getMeasuredState()
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
lp.width == LayoutParams.MATCH_PARENT;
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
// TODO: Should we recompute the heightSpec based on the new total length?
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
//我們沒有限制,所以把所有的加權(quán)視圖和最大的子view一樣高瘫絮。
if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {
child.measure(
MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(largestChildHeight,
MeasureSpec.EXACTLY));
}
}
}
}
if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}
maxWidth += mPaddingLeft + mPaddingRight;
// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if (matchWidth) {
forceUniformWidth(count, heightMeasureSpec);
}
}
resolveSizeAndState 方法
傳入最大的大小涨冀、父類限制的大小、子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) {
//當specMode為AT_MOST麦萤,并且父控件指定的尺寸specSize小于View自己想要的尺寸時鹿鳖,
//我們就會用掩碼MEASURED_STATE_TOO_SMALL向量算結(jié)果加入尺寸太小的標記
//這樣其父ViewGroup就可以通過該標記其給子View的尺寸太小了,
//然后可能分配更大一點的尺寸給子View
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);//使用了位運行 返回一個帶大小和狀態(tài)的值
}
getDefaultSize方法
/**
* 作用是返回一個默認的值频鉴,如果MeasureSpec沒有強制限制的話則使. 用提供的大小.否則在允許范圍內(nèi)可任意指定大小
* 第一個參數(shù)size為提供的默認大小栓辜,第二個參數(shù)為測量的大小
*/
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
// Mode = UNSPECIFIED時使用提供的默認大小
case MeasureSpec.UNSPECIFIED:
result = size;
break;
// Mode = AT_MOST,EXACTLY時使用測量的大小
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
getSuggestedMinimumHeight方法
獲取最小的推薦高度
protected int getSuggestedMinimumHeight() {
//如果沒有給View設(shè)置背景,那么就返回View本身的最小寬度mMinWidth
//如果給View設(shè)置了背景垛孔,那么就取View本身最小寬度mMinWidth和背景的最小寬度的最大值
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}