问题:
当把ListView嵌套在ScrollView中时,ListView只显示一行。
自定义一个ListView,重写onMeasure方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
开始分析:
首先,ListView的高度由什么决定?
答:由自身和父View共同决定。
如果不清楚View的测量(Measure)流程的,请自行用百度去google一下。
大概流程是父View根据自身特性对子View进行测量,然后调用子view的measure()方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec)
measure()方法由父View调用,其中的widthMeasureSpec和heightMeasureSpec是父View根据自身特性给予子View的测量“建议”,包括大小和测量模式,子view根据父view给的建议再对自己进行测量。
这是View类的measure()方法,可以看到定义成了final类型,不可以被子类重写。
原因是measure()方法只是进行一些通用的处理,并不真正进行测量,而是会调用onMeasure()方法来进行测量,所以子类必须重写onMeasure()方法来实现自己的测量逻辑,并且必须在测量完毕时调用 setMeasuredDimension()确定最终结果(这也是为什么一般我们自定义View是都需要重写onMeasure()方法)。
好了,进入正题。根据上面的流程追踪一下ListView在ScrollView中的测量流程。
首先,ScrollView中对子View的测量:
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);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到,ScrollView测量子view高度的方式是:
不管你三七二十一,给子view的“建议”就是,size为0,mode为MeasureSpec.UNSPECIFIED(UNSPECIFIED表示:未指定,父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小),因为ScrollView是可滚动的,所以给子view的建议是你想多高就多高是符合逻辑的。
接下来,measure()方法只是进行一些通用处理,我们直接跳到ListView的onMeasure()方法,看看ListView得到父view给它的"建议"后,它是怎样测量自己的:
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);
}
可以看到:
当父view的建议为MeasureSpec.UNSPECIFIED时,ListView测量自己的高度为child(这里的child为Listview的第一个item,即第一行)的高度加上一些上下间隔。(我也不知道ListView为什么这么"谦虚",父view说了你要多高都行,然后它只是想要有第一行那么高就行了,不知道这样设计是为了迎合什么场景,知道的请指教一下啊)
当父view的建议为MeasureSpec.At_MOST时,ListView会调用measureHeightOfChildren()方法来测量自己的高度,measureHeightOfChildren()方法会累加所有child(item)的高度和dividerHeight(item之间的间隔view高度)。
回到网上流传方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
补充:
以上所述同样适用于GridView嵌套在ScrollView中,GridView嵌套在ListView中,ListView嵌套在GridView中。
这些能滚动的View就是任性。
第二篇了,看的人少的可怜。好吧,其实就我自己一个人看,不过就当做笔记了。看到觉得有帮助的请点赞啊,觉得有错的请评论指正啊。