android列表View,ListView源碼分析

ListView結構關系
首先理清listview的層級關系,

271423506402853.jpg

了解一下 AdapterView

public abstract class AdapterView<T extends Adapter> 
           extends ViewGroup {
            省略部分代碼
    public void setOnItemClickListener(@Nullable OnItemClickListener listener)  
    public void setOnItemLongClickListener(OnItemLongClickListener listener)
    public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener)
    public abstract T getAdapter();
    public abstract void setAdapter(T adapter);
    public int getCount()
    @Override
    public void addView(View child) {
        throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
    }

//在AdapterView的addView方法中會拋出異常,
//也就是說AdapterView禁用了addView方法。
    @Override
    public void addView(View child, int index) {
        throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
    }
      省略部分代碼
    class AdapterDataSetObserver extends DataSetObserver {
        private Parcelable mInstanceState = null;
         //當adapter數據發生改變的時候申請重繪
         //DataSetObserver 用到了觀察者模式
        @Override
        public void onChanged() {
        省略部分代碼
        }
        @Override
        public void onInvalidated() {
         省略部分代碼
    }
}

首先需要說一下RecycleBin的基本原理,這個類也是實現復用的關鍵類。RecleBin是AbsListView內部類。AbsListView中有一個RecycleBin的對象mRecycler

AbsListView.java

//用于存儲不用的view,以便在下次layout中使用來避免創建新的
final RecycleBin mRecycler = new RecycleBin();

RecycleBin使用兩級view來進行回收:
ActiveView
:激活view,當前顯示在屏幕上的激活view。
ScrapView
:廢棄view,被刪除的ActiveView會被自動加入ScrapView。

RecycleBin變量說明
AbsListView.java

private RecyclerListener mRecyclerListener;

//存儲在mActiveViews中的第一個view的位置
private int mFirstActivePosition;

//布局開始時屏幕顯示的view,這個數組會在布局開始時填充,布局結束后所有view被移至mScrapViews。
private View[] mActiveViews = new View[0];
//可以被適配器用作convert view的無序view數組。 這個ArrayList就是adapter中getView方法中的
//參數convertView的來源。注意:這里是一個數組,因為如果adapter中數據有多種類型,
//那么就會有多個ScrapViews。
private ArrayList<View>[] mScrapViews;

//view類型總數,列表中可能有多種數據類型,比如內容數據和分割符
private int mViewTypeCount;

//跟mScrapViews的卻別是,mScrapViews是個隊列數組,ArrayList<View>[]類型,
//數組長度為mViewTypeCount,而默認ViewTypeCount = 1
//的情況下mCurrentScrap=mScrapViews[0]。
private ArrayList<View> mCurrentScrap;

RecycleBin 方法說明

//為每個子類(子對象)調用forceLayout()。將mScrapView中回收回來的View設置一樣標志,
//在下次被復用到ListView中時,告訴viewroot重新layout該view。
//forceLayout()方法只是設置標志,并不會通知其parent來重新layout
public void markChildrenDirty()

//判斷給定的view的viewType指明是否可以回收回。
//指定忽略的( ITEM_VIEW_TYPE_IGNORE = -1),
//或者是 HeaderView / (FootViewITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2)是不被回收的。
//如有特殊需要可以將自己定義的viewType設置為-1,否則,將會浪費內存,導致OOM。
 public boolean shouldRecycleViewType(int viewType) {
            return viewType >= 0;
        }

//Clears the scrap heap.清空廢棄view堆,并將這些View從窗口中Detach。
void clear()

//獲取mActiveViews中指定位置的view,如果找到會將該view從mActiveViews中移除
View getActiveView(int position)

//用AbsListView.的所有子view填充ActiveViews,
//其中childCount是mActiveViews應該保存的最少的view數,
//firstActivePosition是mActiveViews中存儲的首個view的位置。
void fillActiveViews(int childCount, int firstActivePosition)

//清掉當前處于transient(轉換)狀態的所有保存的view
void clearTransientStateViews()

//將view放入scrapview list中
//
void addScrapView(View scrap, int position) 

//
View getScrapView(int position)

//
private View retrieveFromScrap(ArrayList<View> scrapViews, int position)

//
void removeSkippedScrap()
//
private void pruneScrapViews()
//
void reclaimScrapViews(List<View> views)

//
void scrapActiveViews()
//
void setCacheColorHint(int color)

ActivityView就是在UI屏幕上可見的視圖(onScreenView),也是與用戶進行交互的View。這些View會通過RecycleBin直接存儲到mActivityView數組當中。
當滑動ListView的時候,有些View被滑動到屏幕之外(offScreen) View,這些View就成為了ScrapView,就是廢棄的View,已經無法與用戶進行交互了,此時在UI視圖改變的時候就沒有繪制視圖的必要。這些Veiw被RecycleBin存在mScrapView數組當中,但是沒有被銷毀掉,目的是為了二次復用,也就是間接復用。

選區_131.png

當新的View需要顯示的時候,先判斷mActiveViews中是否存在,如果存在那么就可以從mActivityView數組當中直接取出復用,直接復用,否則從mScrapView數組當中進行判斷,存在,二次復用當前的視圖,不存在,就需要inflate View了。

選區_132.png

ListView比如第一次繪制 會執行 onMeasure->onLayout->onDraw
比如數據改變,申請requestLayout

調用的函數的堆棧圖如下。(這里只是選擇的特殊的一條函數執行路徑)

選區_133.png

AbsListView.java

    /**
     * 子類不必覆寫這個方法,覆寫layoutChildren代替
     *  
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        mInLayout = true;

        final int childCount = getChildCount();
        if (changed) {
            for (int i = 0; i < childCount; i++) {
                getChildAt(i).forceLayout();
            }
            mRecycler.markChildrenDirty();
        }
        //對item進行布局的流程
        layoutChildren();
        mInLayout = false;

        省略部分代碼
    }

fillFromTop 如下

ListView.java


    private View fillDown(int pos, int nextTop) {
        View selectedView = null;
    /**
     * end用來判斷Item是否已經將ListView填充滿
     */
        int end = (mBottom - mTop);
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            end -= mListPadding.bottom;
        }

         /**
          * nextTop < end確保了我們只要將新增的子View能夠覆蓋ListView的界面就可以了
          *pos < mItemCount確保了我們新增的子View在Adapter中都有對應的數據源item
          */
        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;
    }

上面函數的理解

選區_134.png

makeAndAddView如下

ListView.java

    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) {
                 //如果存在activeView,使用activeView
                // 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;
    }

obtainView的如下

AbListView.java

    View obtainView(int position, boolean[] outMetadata) {
        省略部分代碼
        final View scrapView = mRecycler.getScrapView(position);
        // mAdapter.getView登場
        final View child = mAdapter.getView(position, scrapView, this);
        if (scrapView != null) {
            if (child != scrapView) {
                // Failed to re-bind the data, return scrap to the heap.
                mRecycler.addScrapView(scrapView, position);
            } else if (child.isTemporarilyDetached()) {
                outMetadata[0] = true;

                // Finish the temporary detach started in addScrapView().
                child.dispatchFinishTemporaryDetach();
            }
        }
        省略部分代碼
        return child;
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容