相信每个android开发者都用到过ListView和Scrollview,当我们在使用Scrollview嵌套ListView的时候会出现ListView无法全部显示的问题,那么是什么引起的呢我们一起打开源码看下。首先我们看下ScrollView是继承自FrameLayout,FrameLayout的onMeasure方法有如下代码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
<!--拿到它的子View然后循环去测量-->
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
<!--调用父类的方法进行测量,重要方法因为Scrollview重写了这个方法所以出现的问题-->
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);
}
}
}
}
}
复制代码
代码比较简单就是循环查找自己的子View如果子View不是GONE的话就调用ViewGroup的measureChildWithMargins方法,然后我们在看下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;
<!--childHeightMeasureSpec的模式设置成了MeasureSpec.UNSPECIFIED-->
final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
ScrollView重新measureChildWithMargins方法并且对他的childHeightMeasureSpec进行设置值,设置他的高度的测量模式heightMode为MeasureSpec.UNSPECIFIED设置完成以后调用了child.measure(childWidthMeasureSpec,childHeightMeasureSpec);方法,紧接着也就是进入了ListView的onMeasure方法我删除了一些没用用的方便
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
<!--获得高度的模式-->
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childHeight = 0
<!--如果item数量大于0并且有个模式为--UNSPECIFIED执行此方法>
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
|| heightMode == MeasureSpec.UNSPECIFIED)) {
final View child = obtainView(0, mIsScrap);
childHeight = child.getMeasuredHeight();
<!--如果高度的模式为UNSPECIFIED执行,我们通过刚刚查看ScrollView的源码发现是这种模式所以此方法为true-->
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
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);
}
复制代码
以上是ListView里面的关于测试模式为MeasureSpec.UNSPECIFIED的代码,我们不难从代码中看出来,第一个判断意思是如果他的Item的总数大于0并且它的高或者宽有一个模式为MeasureSpec.UNSPECIFIED就会执行这个方法,那么正常情况下系统这个判断就是为true,也就是拿到它的一个item的高度childHeight = child.getMeasuredHeight();紧接着它判断它的高度的模式是否为MeasureSpec.UNSPECIFIED显然也是true所以他就是让它的整个的高度等于childHeight 最后完成setMeasuredDimension(widthSize,heightSize);也就是造成了我们的ListView无法正常显示,那么我们现在已经知道问题出现的原因了那么怎么解决问题也就变得非常简单了,我们需要更改他的模式为MeasureSpec.AT_MOST让它执行,所以我们只需要继承ListView然后重新他的onMeasure方法就可以了如下
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
复制代码