ListView 源码分析

ListView 作为 Android 开发中最常用的组件之一,最近刚好有时间,看了源码,记录一下。

ListView 的继承结构

一. 需要解决的问题

. ListView 本质上也是一个 ViewGroup, 它的 onMeasure(), onLayout() 过程, onDraw() 过程?

. 它是如何配合 Adaper 实现数据的展示的?

. ListView 是如何构造 ItemView 的循环使用,而不会造成 OOM 的?

二. 重要的类

1. RecycleBin

RecycleBin 是分析 ListView 重要的类,它是 AbsListView 的一个内部类,它的几个方法,承担着 ListView 对 ItemView 循环使用的功能。

主要的成员变量:
. View[] mActiveViews; 以数据的形式存储在屏幕上展示的 View.
. ArrayList[] mScropViews; 无序的存储回收的 View.

主要的方法:
. 填充 ActiveView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 void fillActiveViews(int childCount, int firstActivePosition) {
if (mActiveViews.length < childCount) {
mActiveViews = new View[childCount];
}
mFirstActivePosition = firstActivePosition;

//noinspection MismatchedReadAndWriteOfArray
final View[] activeViews = mActiveViews;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
// Don't put header or footer views into the scrap heap
if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
// Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
// However, we will NOT place them into scrap views.
activeViews[i] = child;
// Remember the position so that setupChild() doesn't reset state.
lp.scrappedFromPosition = firstActivePosition + i;
}
}
}

. 根据指定的位置,从 mActiveViews 中获取相应的 View。 如果找到了,返回,并同时从 mActiveViews 中移除。

1
2
3
4
5
6
7
8
9
10
View getActiveView(int position) {
int index = position - mFirstActivePosition;
final View[] activeViews = mActiveViews;
if (index >=0 && index < activeViews.length) {
final View match = activeViews[index];
activeViews[index] = null;
return match;
}
return null;
}

.根据指定的位置,从 mScrapViews 中获取相应的 scropView

1
2
3
4
5
6
7
8
9
10
11
12
View getScrapView(int position) {
final int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap < 0) {
return null;
}
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} else if (whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
return null;
}

. 把 View 放入回收的 mScrapViews 中缓存起来

1
2
3
4
5
6
7
8
9
void addScrapView(View scrap, int position) {
...
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
...
}

2. HeaderViewListAdapter

有 header view 时,进行包装的 ListAdapter.

3. DataSetObservable

通知数据产生了变化,(这里使用了观察者模式,待分析)

三. OnLayout 过程

是 AbsListView#onLayout(…) 方法
调用 layoutChildren(), 抽象方法,由子类实现

ListView#layoutChildren()

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
44
45
46
47
48
49
50
51
52
53
54
55
ListView#layoutChildren() {
...
final int childCount = getChildCount();
...
boolean dataChanged = mDataChanged;
if (dataChanged) {
handleDataChanged();
}
...
// Pull all children into the RecycleBin.
// These views will be reused if possible
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;
}

// Flush any cached views that did not get reused above
recycleBin.scrapActiveViews();
}

dataChanged 由 mDataChanged 赋值。mDataChanged 是 AdapterView 的成员变量,初始值是 false, 在 Adapter#notifyDataSetChanged() 是才会变成 true, 这个在后面有介绍。当第一次 Layout 的时候时,ListView 还没有 ChildView, 所以 childCount 是 0.

由于 childCount 为 0, recycleBin.fillActiveViews 里面不起作用,跳出循环了。

mStackFromBottom 是否从底部开始填充。 fillFromTop 是从 ListView 的顶部开始填充,fillUp 是从 ListView 的底部开始填充。
它们只是方向不同而已。

fillFromTop 又会调动 fillDown(), fillDown 方法里面会循环,直到所有 child 的高度高于平面的高度,说明已经填满屏幕了,则跳出循环。

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;
}
// 循环,直到所有 Child 的高度超过平面的高度
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;
}

makaAndAaddView 获取 View, 并将它存放入 child 的 list 中

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
 /**
* Obtains the view and adds it to our list of children. The view can be
* made fresh, converted from an unused view, or used as is if it was in
* the recycle bin.
* @return the view that was added
*/
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;
}

当第一次 layout 时, mRecycler.getActiveView 返回的是空的。
最终会是调用 obtainView 方法。obtainView 方法是在 ListView 的父类 AbsListView 的方法

AbsListView#obtainView(…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Gets a view and have it show the data associated with the specified
* position. This is called when we have already discovered that the view
* is not available for reuse in the recycle bin. The only choices left are
* converting an old view or making a new one.
* @return A view displaying the data associated with the specified position
*/
View obtainView(int position, boolean[] outMetadata) {
...
final View scrapView = mRecycler.getScrapView(position);
// 重点 调用 Adatper#getView(...) 方法
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {
if (child != scrapView) {
mRecycler.addScrapView(scrapView, position);
}
}
...
setItemViewLayoutParams(child, position);
...

return child;
}

会调用从 RecyclerBin#getScrapView(…) 尝试从缓存中取出废弃的 View, 但是第一次 layout 的时候,返回来是空的。

重点是 Adapter.getView(…) 方法,当我们继承 BaseAdapter 的时候,要重写它的 getView 方法。
下面我们看看例子

1
2
3
4
5
6
7
8
9
10
11
12
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null){
convertView = LayoutInflater.from(mContext).inflate(R.layout.attention_item_view_layout, parent, false);
mViewHolder = new AttentionViewHolder(convertView);
convertView.setTag(mViewHolder);
} else {
mViewHolder = (AttentionViewHolder) convertView.getTag();
}

...
return convertView;
}

我们看到 converView 就是从缓存中取出的 scrapView, 当 scrapView 是空的时候,所以我们要创建一个 View。
另外参数 ViewGroup parent, 其实就是 ListView.

我们回到 makeAndAddView 方法中, 在 obtainView 中获取 child, 然后调用 setupChild 方法,将 child 添加进 ListView 中。

ListView#setupChild(…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
boolean selected, boolean isAttachedToWindow) {
...

AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
if (p == null) {
p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
}

...

if ((isAttachedToWindow && !p.forceAdd) || (p.recycledHeaderFooter
&& p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
attachViewToParent(child, flowDown ? -1 : 0, p);
...
} else {
...
addViewInLayout(child, flowDown ? -1 : 0, p, true);
...
}

...
}

我们可以看到最终是通过 attachViewToParent 或者 addViewInLayout 方法,将 child 添加进 ListView 中。

当第二次 layout 的时候,就是调用 RecyclerBin 的一些方法时有一些改变,但是整个流程的变化不大。

四. draw 绘制过程

ListView 的绘制在 ListView#dispatchDraw(…) 中。这个方法比较简单,就是绘制分割线和分发 childView 自己的绘制而已。

五. 滑动的过程

滑动开始是 AbsListView#onTouchEvent(…) 然后会调用 AbsListView#onTouchMove()

1
2
3
4
5
6
7
8
9
10
11
12
private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
...
final int y = (int) ev.getY(pointerIndex);

switch (mTouchMode) {
...
case TOUCH_MODE_SCROLL:
case TOUCH_MODE_OVERSCROLL:
scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
break;
}
}

AbsListView#scrollIfNeeded(…)

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
private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
...
final int deltaY = rawDeltaY;
int incrementalDeltaY =
mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
int lastYCorrection = 0;

if (mTouchMode == TOUCH_MODE_SCROLL) {
...
if (y != mLastY) {
...
boolean atEdge = false;
if (incrementalDeltaY != 0) {
atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
}

} else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
if (y != mLastY) {
...
if (incrementalDeltaY != 0) {
...
trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
...
}
}
}

scrllIfNeeded(…) 方法又会调用 trackMotionScroll 方法

AbsListView#trackMotionScroll

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
...
if (down) {
int top = -incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
top += listPadding.top;
}
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getBottom() >= top) {
break;
} else {
count++;
int position = firstPosition + i;
if (position >= headerViewsCount && position < footerViewsStart) {
// The view will be rebound to new data, clear any
// system-managed transient state.
child.clearAccessibilityFocus();
mRecycler.addScrapView(child, position);
}
}
}
} else {
int bottom = getHeight() - incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
bottom -= listPadding.bottom;
}
for (int i = childCount - 1; i >= 0; i--) {
final View child = getChildAt(i);
if (child.getTop() <= bottom) {
break;
} else {
start = i;
count++;
int position = firstPosition + i;
if (position >= headerViewsCount && position < footerViewsStart) {
// The view will be rebound to new data, clear any
// system-managed transient state.
child.clearAccessibilityFocus();
mRecycler.addScrapView(child, position);
}
}
}
}

...

if (count > 0) {
detachViewsFromParent(start, count);
mRecycler.removeSkippedScrap();
}
final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
fillGap(down);
}

mRecycler.fullyDetachScrapViews();
...
return false;
}

在 trackMotionScroll 方法里面,前段,我们看到,无论是向上滑,还是向下滑,只要超出屏幕外,就会调用 RecyclerBin#addScrapView(…),进行回收。同时也会调用 fillGap 进行填充。

fillGap 是抽象方法,由子类实现
ListView#fillGap(…)

1
2
3
4
5
6
7
8
9
10
11
12
void fillGap(boolean down) {
final int count = getChildCount();
if (down) {
...
fillDown(mFirstPosition + count, startOffset);
...
} else {
...
fillUp(mFirstPosition - 1, startOffset);
...
}
}

在 fillGrap 里面调用了 fillDown 或者 fillUp, 对 ListView 填充 childView, 这样滑动的时候,形成循环,回到前面的分析。

六. 数据变化

当数据变化的时候,我们会调用 BaseAdapter#notifyDataSetChanged(), 让 ListView 的布局也跟着更新。
Adapter 更新数据是个典型的观察者模式。

那我们看看 Adapter 中的观察者模式

从上图我们可以知道 DataSetObserver 是抽象的观察者,AdapterView$AdapterDataSetObserver 是具体的观察者。Observale 是抽象的被观察者,DataSetObservable 是具体的被观察者。

当我们调用 BaseAdapter#notifyDataSetChanged()时,就会调动 DataSetObservable#notifyChanged(), 在这个方面里面通过 for 循环通知所有的观察者,最终调动 AdapterView$AdapterDataSetObserver#onChanged() 方法。
在 AdapterView$AdapterDataSetObserver#onChanged() 方法中,会调动 reuestLayout 要求重新布局。布局的过程见上面的分析。

七. 参考资料

  1. Android ListView工作原理完全解析,带你从源码的角度彻底理解http://blog.csdn.net/guolin_blog/article/details/44996879
yxhuang wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客