先说结论:默认情况下,当父布局为 wrap_content 或者 match_parent 时,无论子 view(view 或者 viewgroup) 是wrap_content 还是 match_parent,最终的效果都是 match_parent。也就是 子 view 会占据父布局中剩下的所有空间。
父布局剩余的空间为 size - padding
why?
我们看看ViewGroup 是如何测量子View的。
android.view.ViewGroup#measureChildren
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {//依次遍历,调用子 view 的测量方法
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
android.view.ViewGroup#measureChild
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);
}
android.view.ViewGroup#getChildMeasureSpec
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;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
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.
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.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
//代码省略……
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
getChildMeasureSpec 方法中,无论父布局的测量模式是 EXACTLY 还是 AT_MOST, resultSize 取值都为 size。测量模式要么是 AT_MOST,要么是 EXACTLY。那为什么说最终效果都是一样的呢?回答这个问题,需要看看子 view 是如何处理接收到的 resultSize 和 resultMode 的。
~注:size = Math.max(0, specSize - padding);
即,原有的空间减去 padding~。
View 的onMeasure 方法默认会调用 getDefaultSize 方法来获取 view 的大小。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
View#getDefaultSize
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://结果尺寸为 exactly
result = specSize;//大小为父布局所给的大小
break;
}
return result;
}
从 view 的getDefaultSize方法可以看到,它对AT_MOST模式以及EXACTLY模式的处理是相同的。所以,也就说明了结论是成立的。
附:最顶层的 View 大小是由谁确定的呢?
「最上层的 root_view」的大小是 WindowManager 赋予的。
ViewRootImpl 中的 view 实际上是 DecorView
WindowManager.LayoutParams#LayoutParams()
,超类为 ViewGroup.LayoutParams
也就是 DecorView 的大小是充满父布局,也就是充满 Window。
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
开发者也可以通过 Window#setAttributes 方法设置窗口的大小。
public void setAttributes(WindowManager.LayoutParams a) {
mWindowAttributes.copyFrom(a);
dispatchWindowAttributesChanged(mWindowAttributes);
}
由于本人水平有限,可能出于误解或者笔误难免出错,如果发现有问题或者对文中内容存在疑问欢迎在下面评论区告诉我,谢谢!
网友评论