背景
在阅读ListView的测绘流程的过程中,发现ListView很多地方都用到了缓存技术,这主要是由它的父类AbsListView的内部类RecycleBin实现的,我整理了一下测绘过程中里面常用的方法,以备日后查看
fillActiveViews
这个方法是用来把当前ListView显示的全部内容缓存到mActiveViews中,代码如下
void fillActiveViews(int childCount, int firstActivePosition) { if (mActiveViews.length < childCount) { mActiveViews = new View[childCount]; } mFirstActivePosition = firstActivePosition; // listView中第一个完整显示的子view的位置 //noinspection MismatchedReadAndWriteOfArray final View[] activeViews = mActiveViews; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams(); // header和footer不缓存到这里 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; // 把子view添加进mActiveViews中 // Remember the position so that setupChild() doesn't reset state. // 更新lp.scrappedFromPosition lp.scrappedFromPosition = firstActivePosition + i; } } }
removeSkippedScrap
这个方法在adapter.getItemViewType = 1时是个摆设,代码如下
void removeSkippedScrap() { // adapter.getItemViewType() == 1 的话,这个方法就是摆设 if (mSkippedScrap == null) { return; } final int count = mSkippedScrap.size(); for (int i = 0; i < count; i++) { removeDetachedView(mSkippedScrap.get(i), false); } mSkippedScrap.clear(); }
getActiveView
用来从mActiveViews缓存中获取view,代码如下
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
用来从mCurrentScrap或mScrapView中获取view,代码如下
View getScrapView(int position) { final int whichScrap = mAdapter.getItemViewType(position); if (whichScrap < 0) { return null; } if (mViewTypeCount == 1) { // 最简单的自定义adapter走的是这一步 return retrieveFromScrap(mCurrentScrap, position); } else if (whichScrap < mScrapViews.length) { return retrieveFromScrap(mScrapViews[whichScrap], position); } return null; }
addScrapView
把view缓存到mCurrentScrap或mScrapViews中,代码如下
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)) { // 判断viewType < 0,一般viewType就是0,进不来 // 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) { // header和footer要直接和list分离,而不是进入回收站 getSkippedScrap().add(scrap); // 加入另一个数组列表mScrapViews中 } return; } scrap.dispatchStartTemporaryDetach(); // 设置标志位,清空view的回调 // 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是短暂状态的view(有标志位标记,一般都不是) .. // 忽略 } else { clearScrapForRebind(scrap); if (mViewTypeCount == 1) { mCurrentScrap.add(scrap); // 一般就这种情况,直接add进数组列表中去 } else { mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); // 通知回调 } } }
getSkippedScrap
获取mSkippedScrap,代码如下
private ArrayList<View> getSkippedScrap() { if (mSkippedScrap == null) { mSkippedScrap = new ArrayList<>(); } return mSkippedScrap; }
scrapActiveViews
把mActiveViews里的数据移到mCurrentScrap中去,代码如下
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; // 把第i个置为空 if (victim.hasTransientState()) { .. // transient_view处理,忽略 } else if (!shouldRecycleViewType(whichScrap)) { .. // view_type < 0,忽略 } else { // 一般就是这里的逻辑 // Store everything else on the appropriate scrap heap. if (multipleScraps) { // 不覆写adapter里的getViewType的话,这里走不到 scrapViews = mScrapViews[whichScrap]; } lp.scrappedFromPosition = mFirstActivePosition + i; removeDetachedView(victim, false); // 移除子view的一些处理 scrapViews.add(victim); // 给mCurrent这个回收站添加子项 if (hasListener) { // 必要的话,通知回调 mRecyclerListener.onMovedToScrapHeap(victim); } } } } pruneScrapViews(); // 处理多种viewType和transient的情况,忽略 }
shouldRecycleViewType
判断viewType是否有效,有效的才能被回收缓存,代码如下
public boolean shouldRecycleViewType(int viewType) { return viewType >= 0; }
removeDetachedView
对分离出去的view进行一些清除工作,代码如下
private void removeDetachedView(View child, boolean animate) { child.setAccessibilityDelegate(null); AbsListView.this.removeDetachedView(child, animate); }
调用了AbsListView.removeDetachedView(),实则是ViewGroup的同名方法,代码如下
protected void removeDetachedView(View child, boolean animate) { if (mTransition != null) { mTransition.removeChild(this, child); } // 清除焦点 if (child == mFocused) { child.clearFocus(); } if (child == mDefaultFocus) { clearDefaultFocus(child); } if (child == mFocusedInCluster) { clearFocusedInCluster(child); } child.clearAccessibilityFocus(); cancelTouchTarget(child); // 取消触摸事件,如果能dispatchTochEvent,就分发下去 cancelHoverTarget(child); // 鼠标移动事件,忽略 if ((animate && child.getAnimation() != null) || (mTransitioningViews != null && mTransitioningViews.contains(child))) { addDisappearingView(child); } else if (child.mAttachInfo != null) { // 走这里 child.dispatchDetachedFromWindow(); // 清理或触发一些回调 } if (child.hasTransientState()) { childHasTransientStateChanged(child, false); } dispatchViewRemoved(child); // 必要的话,触发mOnHierarchyChangeListener.onChildViewRemoved方法 }
retrieveFromScrap
从回收站中读取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. // Traverse backwards to find the most recently used scrap view for (int i = size - 1; i >= 0; i--) { final View view = scrapViews.get(i); final AbsListView.LayoutParams params = (AbsListView.LayoutParams) view.getLayoutParams(); if (mAdapterHasStableIds) { // 适配器继承自BaseAdapter的话,默认为false final long id = mAdapter.getItemId(position); if (id == params.itemId) { return scrapViews.remove(i); } } else if (params.scrappedFromPosition == position) { // 可以看到,view在回收站中的位置是保存在自己的LayoutParams里的 final View scrap = scrapViews.remove(i); // 这里返回的是最后一个位置信息符合要求的view,但不一定是正确的view clearScrapForRebind(scrap); // 无障碍处理 return scrap; } } final View scrap = scrapViews.remove(size - 1); // 找不到对应的view,就把回收站中最后一个view返回出去 clearScrapForRebind(scrap); return scrap; } else { return null; } }
markChildrenDirty
强制所有缓存的子view进行forceLayout,代码如下
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 { .. // 多种viewType处理 } .. // transient处理 }