listview的源码在此之前看过也不止一次了,但是现在想想好像也就停留在看过的样子,除此之外就没啥印象了。
想想之前就是在瞎看,毕竟ListView加上AbsListview有一万多行代码了,瞎看的话确实啥都看不出来啥的。所以这次看的话我就带着几次问题来看就行了,毕竟把1万多行代码看完对很多人来说是不现实的。
今天就带着下面几个问题,再次看一下Listview源码吧:
- Listview的适配器模式
- setAdapter是如何将数据设置到Listview中去的
- Adapter刷新
- Adapter缓存机制RecycleBin
- ListView优化
在此之前先来看一下ListView的继承关系:
一、Listview的适配器模式
适配器模式的定义:
将一个类的接口变成客户端所需要的另一个接口,从而使原本因为接口不匹配而无法在一起工作的两个类能够在一起工作
看Listview源码之前去看了下适配器模式:适配器模式
在ListView中我们需要继承BaseAdapter实现自己的适配器,因为ListView的Item类型可能是千奇百怪的,所以通过BaseAdapter中的getView方法,将每一个Item的View返回。
二、Listview缓存机制RecycleBin
在讲其他地方之前先来了解一下负责ListView的缓存的RecycleBin,因为其他的地方都会用到。
RecycleBin是AbsListView的内部类,主要源码如下:
class RecycleBin {
private RecyclerListener mRecyclerListener;
//The position of the first view stored in mActiveViews.
private int mFirstActivePosition;
private View[] mActiveViews = new View[0];
//Unsorted views that can be used by the adapter as a convert view.
private ArrayList<View>[] mScrapViews;
private int mViewTypeCount;
private ArrayList<View> mCurrentScrap;
private ArrayList<View> mSkippedScrap;
private SparseArray<View> mTransientStateViews;
private LongSparseArray<View> mTransientStateViewsById;
public void setViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
}
//noinspection unchecked
ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
for (int i = 0; i < viewTypeCount; i++) {
scrapViews[i] = new ArrayList<View>();
}
mViewTypeCount = viewTypeCount;
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
}
public boolean shouldRecycleViewType(int viewType) {
return viewType >= 0;
}
/**
* Clears the scrap heap.
*/
void clear() {
if (mViewTypeCount == 1) {
final ArrayList<View> scrap = mCurrentScrap;
clearScrap(scrap);
} else {
final int typeCount = mViewTypeCount;
for (int i = 0; i < typeCount; i++) {
final ArrayList<View> scrap = mScrapViews[i];
clearScrap(scrap);
}
}
clearTransientStateViews();
}
/**
* Fill ActiveViews with all of the children of the AbsListView.
*/
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;
}
}
}
/**
* Get the view corresponding to the specified position. The view will be removed from
* mActiveViews if it is found.
*/
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;
}
/**
* @return A view from the ScrapViews collection. These are unordered.
*/
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;
}
/**
* Puts a view into the list of scrap views.
* <p>
* If the list data hasn't changed or the adapter has stable IDs, views
* with transient state will be preserved for later retrieval.
*/
void addScrapView(View scrap, int position) {
final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
// Can't recycle, but we don't know anything about the view.
// Ignore it completely.
return;
}
lp.scrappedFromPosition = position;
// Remove but don't scrap header or footer views, or views that
// should otherwise not be recycled.
final int viewType = lp.viewType;
if (!shouldRecycleViewType(viewType)) {
// Can't recycle. If it's not a header or footer, which have
// special handling and should be ignored, then skip the scrap
// heap and we'll fully detach the view later.
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
getSkippedScrap().add(scrap);
}
return;
}
scrap.dispatchStartTemporaryDetach();
// The the accessibility state of the view may change while temporary
// detached and we do not allow detached views to fire accessibility
// events. So we are announcing that the subtree changed giving a chance
// to clients holding on to a view in this subtree to refresh it.
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
// Don't scrap views that have transient state.
final boolean scrapHasTransientState = scrap.hasTransientState();
if (scrapHasTransientState) {
if (mAdapter != null && mAdapterHasStableIds) {
// If the adapter has stable IDs, we can reuse the view for
// the same data.
if (mTransientStateViewsById == null) {
mTransientStateViewsById = new LongSparseArray<>();
}
mTransientStateViewsById.put(lp.itemId, scrap);
} else if (!mDataChanged) {
// If the data hasn't changed, we can reuse the views at
// their old positions.
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArray<>();
}
mTransientStateViews.put(position, scrap);
} else {
// Otherwise, we'll have to remove the view and start over.
getSkippedScrap().add(scrap);
}
} else {
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
}
...
}
可以看到在RecycleBin中声明了以下变量:
-
: 存储在mActiveViews中的第一个视图的位置。mFirstActivePosition
-
: 存储屏幕上可见的ItemView数组mActiveViews
-
:mScrapViews
mScrapViews,存储废弃ItemViewArrayList<View>[]
-
: ItemViewType的类型个数mViewTypeCount
-
:mCurrentScrap
mCurrentScrap作用和mScrapViews一样,存储废弃ItemView。由于大部分时候ListView的mViewTypeCount为默认值1,使用mCurrentScrap存储废弃的ItemView操作会更加方便。ArrayList<View>
上面只保留了几个重要的get和set方法:
-
:初始化mViewTypeCount,mCurrentScrap,mScrapViewssetViewTypeCount(viewTypeCount)
-
:将所有的子View填充到mActiveViewsfillActiveViews(childCount, firstActivePosition)
-
:从mActiveViews中获得ItemViewgetActiveView(position)
-
:当mViewTypeCount==1时,从mCurrentScrap中获得存储的ItemView,否则从mScrapViews中获取getScrapView(position)
-
: 当mViewTypeCount==1时,将废弃 ItemView添加到addScrapView(scrap, position)
中,否则添加到mScrapViews中mCurrentScrap
RecycleBin中主要维护了两个集合,一个存储显示在屏幕上的ItemView数组
mActiveViews
,和一个缓存废弃ItemView的
mCurrentScrap
集合(当mViewTypeCount为1时)。
说明:
在RecycleBin中,根据
mViewTypeCount
的个数是否大于1(即ListView的Item类型是否大于1),采用缓存废弃ItemView的集合不同,大部分情况mViewTypeCount==1是成立,所以RecycleBin专门为mViewTypeCount == 1的情况准备了mCurrentScrap集合来存储废弃的ItemView,下面的获得和存储废弃ItemView默认都从
mCurrentScrap中
存取。
三、Adapter中的数据是如何展示在Listview上的
通过ListView的setAdapter()方法将Adapter设置给ListView
mAdapter = new MyAdapter(this, mList);
mListView.setAdapter(mAdapter);
mList已经初始化完成,有数据了
1、setAdapter源码:
/**
* Sets the data behind this ListView.
*/
@Override
public void setAdapter(ListAdapter adapter) {
//如果已经注册则先取消注册
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
//清空Listview的所有状态和数据
resetList();
//清空缓存机制,mRecycler在AbsListView中声明
mRecycler.clear();
//mAdapter赋值
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
//添加headerView和footerView的Adapter
mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
// AbsListView#setAdapter will update choice mode states.
super.setAdapter(adapter);
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
//创建观察者。AdapterDataSetObserver是AbsListView内部类
mDataSetObserver = new AdapterDataSetObserver();
//注册观察者
mAdapter.registerDataSetObserver(mDataSetObserver);
//设置布局种类的个数
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
int position;
if (mStackFromBottom) {
position = lookForSelectablePosition(mItemCount - 1, false);
} else {
position = lookForSelectablePosition(0, true);
}
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}
//重新测量布局绘制
requestLayout();
}
在setAdapter方法中主要做了:
- 清空ListView的状态
- 赋值mAdapter
- 注册数据变化的观察者
AdapterDataSetObserver
- 调用
重新布局requestLayout
最主要的还是调用
requestLayout
重新测量ListView。
ListView的继承关系如下:
ListView -> AbsListView -> AdapterView -> ViewGroup
可以看到ListView是一个ViewGroup布局,ViewGrope重新绘制时,主要是对子View的测量和布局,所以查看ListView的
onMeasure
方法和AbsListViewd的
onLayout
方法
2、onMeasure()方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
//AbsListView 对ListView 的 padding进行设置
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;
//调用setAdapter()后,此时mAdapter不为空
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
//如果有数据,并且没有指定宽高的测量模式
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
|| heightMode == MeasureSpec.UNSPECIFIED)) {
//通过obtainView()获得第一个子View
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;
}
3、onLayout()方法 & AbsListView
/**
* Subclasses should NOT override this method but
* {@link #layoutChildren()} instead.
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mInLayout = true;
//获得子View数
final int childCount = getChildCount();
if (changed) {
for (int i = 0; i < childCount; i++) {
//强制测量标记
getChildAt(i).forceLayout();
}
mRecycler.markChildrenDirty();
}
//测量子View
layoutChildren();
mInLayout = false;
mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
// TODO: Move somewhere sane. This doesn't belong in onLayout().
if (mFastScroll != null) {
mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
}
}
/**
* Subclasses must override this method to layout their children.
*/
protected void layoutChildren() {
}
在AbsListView 的
onLayout
方法又会调用
layoutChildren
来确定子View的位置,layoutChildren具体实现在ListView中
4、layoutChildren()方法 & ListView
@Override
protected void layoutChildren() {
final boolean blockLayoutRequests = mBlockLayoutRequests;
if (blockLayoutRequests) {
return;
}
mBlockLayoutRequests = true;
try {
super.layoutChildren();
...
// 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;
}
...
} finally {
if (mFocusSelector != null) {
mFocusSelector.onLayoutComplete();
}
if (!blockLayoutRequests) {
mBlockLayoutRequests = false;
}
}
}
初始化时,mLayoutMode的模式为NORMAL,所以会执行default中的方法。当childCount==0的时候,有两种填充模式,fillFromTop从上往下填充,fillUp从下往上填充。默认是fillFromTop。
/**
* Fills the list from top to bottom, starting with mFirstPosition
*/
private View fillFromTop(int nextTop) {
mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
if (mFirstPosition < 0) {
mFirstPosition = 0;
}
return fillDown(mFirstPosition, nextTop);
}
执行fillDown
/**
* Fills the list from pos down to the end of the list view.
*/
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;
}
通过
makeAndAddView
获得一个ItemView。
/**
* 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.
*/
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;
}
首先会从RecycleBin的mActiveViews获得一个ItemView,当ItemView不为空将它布局到特定的位置
如果该ItemView为空,通过
obtainView
获得一个新的ItemView,然后将它布局到指定的位置上
5、obtainView 复用逻辑
obtainView
是AbsListView中的一个方法,通过该方法达到View的复用逻辑。
/**
* 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.
*/
View obtainView(int position, boolean[] outMetadata) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
outMetadata[0] = false;
...
//从缓存(废弃) View的集合中获得scrapView
final View scrapView = mRecycler.getScrapView(position);
//将scrapView 设置给mAdapter.getView
//scrapView 也有可能为空,需要我们在Adapter中判断convertView是否为空
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {
//在Adapter的getView()中如果没有使用缓存的scrapView,
//重新创建一个convertView并返回.
//则child != scrapView成立
if (child != scrapView) {
// Failed to re-bind the data, return scrap to the heap.
//没有使用缓存的scrapView,将scrapView回收到mCurrentScrap中
mRecycler.addScrapView(scrapView, position);
} else if (child.isTemporarilyDetached()) {
outMetadata[0] = true;
// Finish the temporary detach started in addScrapView().
child.dispatchFinishTemporaryDetach();
}
}
...
//布局ItemView
setItemViewLayoutParams(child, position);
...
return child;
}
obtainView
主要做了两件事:
- 首先从
中获取缓存(废弃)的ItemView,并传递到Adapter中getView()方法中的第二个参数,也就是mCurrentScrap
convertView
。根据convertView是否为空,决定是否从XML中创建视图,否则使用缓存视图,这样避免了每次从XML中创建视图,显著提升了ListView的加载效率和性能。
如果Adapter的getView()方法中的
为空,则创建新的View并且会将其添加到存储显示在屏幕上的ItemView集合convertView
中,当有ItemView从屏幕上完全移出的时候,会将移出的ItemView从mActiveViews中移除并添加到mCurrentScrap中,这样当新的ItemView显示在屏幕上的时候,会先从mActiveViews
mCurrentScrap
中获取缓存的ItemView,避免从XML中加载创建新的ItemView,从而显著提升ListView性能,这样即使加载很多条数据,ListView也能够流畅运行。
缓存ItemView的时候,同时也将ItemView上的数据也缓存了,所以使用的时候需要重新设置数据。
- 将ItemView布局到合适的地方
四、Adapter刷新
1、notifyDataSetChanged
/**
* Notifies the attached observers that the underlying data has been changed
* and any View reflecting the data set should refresh itself.
*/
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
DataSetObservable源码:
public class DataSetObservable extends Observable<DataSetObserver> {
public void notifyChanged() {
synchronized(mObservers) {
//遍历订阅集合,通知每个订阅者
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
public void notifyInvalidated() {
synchronized (mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onInvalidated();
}
}
}
}
最终会调用
DataSetObserver
的
onChanged
方法。
我们已经知道,在Adapter的
setAdapter
中会创建
mDataSetObserver
和注册广播:
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
2、AdapterDataSetObserver & AbsListView
AdapterDataSetObserver是AbsListView的内部类:
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
@Override
public void onInvalidated() {
super.onInvalidated();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
}
AbsListView
中的
AdapterDataSetObserver
又继承
AdapterView
的
AdapterDataSetObserver
3、AdapterDataSetObserver & AdapterView
class AdapterDataSetObserver extends DataSetObserver {
private Parcelable mInstanceState = null;
@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount();
// Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
requestLayout();
}
@Override
public void onInvalidated() {
mDataChanged = true;
if (AdapterView.this.getAdapter().hasStableIds()) {
// Remember the current state for the case where our hosting activity is being
// stopped and later restarted
mInstanceState = AdapterView.this.onSaveInstanceState();
}
// Data is invalid so we should reset our state
mOldItemCount = mItemCount;
mItemCount = 0;
mSelectedPosition = INVALID_POSITION;
mSelectedRowId = INVALID_ROW_ID;
mNextSelectedPosition = INVALID_POSITION;
mNextSelectedRowId = INVALID_ROW_ID;
mNeedSync = false;
checkFocus();
requestLayout();
}
public void clearSavedState() {
mInstanceState = null;
}
}
最终会执行AdapterDataSetObserver 的
onChanged
方法。
在
onChanged
方法中,首先会将
mDataChanged
赋值为
true
,然后赋值
mItemCount
,最后调用
requestLayout()
重新测量绘制ListView。
4、观察者模式
Adapter的刷新机制其实是一个观察者模式,首先是在setAdapter中创建并注册观察者。调用mAdapter.notifyDataSetChanged()刷新,会遍历所有订阅的DataSetObserver,并调用DataSetObserver的onChanged方法通知订阅者重新绘制ListView。
五、ListView优化
ListView的优化不外乎两点:
- 复用convertView。减少布局的加载和创建次数,显著提升ListView性能
- 使用ViewHolder。减少findViewById次数
其他的数据错误和图片错位都是复用convertView导致的,只需要每次重新设置数据和给ImageView设置Tag就行。
六、总结
虽然我们需要通过看源码来提升自己的能力,但是千万不能一头就扎进源码里,看之前最好想想看源码的目的,带着目的和疑问去看可能效果会更好。
部分参考:《Android源码设计模式解析与实战》