天天看點

ListView源碼閱讀

listview的源碼在此之前看過也不止一次了,但是現在想想好像也就停留在看過的樣子,除此之外就沒啥印象了。

想想之前就是在瞎看,畢竟ListView加上AbsListview有一萬多行代碼了,瞎看的話确實啥都看不出來啥的。是以這次看的話我就帶着幾次問題來看就行了,畢竟把1萬多行代碼看完對很多人來說是不現實的。

今天就帶着下面幾個問題,再次看一下Listview源碼吧:

  • Listview的擴充卡模式
  • setAdapter是如何将資料設定到Listview中去的
  • Adapter重新整理
  • Adapter緩存機制RecycleBin
  • ListView優化

在此之前先來看一下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中聲明了以下變量:

  • mFirstActivePosition

    : 存儲在mActiveViews中的第一個視圖的位置。
  • mActiveViews

    : 存儲螢幕上可見的ItemView數組
  • mScrapViews

    ArrayList<View>[]

    mScrapViews,存儲廢棄ItemView
  • mViewTypeCount

    : ItemViewType的類型個數
  • mCurrentScrap

    ArrayList<View>

    mCurrentScrap作用和mScrapViews一樣,存儲廢棄ItemView。由于大部分時候ListView的mViewTypeCount為預設值1,使用mCurrentScrap存儲廢棄的ItemView操作會更加友善。

上面隻保留了幾個重要的get和set方法:

  • setViewTypeCount(viewTypeCount)

    :初始化mViewTypeCount,mCurrentScrap,mScrapViews
  • fillActiveViews(childCount, firstActivePosition)

    :将所有的子View填充到mActiveViews
  • getActiveView(position)

    :從mActiveViews中獲得ItemView
  • getScrapView(position)

    :當mViewTypeCount==1時,從mCurrentScrap中獲得存儲的ItemView,否則從mScrapViews中擷取
  • addScrapView(scrap, position)

    : 當mViewTypeCount==1時,将廢棄 ItemView添加到

    mCurrentScrap

    中,否則添加到mScrapViews中

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

主要做了兩件事:

  • 首先從

    mCurrentScrap

    中擷取緩存(廢棄)的ItemView,并傳遞到Adapter中getView()方法中的第二個參數,也就是

    convertView

    。根據convertView是否為空,決定是否從XML中建立視圖,否則使用緩存視圖,這樣避免了每次從XML中建立視圖,顯著提升了ListView的加載效率和性能。

    如果Adapter的getView()方法中的

    convertView

    為空,則建立新的View并且會将其添加到存儲顯示在螢幕上的ItemView集合

    mActiveViews

    中,當有ItemView從螢幕上完全移出的時候,會将移出的ItemView從mActiveViews中移除并添加到mCurrentScrap中,這樣當新的ItemView顯示在螢幕上的時候,會先從

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