一般状况下咱们在使用ScrollView嵌套ListView的时候,当出现问题的时候,相信绝大部分人都是在网上直接找别人的解决方案,都没有关心为何会出现这种问题,为何这样解决。bash
一般状况下ScrollView嵌套ListView会出现只会显示ListView的第一个item的状况,而网上大部分的解决方式是:ide
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
复制代码
那么为何会出现这样的状况呢?为何这样解决呢? 咱们来一块儿分析分析:ui
出现问题的时候咱们会发现只显示了ListView的第一个item,而其余item都是可滑动出来的,猜想是因为onMeasure的问题致使的。spa
因此咱们先来看ScrollView的源码:code
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.UNSPECIFIED) {
return;
}
if (getChildCount() > 0) {
final View child = getChildAt(0);
final int height = getMeasuredHeight();
if (child.getMeasuredHeight() < height) {
final int widthPadding;
final int heightPadding;
final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
if (targetSdkVersion >= VERSION_CODES.M) {
widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
} else {
widthPadding = mPaddingLeft + mPaddingRight;
heightPadding = mPaddingTop + mPaddingBottom;
}
final int childWidthMeasureSpec = getChildMeasureSpec(
widthMeasureSpec, widthPadding, lp.width);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height - heightPadding, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
复制代码
经过源码能够看到ScrollView在测量的时候只拿了子View中的第一个,也就是ListView的高度。get
@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);
int childWidth = 0;
int childHeight = 0;
int childState = 0;
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
|| heightMode == MeasureSpec.UNSPECIFIED)) {
final View child = obtainView(0, mIsScrap);
// Lay out child directly against the parent measure spec so that
// we can obtain exected minimum width and height.
measureScrapChild(child, 0, widthMeasureSpec, heightSize);
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
mRecycler.addScrapView(child, 0);
}
}
// 代码省略
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);
mWidthMeasureSpec = widthMeasureSpec;
}
复制代码
经过ListView的onMeasure方法,咱们看到:源码
MeasureSpec.UNSPECIFIED
,因此ListView的高度就是第一个item的高度加上相关的参数因此这就得出了为何ScrollView嵌套ListView会出现显示不全的问题。string
经过源码咱们知道了问题所在的缘由,那么如何解决呢?it
这个时候就须要重写ListView,并覆盖onMeasure方法了io
首先咱们知道系统的mode是MeasureSpec.UNSPECIFIED
,而咱们又发现:
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);
复制代码
ListView当mode是MeasureSpec.AT_MOST
的时候会依次累计计算每一个item的高度,因此咱们须要设置mode为MeasureSpec.AT_MOST
。
那么为何要设置高度为Integer.MAX_VALUE >> 2
final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
int maxHeight, int disallowPartialChildPosition) {
returnedHeight += child.getMeasuredHeight();
if (returnedHeight >= maxHeight) {
// We went over, figure out which height to return. If returnedHeight > maxHeight,
// then the i'th position did not fit completely. return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1) && (i > disallowPartialChildPosition) // We've past the min pos
&& (prevHeightWithoutPartialChild > 0) // We have a prev height
&& (returnedHeight != maxHeight) // i'th child did not fit completely ? prevHeightWithoutPartialChild : maxHeight; } } 复制代码
在计算高度的时候ListView会累加每一个item的高度,并最终和maxHeight比较,也就是Integer.MAX_VALUE >> 2
,当小于maxHeight的时候,就直接返回ListView的真实高度,这个时候问题也就解决了。