彻底理解其onMeasure方法,主要理解ListView的高度是怎么得来的
结论
1.先上自定义ListView中onMeasure方法的代码,再说我的结论吧!
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
结论:当ListView的(各个列表项的高度之和 + 各项间的间隔之和)小于Integer.MAX_VALUE >> 2时,则ListView的高度就会设置为(各列表项的高度 + 各项的间隔)
论证
分析makeMeasureSpec方法
先分析
int spec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
其他博客应该也讲得很清楚了, 我这里就只根据这里的参数简要地讲一讲。
先看一下makeMeasureSpec它的源码
//大小(size)+模式(mode),前两位是表示模式, 后30位是表示大小。
//都是用二进制进行运算。
public static int makeMeasureSpec(int size,int mode) {
//判断是否是17版本或者更低的版本,仅仅只是为了适配低版本,因为低版本有点bug。
//但这并不会影响最终得到的结果。两个分支得到的结果都会是一样的。
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
Integer.MAX_VALUE >> 2这个参数表示大小(size)
因为:
最大的整数往右移动两位,得到001…(后面全是1,共有30个1),正好它就可以表示大小(size )。因为最前面的两个0可有可无,对原本的数据不会产生影响。
而MeasureSpec.AT_MOST正好表示模式(mode)。所以正好对应了makeMeasureSpec的两个参数。
可能到了这里,会有很多人不理解,自定义ListView中其onMeasure方法,第二个参数为什么要为 MeasureSpec.AT_MOST,当为MeasureSpec.EXACTLY,MeasureSpec.UNSPECIFIED时,为什么就得不到ListView正确的高度呢?听我下面慢慢道来。
分析super.onMeasure方法
分析super.onMeasure(widthMeasureSpec, expandSpec);
这一行代码。很多人分析自定义ListView的时候都没有分析这行代码,都只分析了MeasureSpec方法,反正我认为这恰恰是理解整个onMeasure方法的重点。
源码1
@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 (widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = mListPadding.left + mListPadding.right + childWidth +
getVerticalScrollbarWidth();
} else {
widthSize |= (childState & MEASURED_STATE_MASK);
}
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;
}
源码摘要1
着重观察如下这几行代码
(1)
//根据自定义ListView的onMeasure方法,得到高度的模式
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
(2)
//得到高度的大小,根据自定义ListView的onMeasure方法
//heightSize = Integer.MAX_VALUE >> 2
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
(3)
if (heightMode == MeasureSpec.AT_MOST) {
//前往这个方法内部
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
源码2
所以接下来跳转到measureHeightOfChildren方法中。在这个方法里面,就会得到真正的ListView的高度,也是主要分析部分代码
final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
int maxHeight, int disallowPartialChildPosition) {
final ListAdapter adapter = mAdapter;
if (adapter == null) {
return mListPadding.top + mListPadding.bottom;
}
// Include the padding of the list
int returnedHeight = mListPadding.top + mListPadding.bottom;
final int dividerHeight = mDividerHeight;
// The previous height value that was less than maxHeight and contained
// no partial children
int prevHeightWithoutPartialChild = 0;
int i;
View child;
// mItemCount - 1 since endPosition parameter is inclusive
endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
final AbsListView.RecycleBin recycleBin = mRecycler;
final boolean recyle = recycleOnMeasure();
final boolean[] isScrap = mIsScrap;
for (i = startPosition; i <= endPosition; ++i) {
child = obtainView(i, isScrap);
measureScrapChild(child, i, widthMeasureSpec, maxHeight);
if (i > 0) {
// Count the divider for all but one child
returnedHeight += dividerHeight;
}
// Recycle the view before we possibly return from the method
if (recyle && recycleBin.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
recycleBin.addScrapView(child, -1);
}
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;
}
if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
prevHeightWithoutPartialChild = returnedHeight;
}
}
// At this point, we went through the range of children, and they each
// completely fit, so return the returnedHeight
return returnedHeight;
}
源码摘要2
着重注意这几个变量:returnedHeight与dividerHeight。
最终返回的结果是returnedHeight这个变量。
摘录如下源码:
(1)
//这个返回将在列表中的每个项之间绘制的divider的高度。
public int getDividerHeight() {
return mDividerHeight;
}
(2)
//得到ListView的上下padding之和。
int returnedHeight = mListPadding.top + mListPadding.bottom;
//得到ListView各项之间的间距
final int dividerHeight = mDividerHeight;
(3)
//这里的endPosition = adapter.getCount() - 1
for (i = startPosition; i <= endPosition; ++i) {
child = obtainView(i, isScrap);
//这也是一个ListView中方法,就是得到ListView中的每一个子项(child)。
measureScrapChild(child, i, widthMeasureSpec, maxHeight);
//为什么要i>0
//例如:五项只有四个间隔。即第一项没有间隔。
if (i > 0) {
returnedHeight += dividerHeight;
}
//把每一子项的高度加到returnedHeight上
returnedHeight += child.getMeasuredHeight();
//只说一句, maxHeight = Integer.MAX_VALUE >> 2,大不大,自己应该也知道吧!所以这里不会执行
if (returnedHeight >= maxHeight) {
.......(略)
}
}
总结
总结:到这里为止,你们知道为什么onMeasure方法要这么写了吧!!!