前言:相信大家都解决过,ScrollView嵌套ListView只显示一行的问题,其解决方案大概是这样:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
MeasureSpec.makeMeasureSpec方法的作用是重新设置size跟mode,获取自己生成的heightMeasureSpec,在设置给super,达到修改高度的目的
1:那么为什么要设置 MeasureSpec.AT_MOST
查看ScrollView的源码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
super点进去:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
......
}
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
查看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);
}
可以看到ScrollView给ListView设置的高度mode为:MeasureSpec.UNSPECIFIED
查看ListView源码:
@Override
protected void onMeasure(int widthMeasureSpec, int 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;
}
可知:
当heightMode == MeasureSpec.UNSPECIFIED的时候,ListView的高度仅为一个item的高度
当MeasureSpec.AT_MOST的时候,才会继续测量ListView的item的高度
所以我们手动给MeasureSpec.AT_MOST模式。
2:那么另一个问题是size为什么是Integer.MAX_VALUE >> 2呢?
大家可以去看所有的博客,都写的是Integer.MAX_VALUE >> 2
那么Integer.MAX_VALUE >> 2到底是有什么特殊的含义呢?用别的行不行
Integer.MAX_VALUE >> 2到底有没有道理,我认为这么写完全没有道理
为什么呢?
当MeasureSpec.AT_MOST的时候会执行measureHeightOfChildren,我们点进去
final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
int maxHeight, int disallowPartialChildPosition) {
......
for (i = startPosition; i <= endPosition; ++i) {
......
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;
}
return returnedHeight;
}
这段代码告诉我们,我们设置的heightSize,在这里也就是maxHeight起到一个最大高度的作用,一旦child累计的高度超过了maxHeight了,ListView的高度就不会再继续改变了,带来的问题就是列表显示不全!
为了解决这个问题我们想到了给他设置一个比较大的数:Integer.MAX_VALUE
Integer.MAX_VALUE 是int里正整数最大的了,好吧那我们试试:
发现竟然报错了,我们看看报的什么错:
哦,原来这里有限制,最大不能超过1073741823,那么1073741823这个数字又是什么呢?原来这个数字刚好等于Integer.MAX_VALUE右移1位
改成Integer.MAX_VALUE》1试一下,果然可以!!!
那位说了,你说的不对
人家MeasureSpec的用法是一个int值代表两个含义,你看
public static final int AT_MOST = 2 << MODE_SHIFT;//MODE_SHIFT=30
2左移30位用二进制表示应该是:10000000000000000000000000000000
而前两位的10用以表达mode
后30位则是给size准备的,所以Integer.MAX_VALUE>>2的目的是给左边的10让出位置
但是
我想说的是,别忘了就算是Integer.MAX_VALUE也占不满32位,第一位是用来表示正数的0
/**
* A constant holding the maximum value an {@code int} can
* have, 2^31-1.
*/
@Native public static final int MAX_VALUE = 0x7fffffff;
2^31-1换算成二进制为:
01111111111111111111111111111111
所以说第一位我已经让出,我再>>1,再让出一位完全够用,为什么我要>>2让出2位呢?
当然了Integer.MAX_VALUE>>2也不会有什么问题,毕竟就算Integer.MAX_VALUE>>2也足够大,足够我们使用了
但是这样不免让我们产生误解:Integer.MAX_VALUE>>2为最优写法
3:因此我说Integer.MAX_VALUE>>2没有道理
最优写法,应该是Integer.MAX_VALUE>>1,因为这样写会时刻提醒我们,这样写的原因所在!好了这就是本篇的全部内容,欢迎批评指正!