Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android ListView復用機制詳解

Android ListView復用機制詳解

編輯:關於Android編程

最近用到RecyclerView,想研究RecyclerView和ListView復用機制的區別,這篇文章以解析源碼的方式解析ListView復用機制。

 

ListView復用是通過AbsListView的RecycleBin內部類來實現的,源碼注釋如下:

 

/**
     * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
     * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
     * start of a layout. By construction, they are displaying current information. At the end of
     * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
     * could potentially be used by the adapter to avoid allocating views unnecessarily.
     */
RecycleBin是2級的存儲結構,

 

ActiveViews: 當前屏幕上的活動View

ScrapViews: 廢棄View,可復用的舊View
 

再看RecycleBin的成員變量

 

//回收Listener,當View變為可回收,即ScrapView時,會通過mRecyclerListener通知注冊者,listener可通過setRecyclerListener注冊
private RecyclerListener mRecyclerListener;

/**
 * The position of the first view stored in mActiveViews.
 */
// 第一個活動view的position,即第一個可視view的position
private int mFirstActivePosition;

/**
 * Views that were on screen at the start of layout. This array is populated at the start of
 * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
 * Views in mActiveViews represent a contiguous range of Views, with position of the first
 * view store in mFirstActivePosition.
 */
// 活動view的集合
private View[] mActiveViews = new View[0];

/**
 * Unsorted views that can be used by the adapter as a convert view.
 */
/**廢棄的可修復view集合,復用時傳遞到Adapter#getView方法的convertView參數。
 * 因為item type可能大於1,只有view type相同的view之間才能復用,所以是個二維數組
 */
private ArrayList[] mScrapViews;

// ListView item type數量
private int mViewTypeCount;

// 當前的廢棄view數組,定義這個成員是為了在mViewTypeCount為1時使用方便,不需要去取mScrapViews的第一個元素
private ArrayList mCurrentScrap;

// 被跳過的,不能復用的view集合。view type小於0或者處理transient狀態的view不能被復用。
private ArrayList mSkippedScrap;

// 處於transient狀態的view集合,處於transient狀態的view不能被復用,如view的動畫正在播放,
// transient是瞬時、過渡的意思,關於transient狀態詳見android.view.View#PFLAG2_HAS_TRANSIENT_STATE
private SparseArray mTransientStateViews;
// 如果adapter的hasStableIds方法返回true,處於過度狀態的view保存到這裡。因為需要保存view的position,而且處於過度狀態的view一般很少,
// 這2個成員用了稀疏數組。具體不需要case,知道是保存轉換狀態view的集合就行。
private LongSparseArray mTransientStateViewsById;

 

從RecycleBin成員變量的定義基本可以看出復用的原理:

1. 廢棄的view保存在一個數組中,復用時從中取出

2. 擁有相同view type的view之間才能復用,所以mScrapViews是個二維數組

3. 處於transient狀態的view不能被復用
 

再來看方法

setViewTypeCount: 這個方法在給ListView設置adapter時調用,取值是adapter#getViewTypeCount()
public void setViewTypeCount(int viewTypeCount) {                                       
    if (viewTypeCount < 1) {                                                            
        throw new IllegalArgumentException("Can't have a viewTypeCount < 1");           
    }                                                                                   
    //noinspection [] scrapViews = new ArrayList[viewTypeCount];                        
    for (int i = 0; i < viewTypeCount; i++) {                                           
        scrapViews[i] = new ArrayList();                                          
    }                                                                                   
    mViewTypeCount = viewTypeCount;                                                     
    mCurrentScrap = scrapViews[0];                                                      
    mScrapViews = scrapViews;                                                           
}                                                                                       
  markChildrenDirty: 當ListView size或position變化時,設置mScrapViews和transient views的forceLayout flag,在下一次被復用時會重新布置。
public void markChildrenDirty() {                               
    if (mViewTypeCount == 1) {                                  
        final ArrayList scrap = mCurrentScrap;            
        final int scrapCount = scrap.size();                    
        for (int i = 0; i < scrapCount; i++) {                  
            scrap.get(i).forceLayout();                         
        }                                                       
    } else {                                                    
        final int typeCount = mViewTypeCount;                   
        for (int i = 0; i < typeCount; i++) {                   
            final ArrayList scrap = mScrapViews[i];       
            final int scrapCount = scrap.size();                
            for (int j = 0; j < scrapCount; j++) {              
                scrap.get(j).forceLayout();                     
            }                                                   
        }                                                       
    }                                                           
    if (mTransientStateViews != null) {                         
        final int count = mTransientStateViews.size();          
        for (int i = 0; i < count; i++) {                       
            mTransientStateViews.valueAt(i).forceLayout();      
        }                                                       
    }                                                           
    if (mTransientStateViewsById != null) {                     
        final int count = mTransientStateViewsById.size();      
        for (int i = 0; i < count; i++) {                       
            mTransientStateViewsById.valueAt(i).forceLayout();  
        }                                                       
    }                                                           
}                                                               

shouldRecycleViewType: 判斷有當前viewType的view是否應該被回收復用,viewType小於0的不復用
public boolean shouldRecycleViewType(int viewType) {   
    return viewType >= 0;                              
}                                                      

fillActiveViews: 將屏幕上所有活動view填充到mActiveViews中,childCount為需保存的最小view個數,即當前屏幕上ListView所展示的view數,注意不是adapter的item數。 firstActivePosition為第一個可視view的position,即view在adapter中的
/**                                                                                         
 * Fill ActiveViews with all of the children of the AbsListView.                            
 *                                                                                          
 * @param childCount The minimum number of views mActiveViews should hold                   
 * @param firstActivePosition The position of the first view that will be stored in         
 *        mActiveViews                                                                      
 */                                                                                         
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;                              
        }                                                                                   
    }                                                                                       
}                                                                    
  getActiveView: 查找mActiveView中指定position的view,找到後將從mActiveViews中移除
/**                                                                                     
 * Get the view corresponding to the specified position. The view will be removed from  
 * mActiveViews if it is found.                                                         
 *                                                                                      
 * @param position The position to look up in mActiveViews                              
 * @return The view if it is found, null otherwise                                      
 */                                                                                     
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;                                                                        
}                                                                    
  getScrapView: 查找復用的view,先通過position從adapter中找到view type,然後再從相應的scrap中查找
/**                                                                   
 * @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;                                                      
}                                                                    

retrieveFromScrap: 從指定的scrap中查找可復用的view,分別3個優先級: 1. 如adapter有stable ids,則先比較id 2. 如adapter無stable ids,則查找是否有在當前position被回收利用的view 3. 如優先級1,2都沒查找到,則取最近加入scrap中的view
private View retrieveFromScrap(ArrayList scrapViews, int position) {      
    final int size = scrapViews.size();                                         
    if (size > 0) {                                                             
        // See if we still have a view for this position or ID.                 
        for (int i = 0; i < size; i++) {                                        
            final View view = scrapViews.get(i);                                
            final AbsListView.LayoutParams params =                             
                    (AbsListView.LayoutParams) view.getLayoutParams();          
                                                                                
            if (mAdapterHasStableIds) {                                         
                final long id = mAdapter.getItemId(position);                   
                if (id == params.itemId) {                                      
                    return scrapViews.remove(i);                                
                }                                                               
            } else if (params.scrappedFromPosition == position) {               
                final View scrap = scrapViews.remove(i);                        
                clearAccessibilityFromScrap(scrap);                             
                return scrap;                                                   
            }                                                                   
        }                                                                       
        final View scrap = scrapViews.remove(size - 1);                         
        clearAccessibilityFromScrap(scrap);                                     
        return scrap;                                                           
    } else {                                                                    
        return null;                                                            
    }                                                                           
}                                                                               

addScrapView: 將可復用的view加入scrap數組中
/**                                                                                             
 * Puts a view into the list of scrap views.                                                    
 * 
* If the list data hasn't changed or the adapter has stable IDs, views * with transient state will be preserved for later retrieval. * * @param scrap The view to add * @param position The view's position within its parent */ 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)) { //不能被復用的view type,並且不為header或footer時加入skipped scrap // 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) { //view處於transient狀態 if (mAdapter != null && mAdapterHasStableIds) { //adpater has stable ids,加入mTransientStateViewsById中 // 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) { //數據未變化,加入mTransientStateViews中 // 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 { //adapter為null或adpater不為null,無stable ids,且數據變化,則丟棄scrap view // Otherwise, we'll have to remove the view and start over. getSkippedScrap().add(scrap); } } else { //不處於transient狀態,將scrap view加入mScarpViews中 if (mViewTypeCount == 1) { //view type count為1時,為方便,直接操作mCurrentScrap mCurrentScrap.add(scrap); } else { mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } }

scrapActiveViews: 將mActiveViews中剩余的所有view移到mScrapViews中
/**                                                                                   
 * Move all views remaining in mActiveViews to mScrapViews.                           
 */                                                                                   
void scrapActiveViews() {                                                             
    final View[] activeViews = mActiveViews;                                          
    final boolean hasListener = mRecyclerListener != null;                            
    final boolean multipleScraps = mViewTypeCount > 1;                                
                                                                                      
    ArrayList scrapViews = mCurrentScrap;                                       
    final int count = activeViews.length;                                             
    for (int i = count - 1; i >= 0; i--) {                                            
        final View victim = activeViews[i];                                           
        if (victim != null) {                                                         
            final AbsListView.LayoutParams lp                                         
                    = (AbsListView.LayoutParams) victim.getLayoutParams();            
            final int whichScrap = lp.viewType;                                       
                                                                                      
            activeViews[i] = null;                                                    
                                                                                      
            if (victim.hasTransientState()) {                                         
                // Store views with transient state for later use.                    
                victim.dispatchStartTemporaryDetach();                                
                                                                                      
                if (mAdapter != null && mAdapterHasStableIds) {                       
                    if (mTransientStateViewsById == null) {                           
                        mTransientStateViewsById = new LongSparseArray();       
                    }                                                                 
                    long id = mAdapter.getItemId(mFirstActivePosition + i);           
                    mTransientStateViewsById.put(id, victim);                         
                } else if (!mDataChanged) {                                           
                    if (mTransientStateViews == null) {                               
                        mTransientStateViews = new SparseArray();               
                    }                                                                 
                    mTransientStateViews.put(mFirstActivePosition + i, victim);       
                } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {           
                    // The data has changed, we can't keep this view.                 
                    removeDetachedView(victim, false);                                
                }                                                                     
            } else if (!shouldRecycleViewType(whichScrap)) {                          
                // Discard non-recyclable views except headers/footers.               
                if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {                  
                    removeDetachedView(victim, false);                                
                }                                                                     
            } else {                                                                  
                // Store everything else on the appropriate scrap heap.               
                if (multipleScraps) {                                                 
                    scrapViews = mScrapViews[whichScrap];                             
                }                                                                     
                                                                                      
                victim.dispatchStartTemporaryDetach();                                
                lp.scrappedFromPosition = mFirstActivePosition + i;                   
                scrapViews.add(victim);                                               
                                                                                      
                if (hasListener) {                                                    
                    mRecyclerListener.onMovedToScrapHeap(victim);                     
                }                                                                     
            }                                                                         
        }                                                                             
    }                                                                                 
                                                                                      
    pruneScrapViews();                                                                
}                                                                                     

剩下的方法由於篇幅所限,不一一貼出源碼,在此只對方法的功能做下說明:
//清除所有scrap view和transient view
void clear();

//獲取當前position下的transient view,如無則返回null
View getTransientStateView(int position);

//獲取mSkippedScrap
private ArrayList getSkippedScrap();

//清除mSkippedScrap
void removeSkippedScrap();

//裁剪mScarpViews,確保size不大於mActiveViews的size;移除已不是transient state的view
private void pruneScrapViews();

//將所有scrap views放入指定的views中,這個方法沒弄明白,不知何時調用
void reclaimScrapViews(List views);

//為每一個scrap和active view設置緩存背影色
void setCacheColorHint(int color);

//從ListView層次中移除view
private void removeDetachedView(View child, boolean animate);

    未完待續
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved