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源碼設計模式解析與實戰》