ListView原理分析

先上张图

ListView 的视图循环使用机制主要实现自内部的 RecyclerBin。


ListView继承关系

通过 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,然后设置。

未完待续

赏杯咖啡 🍵 Donate