View展示出来一共有三个过程,大致为:测量每个View大小(measure
)-->把每个View放置到相应的位置(layout
)-->绘制每个View(draw
)
今天本文主要详解的是测量每个View大小(measure
)这个过程
主要先从如下流程图开始,从上往下一路详解其主要的方法原理
主要方法简介
handleLaunchActivity
该方法会执行很多方法,这个是入口,简单来说会创建Activity对象
- 调用其启动生命周期,
attach
、onCreate
、onStart
、onResume
,以及添加到WindowManager中 - 我们在Activity的
attach
函数中新建了PhoneWindow
对象,在PhoneWindow
的setContentView
函数中会调用installDector
来创建DecorView
对象
handleResumeActivity
进入到绘制界面后,会走到handleResumeActivity方法,通过performResumeActivity调用activity的onResume
方法
如下是进入绘制界面的主要步骤
- 第一步通过ViewManager wm = a.getWindowManager(),绑定WindowManager获取其对象。
- 第二步
wm.addView(decor, l)
将decorView传入,而WindowManager的实现类是WindowManagerImpl,而它则是通过WindowManagerGlobal代理实现addView的
WindowManagerGlobal
WindowManagerImpl这种工作模式是典型的桥接模式,将所有的操作委托给WindowManagerGlobal来实现,如下是其实现的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
- 先调用
requestLayout()
,完成第一次layout布局过程,以确保在收到任何系统事件后面重新布局。 - 接着会通过WindowSession最终来完成Window的添加过程。在下面的代码中mWindowSession类型是IWindowSession,它是一个
Binder
对象,真正的实现类是Session,也就是说这其实是一次IPC
过程,远程调用了Session中的addToDisPlay
方法。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
//requestLayout最终会调用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调用performTraversals方法开始对view的测量布局绘制
关键方法有三个
- performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)
- performLayout(lp, desiredWindowWidth, desiredWindowHeight)
- performDraw()
其中当窗口的最新尺寸与ViewRootImpl中的现有尺寸不同时
layoutRequested
会设置会true,这个时候会要求进行预测量即measureHierarchy
measureHierarchy方法
该方法用于测量整个控件树,通常针对悬浮弹框布局,通过预测量来调整弹框的显示大小,达到最好的视觉显示效果(如下图右边)。
一共有两次协商测量机会(两次协商测量仅在其width等于wrap_content下进行,因为match_parent及设置大小均没必要进行协商测量,因为一个是填充满父view,另一个是已设定好的大小),第一次协商使用系统资源预设好的大小配置,若设置后仍不满意则进入到第二次协商即 (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;// 表示测量结果是否可能导致窗口的尺寸发生变化
boolean goodMeasure = false;// 表示了测量是否能满足控件树充分显示内容的要求
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);
//第一次协商是通过使用它最期望的宽度限制进行测量。这一宽度限制定义为一个系统资源
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的测量结果来判断,如果满足条件则认为是测量满意否则进入第二次协商
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
// 第二次协商
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) {
// 最终测量。当控件树对上述两次协商的结果都不满意时,measureHierarchy()放弃所有限制
// 做最终测量。这一次将不再检查控件树是否满意了,因为即便其不满意,measurehierarchy()也没有更多的空间供其使用了
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
//如果测量结果与viewrootimpl的高宽不一致则需要进行调整
windowSizeMayChange = true;
}
}
return windowSizeMayChange;
}
onMeasure()
从上面所说的当我们进入到performTraversals后,会执行performMeasure来进入view的测量即onMeasure
下图是view测量的流程图
注意:FrameLayout(id/content)往下走的view为自定义的layout布局
从上图可知view的根view是DecorView
,DecorView由TitleView
和ContentView
构成,而ContentView就是我们启动activity时setContentView进去的xml布局,也就是说当我们xml中的textview组件需要绘制的时候,必须先从DecorView开始,由外层view一路到最内层view的绘制过程。
MeasureSpec
MeasureSpec类封装了一个View的规格尺寸,包括View的宽和高的信息,但是要注意,MeasureSpec并不是指View的测量宽高,这是不同的,是根据MeasueSpec而测出测量宽高。
在系统中组件的大小模式有三种:
-
精确模式(
MeasureSpec.EXACTLY
)
在这种模式下,尺寸的值是多少,那么这个组件的长或宽就是多少。 -
最大模式(
MeasureSpec.AT_MOST
)
特指当前组件的宽或高大小只能在父组件给出的最大空间里定义,不得超出 -
未指定模式(
MeasureSpec.UNSPECIFIED
)
父控件对子控件不加任何束缚,子元素可以得到任意想要的大小,这种MeasureSpec一般是由父控件自身的特性决定的。比如ScrollView,它的子View可以随意设置大小,无论多高,都能滚动显示,这个时候,size一般就没什么意义。
一个int型整数表示两个东西(大小模式和大小的值),一个int类型我们知道有32位。而模式有三种,要表示三种状态,至少得2位二进制位。于是系统采用了最高的2位表示模式。如图:
最高两位是
00
的时候表示"未指定模式"。即MeasureSpec.UNSPECIFIED
最高两位是
01
的时候表示"'精确模式"。即MeasureSpec.EXACTLY
最高两位是
11
的时候表示"最大模式"。即MeasureSpec.AT_MOST
当然我们并不需要刻意的去记住,因为MeasureSpec类已提供getSize、getMode等方法给我们,但我们应该大概了解即可。
@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();
//根据当前布局的宽高来测量模式为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方法
根据获取的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:
//在这种模式下,尺寸的值是多少,那么这个组件的长或宽就是多少
if (childDimension >= 0) {
//如果子view设置具体值, 则取子view大小,mode设置为 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:
//这个也就是父组件,能够给出的最大的空间,当前组件的长或宽只能在其父组件给出的范围内
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) {
//大小自己设置
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一共有两个方法,根据其布局属性来执行,分别为
- measureVertical(widthMeasureSpec, heightMeasureSpec)
- measureHorizontal(widthMeasureSpec, heightMeasureSpec)
以measureVertical为例
先获取子view数量然后进行遍历,如果heightMode为Match_Parent且高度为0权重大于0 则统计当前Linearlayout总大小,并设置skippedMeasure为true,进入到权重测量(即根据weight来进行二次measure),否则则进入measureChildBeforeLayout(关键方法)该方法点进去其实就是measureChildWithMargins用于把 margin 及 padding 也作为子视图大小的一部分返回,最后进入child.measure计算测量,当子view测量完成后,再由父view设置setMeasuredDimension(widthSizeAndState
,heightSizeAndState
)决定当前容器大小
widthSizeAndState:
主要由两种情况得出maxWidth
- maxWidth由所有子view的宽度+margin叠加
- 当不填充满父view及父的widthMode != MeasureSpec.EXACTLY都满足下
则取alternativeMaxWidth,alternativeMaxWidth及getSuggestedMinimumWidth通过max得出的值maxWidth
,再由resolveSizeAndState(maxWidth
,widthMeasureSpec
,childState
)返回一个合适的大小即widthSizeAndState
其中weightedMaxWidth
:权重 的最大宽
其中alternativeMaxWidth
:改变本地最大宽度
关于alternativeMaxWidth
的获取如下:
if (lp.weight > 0) {
//如果子view设置了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)) {//如果设置了divider,则加上divider高度
mTotalLength += mDividerHeight;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
//如果heightMode为Match_Parent且高度为0权重大于0 则统计当前Linearlayout总大小,并设置skippedMeasure为true,进入到权重测量
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根据传入的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的时候, 所有带权重的子元素都会具有最大子元素的最小尺寸。
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);
// 合并子元素的测量状态
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);
//我们没有限制,所以把所有的加权视图和最大的子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向量算结果加入尺寸太小的标记
//这样其父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);//使用了位运行 返回一个带大小和状态的值
}
getDefaultSize方法
/**
* 作用是返回一个默认的值,如果MeasureSpec没有强制限制的话则使. 用提供的大小.否则在允许范围内可任意指定大小
* 第一个参数size为提供的默认大小,第二个参数为测量的大小
*/
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设置背景,那么就返回View本身的最小宽度mMinWidth
//如果给View设置了背景,那么就取View本身最小宽度mMinWidth和背景的最小宽度的最大值
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
网友评论