Android學習筆記之ListView複用機制

PS:滿打滿算,差很少三個月沒寫博客了...前一陣忙的不可開交...總算是能夠抽出時間研究研究其餘事情了...數組

 

學習內容:緩存

1.ListView的複用機制app

2.ViewHolder的概念ide

 

1.ListView的複用機制模塊化

  ListView是咱們常用的一個控件,雖說都會用,可是卻並不必定徹底清楚ListView的複用機制,雖然在Android 5.0版本以後提供了RecycleView去替代ListView和GridView,提供了一種插拔式的體驗,也就是所謂的模塊化。本篇主要針對ListView的複用機制進行探討,所以就 提RecycleView。昨天看了一下郭霖大神的ListView原理深度解析的一篇博客,所以學習了一段時間,本身也說一下本身的理解。佈局

i.RecycleBin的基本原理post

  首先須要說一下RecycleBin的基本原理,這個類也是實現複用的關鍵類。接着咱們須要明確ActiveView的概念,ActivityView其實就是在UI屏幕上可見的視圖(onScreenView),也是與用戶進行交互的View,那麼這些View會經過RecycleBin直接存儲到mActivityView數組當中,以便爲了直接複用,那麼當咱們滑動ListView的時候,有些View被滑動到屏幕以外(offScreen) View,那麼這些View就成爲了ScrapView,也就是廢棄的View,已經沒法與用戶進行交互了,這樣在UI視圖改變的時候就沒有繪製這些無用視圖的必要了。他將會被RecycleBin存儲到mScrapView數組當中,可是沒有被銷燬掉,目的是爲了二次複用,也就是間接複用。當新的View須要顯示的時候,先判斷mActivityView中是否存在,若是存在那麼咱們就能夠從mActivityView數組當中直接取出複用,也就是直接複用,不然的話從mScrapView數組當中進行判斷,若是存在,那麼二次複用當前的視圖,若是不存在,那麼就須要inflate View了。學習

  這是一個整體的流程圖,複用機制就是這樣的。那麼咱們先來理解一下ListView第一次加載的時候都作了哪些工做,首先會執行onLayout方法。。this

/**
 * 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;
    if (changed) {
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            getChildAt(i).forceLayout();
        }
        mRecycler.markChildrenDirty();
    }
    layoutChildren();
    mInLayout = false;
}

  這裏能夠看到onLayout方法會調用layoutChildren()方法,也就是對item進行佈局的流程,layoutChildren()方法就不進行粘貼了,代碼量過長咱們只須要知道,這是對ListView中的子View進行佈局的一個方式就能夠了,在咱們第一次加載ListView的時候,RecycleBin中的數組都沒有任何的數據,所以第一次加載都須要inflate View,也就是建立新的View。而且第一次加載的時候是自頂向下對數據進行加載的,所以在layoutChildren()會執行fillFromTop()方法。fillFromTop()會執行filleDown()方法。spa

/**
 * Fills the list from pos down to the end of the list view.
 *
 * @param pos The first position to put in the list
 *
 * @param nextTop The location where the top of the item associated with pos
 *        should be drawn
 *
 * @return The view that is currently selected, if it happens to be in the
 *         range that we draw.
 * 
 * @param pos:列表中的一個繪製的Item在Adapter數據源中對應的位置
 * @param nextTop:表示當前繪製的Item在ListView中的實際位置..
 */
private View fillDown(int pos, int nextTop) {
    View selectedView = null;
    /**
     * end用來判斷Item是否已經將ListView填充滿
     */
    int end = (getBottom() - getTop()) - mListPadding.bottom;
    while (nextTop < end && pos < mItemCount) {
         /**
          * nextTop < end確保了咱們只要將新增的子View可以覆蓋ListView的界面就能夠了
          *pos < mItemCount確保了咱們新增的子View在Adapter中都有對應的數據源item
          */
        // is this the selected item?
        boolean selected = pos == mSelectedPosition;
        View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
        /**
           *將最新child的bottom值做爲下一個child的top值,存儲在nextTop中
           */
        nextTop = child.getBottom() + mDividerHeight;
        if (selected) {
            selectedView = child;
        }
        pos++;
    }
    return selectedView;
}
  • 在while循環中添加子View,咱們先不看while循環的具體條件,先看一下循環體。在循環體中,將pos和nextTop傳遞給makeAndAddView方法,該方法返回一個View做爲child,該方法會建立View,並把該View做爲child添加到ListView的children數組中。

  • 而後執行nextTop = child.getBottom() + mDividerHeight,child的bottom值表示的是該child的底部到ListView頂部的距離,將該child的bottom做爲下一個child的top,也就是說nextTop一直保存着下一個child的top值。

  • 最後調用pos++實現position指針下移。如今咱們回過頭來看一下while循環的條件while (nextTop < end && pos < mItemCount)。

  • nextTop < end確保了咱們只要將新增的子View可以覆蓋ListView的界面就能夠了,好比ListView的高度最多顯示10個子View,咱們不必向ListView中加入11個子View。

  • pos < mItemCount確保了咱們新增的子View在Adapter中都有對應的數據源item,好比ListView的高度最多顯示10個子View,可是咱們Adapter中一共纔有5條數據,這種狀況下只能向ListView中加入5個子View,從而不能填充滿ListView的所有高度。

  這裏存在一個關鍵方法,也就是makeAndAddView()方法,這是ListView將Item顯示出來的核心部分,也是這個部分涉及到了ListView的複用

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
        boolean selected) {
    View child;
    //判斷數據源是否發生了變化.
    if (!mDataChanged) {
        // Try to use an exsiting view for this position
        //若是mActivityView[]數組中存在能夠直接複用的View,那麼直接獲取,而後從新佈局.
        child = mRecycler.getActiveView(position);
        if (child != null) {
            // Found it -- we're using an existing child
            // This just needs to be positioned
            setupChild(child, position, y, flow, childrenLeft, selected, true);
            return child;
        }
    }
    // 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)方法.
*/
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的直接複用。

  若是咱們在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返回。整體的流程就是這樣。

  這裏咱們能夠看到,ListView始終只會在getView方法中inflate一頁的Item,也就是new View只會執行一頁Item的次數。後續的Item經過直接複用和間接複用完成。

 注意一種狀況:好比說仍是一頁的Item,可是position = 0的Item沒有徹底滑動出UI,position = 10的Item沒有徹底進入到UI的時候,那麼position = 0的Item不會被detach掉,一樣不會被加入到廢棄View數組,這時mScrapView是空的,沒有任何數據,那麼position = 10的Item即沒法從mActivityView中直接複用View,由於是第一次加載。mActivityView[10]是不存在的,同時mScrapView是空的,所以position = 10的Item只能從新生成View,也就是從getView方法中inflate。這裏obtainView方法沒有具體貼出,你們能夠本身進去看看。obtainView其實就是判斷可否從廢棄View中獲取到View,獲取到了則執行:

if (scrapView != null) {  
    child = mAdapter.getView(position, scrapView, this);    
} 

  這裏是能夠獲取到,那麼getView會傳遞scrapView。不然的話:

else {  
    child = mAdapter.getView(position, null, this);  
}  

  獲取不到就傳遞null,這樣就會執行咱們定義的Adapter中的方法。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if(convertView == null){
        convertView = View.inflate(context, R.layout.list_item_layout, null);
    }
    return convertView;
}

 至於向上滑動會執行其餘的一些方法,也就是自底向上鋪滿ListView,一樣也會直接或者間接複用控件。理解了複用的機制纔是關鍵,所以向上滑基本就不難理解了。補充一點,RecycleBin中還存在一個方法,setViewTypeCount()方法。這個是針對Adapter中的getViewTypeCount()設定的。針對每一種數據類型,setViewTypeCount()會爲每種數據類型開啓一個單獨的RecycleBin回收機制。這裏咱們只須要知道就能夠了。至於在郭神博客中看到ListView會onLayout屢次,這是確定的,因爲Android View加載機制問題,子控件須要根據父控件的大小要從新測量大小,通過屢次測量纔可以顯示在UI上。這是View測量屢次的緣由。至於ListView在屢次佈局的問題我就不進行贅餘了,總之不管幾回測量,ListView是不會屢次執行重複的邏輯的,也就是說數據不會有多份,只會存在一份數據。

 這裏也就是ListView複用的基本原理和RecycleBin的回收機制了。代碼貼的不多,都是一些關鍵代碼,不必去一行一行的研究代碼,畢竟和大神還差很大的一個檔次。咱們只須要知道這個執行過程和原理就能夠了。

2.ViewHolder

 最後說一說ViewHolder這個東西,不少Android學習者會把這個東西和ListView的複用機制搞混。這裏ViewHolder也是在複用的時候進行使用,可是和複用機制是沒太大關係的。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
        final ViewHolder holder;
        ListViewItem itemData = items.get(position);
        if(convertView == null){
            convertView = View.inflate(context, R.layout.list_item_layout, null);
            holder = new ViewHolder();
            holder.userImg = (ImageView) convertView.findViewById(R.id.user_header_img);
            holder.userName = (TextView) convertView.findViewById(R.id.user_name);
            holder.userComment = (TextView) convertView.findViewById(R.id.user_coomment);
            convertView.setTag(holder);
        }else{
            holder = (ViewHolder) convertView.getTag();
        }
        holder.userImg.setImageResource(itemData.getUserImg());
        holder.userName.setText(itemData.getUserName());
        holder.userComment.setText(itemData.getUserComment());
        return convertView;
}

static class ViewHolder{
        ImageView userImg;
        TextView userName;
        TextView userComment;
}

  在實現Adapter的時候,咱們通常會加上ViewHolder這個東西,ViewHolder和複用機制和原理是無關的,他的主要目的是持有Item中控件的引用,從而減小findViewById()的次數,由於findViewById()方法也是會影響效率的,所以在複用的時候他起的做用是這個,減小方法執行次數增長效率。這裏作個簡單的提醒,別弄混就行。

相關文章
相關標籤/搜索