徹底理解其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方法要這麼寫了吧!!!