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
- Android ListView复用机制详解
- Android学习笔记之ListView复用机制详解
- android listview复用机制原理
- ListView复用机制
- listview实现机制详解
- Android ListView 加载机制
- android ListView详解
- android Listview详解
- android Listview详解
- android Listview详解
- android ListView详解
- android ListView详解
- android listview详解
- android Listview详解
- android ListView详解
- android ListView详解
- android ListView详解
- android Listview详解
- Effective Java学习笔记 第60条: 优先使用标准的异常
- getParameter和 getAttribute的区别?
- Date & Time Format
- Orcal数据库的表结构转换成mysql数据库的表结构
- leetcode题解日练--2016.9.17
- Android ListView复用机制详解
- python核心编程学习笔记-2016-09-17-02-数据库编程(二)
- 安卓中画笔工具的初步认识
- Android 图片获取:从服务器下载与缓存本地
- 猿辅导2017校园招聘笔试题 求和为0的最长连续子数组
- 信息摘要算法——MessageDigest类
- Linux基础知识
- php史上最全的正则表达式,供学习参考
- 递归和非递归实现二叉树的前序遍历