这是以前常见的一个问题,现在可能很多人都不用ListView了。但是之前一直不是很清楚是什么原因。
为什么ListView只显示一个Item的高度
首先看下正常情况下ListView的高度是怎么算的。看下ListView的onMeasure()方法如何计算高的:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
.......
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;
}
正常情况下,高的Mode是不会是MeasureSpec.UNSPECIFIED,所以heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);不用看猜也能猜到是所以Item高的和,但是现在不正常了 我么看下MeasureSpec.UNSPECIFIED时,是如何计算高的
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
这个childHeight就是第一个Item的高度:
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();
所以当ListView的高的Mode被设置为MeasureSpec.UNSPECIFIED时,它的高度就是第一个Item的高度加上一些padding值等了。这个跟我们看到ScrollView嵌套ListView看到的现象是一样。所以我们可以大胆猜测,ScrollView篡改了ListView高的Mode。 是这样吗?
事实就是这样,ScrollView重写了measureChild和measureChildWithMargins方法,在测量自己子View的时候会将高的Mode改成UNSPECIFIED。
@Override
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
ViewGroup.LayoutParams lp = child.getLayoutParams();
int childWidthMeasureSpec;
int childHeightMeasureSpec;
childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
+ mPaddingRight, lp.width);
final int verticalPadding = mPaddingTop + mPaddingBottom;
childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
@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高的Mode已经被改了
childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
MeasureSpec.UNSPECIFIED);
这应该就是这个现象最本质,最正确的解释。网上有各种解释,以前也没怎么看懂。现在撸源码顺便过了下,发现源码真是个好东西。
如何解决这个问题呢
其实解决这个问题的方式有很多,网上一搜各种都有。但是知道产生这个现象的原因后,我觉得最简单也是最好的解决方式就是将ListView的Mode改回去就行了,改成AT_MOST。
/**
* 解决ScrollView嵌套后显示的高不正确问题
* @author frc on 2018/3/25.
*/
public class FrcListView extends ListView {
public FrcListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightSpec =MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightSpec);
就是这么简单。