Android ListView复用机制详解

来源:互联网 发布:nemo软件 编辑:程序博客网 时间:2024/06/04 19:14

最近用到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的positionprivate 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<View>[] mScrapViews;// ListView item type数量private int mViewTypeCount;// 当前的废弃view数组,定义这个成员是为了在mViewTypeCount为1时使用方便,不需要去取mScrapViews的第一个元素private ArrayList<View> mCurrentScrap;// 被跳过的,不能复用的view集合。view type小于0或者处理transient状态的view不能被复用。private ArrayList<View> mSkippedScrap;// 处于transient状态的view集合,处于transient状态的view不能被复用,如view的动画正在播放,// transient是瞬时、过渡的意思,关于transient状态详见android.view.View#PFLAG2_HAS_TRANSIENT_STATEprivate SparseArray<View> mTransientStateViews;// 如果adapter的hasStableIds方法返回true,处于过度状态的view保存到这里。因为需要保存view的position,而且处于过度状态的view一般很少,// 这2个成员用了稀疏数组。具体不需要case,知道是保存转换状态view的集合就行。private LongSparseArray<View> 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 <unchecked<    //从中可看出adpater的view type应该从0开始,不然通过viewType取scarp时可能出现错误                                                            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;                                                           }                                                                                       

markChildrenDirty: 当ListView size或position变化时,设置mScrapViews和transient views的forceLayout flag,在下一次被复用时会重新布置。
public void markChildrenDirty() {                                   if (mViewTypeCount == 1) {                                          final ArrayList<View> 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<View> 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<View> 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.                                                     * <p>                                                                                           * 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)) { <strong>//不能被复用的view type,并且不为header或footer时加入skipped scrap</strong>                                                          // 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) { <strong>//view处于transient状态 </strong>                                                                   if (mAdapter != null && mAdapterHasStableIds) { <strong>//adpater has stable ids,加入mTransientStateViewsById中</strong>            // 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) { <strong>//数据未变化,加入mTransientStateViews中</strong>            // 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 { <strong>//adapter为null或adpater不为null,无stable ids,且数据变化,则丢弃scrap view  </strong>                                                                                      // Otherwise, we'll have to remove the view and start over.                                     getSkippedScrap().add(scrap);                                                               }                                                                                           } else { <strong>//不处于transient状态,将scrap view加入mScarpViews中</strong>                                                                                          if (mViewTypeCount == 1) { <strong>//view type count为1时,为方便,直接操作mCurrentScrap    </strong>                                                                  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<View> 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<View>();                           }                                                                                     long id = mAdapter.getItemId(mFirstActivePosition + i);                               mTransientStateViewsById.put(id, victim);                                         } else if (!mDataChanged) {                                                               if (mTransientStateViews == null) {                                                       mTransientStateViews = new SparseArray<View>();                                   }                                                                                     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 viewvoid clear();//获取当前position下的transient view,如无则返回nullView getTransientStateView(int position);//获取mSkippedScrapprivate ArrayList<View> getSkippedScrap();//清除mSkippedScrapvoid removeSkippedScrap();//裁剪mScarpViews,确保size不大于mActiveViews的size;移除已不是transient state的viewprivate void pruneScrapViews();//将所有scrap views放入指定的views中,这个方法没弄明白,不知何时调用void reclaimScrapViews(List<View> views);//为每一个scrap和active view设置缓存背影色void setCacheColorHint(int color);//从ListView层次中移除viewprivate void removeDetachedView(View child, boolean animate);



未完待续





1 0
原创粉丝点击