ListView 的视图循环使用机制主要实现自内部的 RecyclerBin。
通过 ListView 的继承关系,可以看出它继承自 AbsListView,是个 ViewGroup。ViewGroup 的绘制流程无非就是 measure -> layout -> draw。
ListView 中实现了自己的 onMeasure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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) {
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
|
没什么特别的,把各种尺寸和子View的measure求和。然后在 ListView 中没有发现 onLayout,进入 AbsListView 查看。
1
2
3
4
5
6
7
8
9
|
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mInLayout = true;
......
layoutChildren();
......
mInLayout = false;
}
|
会调用 layoutChildren,ListView 中实现了这个方法。直接到核心步骤。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
......
final int firstPosition = mFirstPosition;
final RecycleBin recycleBin = mRecycler;
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
} else {
recycleBin.fillActiveViews(childCount, firstPosition);
}
// Clear out old views
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();
switch (mLayoutMode) {
......
default:
if (childCount == 0) {
if (!mStackFromBottom) {
final int position = lookForSelectablePosition(0, true);
setSelectedPositionInt(position);
sel = fillFromTop(childrenTop);
} else {
final int position = lookForSelectablePosition(mItemCount - 1, false);
setSelectedPositionInt(position);
sel = fillUp(mItemCount - 1, childrenBottom);
}
} else {
if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
sel = fillSpecific(mSelectedPosition,
oldSel == null ? childrenTop : oldSel.getTop());
} else if (mFirstPosition < mItemCount) {
sel = fillSpecific(mFirstPosition,
oldFirst == null ? childrenTop : oldFirst.getTop());
} else {
sel = fillSpecific(0, childrenTop);
}
}
break;
}
recycleBin.scrapActiveViews();
|
- childCount
- mStackFromBottom: AbsListView 中的属性,默认为 false,表示列表将会从上到下的填充,反之从下到上填充
不单独调用 setStackFromBottom(true)的话, ListView 就会从上到下开始填充,调用 fillFromTop
,接着调用 fillDown
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
private View fillDown(int pos, int nextTop) {
View selectedView = null;
int end = (mBottom - mTop);
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end -= mListPadding.bottom;
}
while (nextTop < end && pos < mItemCount) {
// is this the selected item?
boolean selected = pos == mSelectedPosition;
View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
nextTop = child.getBottom() + mDividerHeight;
if (selected) {
selectedView = child;
}
pos++;
}
setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
return selectedView;
}
|
fillDown
里循环进行了 子View 的填充。
- end: ListView 的高度
- nextTop: 表示单个 子View的顶部 相对于 ListView 的位置高度
- pos: 进入 ListView 的第一个位置,默认为 0,没填充一个子 View 就 +1
循环条件 nextTop < end && pos < mItemCount
:ListView 的高度要大于下一个子 View 的顶部位置 && pos 要小于 adapter 里返回的 ItemCount。这样就确保 ListView 只会填充一定数量,它自身高度能够显示完全的子 View。
进入 makeAndAddView
方法中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
if (!mDataChanged) {
// Try to use an existing view for this position.
final View activeView = mRecycler.getActiveView(position);
if (activeView != null) {
// Found it. We're reusing an existing child, so it just needs
// to be positioned like a scrap view.
setupChild(activeView, position, y, flow, childrenLeft, selected, true);
return activeView;
}
}
// Make a new view for this position, or convert an unused view if
// possible.
final View child = obtainView(position, mIsScrap);
// This needs to be positioned and measured.
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
|
如果数据没有改变,会先从 recycler 中获取与指定位置对应的视图,如果找到了就直接设置然后返回;没找到就会调用 AbsListView 的 obtainView
获取一个 View,然后设置。
未完待续