ListView 複用學習

1.listview的測量說明

//寬度測量邏輯
if (widthMode == MeasureSpec.UNSPECIFIED) {
    widthSize = mListPadding.left + mListPadding.right + childWidth +
              getVerticalScrollbarWidth();
} else {
//初始化childState = combineMeasuredStates(childState, child.getMeasuredState())
    widthSize |= (childState & MEASURED_STATE_MASK);
}
//高度測量邏輯,這裏若是測量模式爲UNSPECIFIED,listview的高度就只會顯示一個childHeight的高度
if (heightMode == MeasureSpec.UNSPECIFIED) {
//初始化 childHeight = child.getMeasuredHeight();
   heightSize = mListPadding.top + mListPadding.bottom + childHeight +
           getVerticalFadingEdgeLength() * 2;
}
//正常狀況下測量模式應該是AT_MOST,此時會去累加每個children的高度
if (heightMode == MeasureSpec.AT_MOST) {
    heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}

2.listview的複用原理

listview經過RecycleBin進行view的複用.複用的時機在layoutChildren中進行java

2.1 RecycleBin原理數組

RecycleBin兩個比較重要的view數組緩存

//存儲當前顯示的view內容
 private View[] mActiveViews = new View[0];
 //根據不一樣的type廢棄的viewlist
 private ArrayList<View>[] mScrapViews;

在listview的layoutChildren()方法中 RecycleBin初始化mActiveViews方式markdown

if (dataChanged) {
    for (int i = 0; i < childCount; i++) {
    //數據改變後把view放置廢棄的複用池裏
        recycleBin.addScrapView(getChildAt(i), firstPosition+i);
    }
} else {
//childCount爲有效顯示的child數量
    recycleBin.fillActiveViews(childCount, firstPosition);
}

...

//將目前全部的ActiveViews降級爲ScrapViews,並將以前的全部ScrapViews清除,爲新產生的
//ActiveViews作好準備
recycleBin.scrapActiveViews();

RecycleBin複用過程 :
與用戶進行交互的View,那麼這些View會經過RecycleBin直接存儲到mActivityView數組當中,以便爲了直接複用. 當咱們滑動ListView的時候,有些View被滑動到屏幕以外(offScreen) View,那麼這些View就成爲了ScrapView,也就是廢棄的View,已經沒法與用戶進行交互了,這樣在UI視圖改變的時候就沒有繪製這些無用視圖的必要了。他將會被RecycleBin存儲到mScrapView數組當中,可是沒有被銷燬掉,目的是爲了二次複用,也就是間接複用。當新的View須要顯示的時候,先判斷mActivityView中是否存在,若是存在那麼咱們就能夠從mActivityView數組當中直接取出複用,也就是直接複用,不然的話從mScrapView數組當中進行判斷,若是存在,那麼二次複用當前的視圖,若是不存在,那麼就須要inflate View了。網絡

搬挪自網絡的總結圖片:ide

這裏寫圖片描述

獲取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邏輯方法
        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;
}
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
if (!mDataChanged) {
        //獲取ActiveView邏輯
       // 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.
/** *若是mActivityView[]數組中沒有可用的View,那麼嘗試從mScrapView數組中讀取.而後從新佈局. *若是能夠從mScrapView數組中能夠獲取到,那麼直接返回調用mAdapter.getView(position,scrapView,this); *若是獲取不到那麼執行mAdapter.getView(position,null,this)方法. */
   final View child = obtainView(position, mIsScrap);

   // This needs to be positioned and measured.
   setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

   return child;
}

這裏能夠看到若是數據源沒有變化的時候,會從mActivityView數組中判斷是否存在能夠直接複用的View,可能不少讀者都不太明白直接複用究竟是怎麼個過程,舉個例子,好比說咱們ListView一頁能夠顯示10條數據,那麼咱們在這個時候滑動一個Item的距離,也就是說把position = 0的Item移除屏幕,將position = 10 的Item移入屏幕,那麼position = 1的Item是否是就直接可以從mActivityView數組中拿到呢?這是能夠的,咱們在第一次加載Item數據的時候,已經將position = 0~9的Item加入到了mActivityView數組當中,那麼在第二次加載的時候,因爲position = 1 的Item仍是ActivityView,那麼這裏就能夠直接從數組中獲取,而後從新佈局。這裏也就表示的是Item的直接複用。post

若是咱們在mActivityView數組中獲取不到position對應的View,那麼就嘗試從mScrapView廢棄View數組中嘗試去獲取,還拿剛纔的例子來講當position = 0的Item被移除屏幕的時候,首先會Detach讓View和視圖進行分離,清空children,而後將廢棄View添加到mScrapView數組當中,當加載position = 10的Item時,mActivityView數組確定是沒有的,也就沒法獲取到,一樣mScrapView中也是不存在postion = 10與之對應的廢棄View,說白了就是mScrapView數組只有mScrapView[0]這一項數據,確定是沒有mScrapView[10]這項數據的,那麼咱們就會這樣想,確定是從Adapter中的getView方法獲取新的數據嘍,其實並非這樣,雖然mScrapView中雖然沒有與之對應的廢棄View,可是會返回最後一個緩存的View傳遞給convertview。那麼也就是將mScrapView[0]對應的View返回。整體的流程就是這樣。this

抄自網絡圖片:
這裏寫圖片描述spa

相關文章
相關標籤/搜索