ListView这个类中的onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
..........
这里有两个函数,MeasureSpec.getMode(heightMeasureSpec)是用来取调用者设置大小的测量模式,其结果值为
/**
* 这个值可以理解为无限大的意思,平时很少用,一般会给ScrollView或者ListView这样的控件机算
* 大小使用
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* 这个值就是在布局中设置大小给出了math_parent,fill_parent,或者一个固定大小的值 比如 50dp
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* 这个值是在布局中给出了 pwrap_content
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
还有一个函数是MeasureSpec.getSize(heightMeasureSpec),这是用来获取准确控件大小的值,这个很好理解。
ScrollView是继承自FrameLayout,先看看FrameLayout的onMeasure方法
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//这里很好理解,就是子空间不是GONE就计算一下它的大小
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) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
而ScrollView也重写了measureChildWithMargins()这个方法
@Override
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 usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
heightUsed;
//重点其实在这里,这里是设置子控件高度的具体值,测量模式给出了无限大
final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
那好,那我们再来看ListView的onMeasure当测量模式给出无限大的情况下,怎么计算高度
if (widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = mListPadding.left + mListPadding.right + childWidth +
getVerticalScrollbarWidth();
} else {
widthSize |= (childState & MEASURED_STATE_MASK);
}
//高度测量模式为无限大,高度的计算方法
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
所以,当ScrollView嵌套ListView在不做任何处理的情况下,能看到的仅仅是大约一个多item的高度。
解决方案其实有很多个版本,网上能查到最多的版本是继承一个ListView,然后重写onMeasure方法
@Override
/**
* 重写该方法,达到使ListView适应ScrollView的效果
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
但是很多同学其实不知道为什么这样去解决,首先我们要看代码,怎样才能让这种情况下的ListView有一个可算的高度
if (widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = mListPadding.left + mListPadding.right + childWidth +
getVerticalScrollbarWidth();
} else {
widthSize |= (childState & MEASURED_STATE_MASK);
}
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
//一定是要进入到这个方法,heightSize才会有一个具体的值计算出来
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible
position, not 0
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION,
heightSize, -1);
}
setMeasuredDimension(widthSize, heightSize);
所以测量模式确定了,肯定是AT_MOST
//长度30
private static final int MODE_SHIFT = 30;
.................
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
android中把测量出的int做了处理,int类型的长度是32位,把前两位作为标记位,标记了测量模式,如EXACTLY、AT_MOST,把最后30位作为测量的具体高度或者是宽度。所以我们在调用MeasureSpec.makeMeasureSpec(size,mode)方法时,传入的size参数要把Integer.MAX_VALUE右移2位,因为前两位会被认为是标志,而不是值。
//这里是取了前两位的测量模式
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//这里是取了后30位的测量值
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
其实,ScrollView也不知道子控件的实际大小到底有多大,所以这里我们尽量给出一个相对较大的值。