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.
// 不要废弃掉瞬态的 View finalboolean scrapHasTransientState = scrap.hasTransientState(); if (scrapHasTransientState) { if (mAdapter != null && mAdapterHasStableIds) { // 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); } elseif (!mDataChanged) { // 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 { // Otherwise, we'll have to remove the view and start over. clearScrapForRebind(scrap); getSkippedScrap().add(scrap); } } else { clearScrapForRebind(scrap); if (mViewTypeCount == 1) { mCurrentScrap.add(scrap); } else { mScrapViews[viewType].add(scrap); }
if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } }
private ArrayListgetSkippedScrap(){ if (mSkippedScrap == null) { mSkippedScrap = new ArrayList<>(); } return mSkippedScrap; }
voidremoveSkippedScrap(){ if (mSkippedScrap == null) { return; } finalint count = mSkippedScrap.size(); for (int i = 0; i < count; i++) { removeDetachedView(mSkippedScrap.get(i), false); } mSkippedScrap.clear(); }
if (hasListener) { mRecyclerListener.onMovedToScrapHeap(victim); } } } } pruneScrapViews(); }
// layout 过程的最后,所有暂时被 detach 掉的 View 要么应该被重新 attach,或者完全被 detach。这个方法能保证在 // scrap 列表中的所有 View 都被 detach 掉 voidfullyDetachScrapViews(){ finalint viewTypeCount = mViewTypeCount; final ArrayList[] scrapViews = mScrapViews; for (int i = 0; i < viewTypeCount; ++i) { final ArrayList scrapPile = scrapViews[i]; for (int j = scrapPile.size() - 1; j >= 0; j--) { final View view = scrapPile.get(j); if (view.isTemporarilyDetached()) { removeDetachedView(view, false); } } } }
// 确保 scrap 列表的大小不要超过 active 列表的大小,因为有时 adapter 可能会不回收 view。 // 同时移除所有缓存的已经不再拥有瞬态的瞬态 view privatevoidpruneScrapViews(){ finalint maxViews = mActiveViews.length; finalint viewTypeCount = mViewTypeCount; final ArrayList[] scrapViews = mScrapViews; for (int i = 0; i < viewTypeCount; ++i) { final ArrayList scrapPile = scrapViews[i]; int size = scrapPile.size(); while (size > maxViews) { scrapPile.remove(--size); } }
final SparseArray transViewsByPos = mTransientStateViews; if (transViewsByPos != null) { for (int i = 0; i < transViewsByPos.size(); i++) { final View v = transViewsByPos.valueAt(i); if (!v.hasTransientState()) { removeDetachedView(v, false); transViewsByPos.removeAt(i); i--; } } }
final LongSparseArray transViewsById = mTransientStateViewsById; if (transViewsById != null) { for (int i = 0; i < transViewsById.size(); i++) { final View v = transViewsById.valueAt(i); if (!v.hasTransientState()) { removeDetachedView(v, false); transViewsById.removeAt(i); i--; } } } }
voidreclaimScrapViews(List views){ if (mViewTypeCount == 1) { views.addAll(mCurrentScrap); } else { finalint viewTypeCount = mViewTypeCount; final ArrayList[] scrapViews = mScrapViews; for (int i = 0; i < viewTypeCount; ++i) { final ArrayList scrapPile = scrapViews[i]; views.addAll(scrapPile); } } }
/** * Updates the cache color hint of all known views. * * @param color The new cache color hint. */ voidsetCacheColorHint(int color){ if (mViewTypeCount == 1) { final ArrayList scrap = mCurrentScrap; finalint scrapCount = scrap.size(); for (int i = 0; i < scrapCount; i++) { scrap.get(i).setDrawingCacheBackgroundColor(color); } } else { finalint typeCount = mViewTypeCount; for (int i = 0; i < typeCount; i++) { final ArrayList scrap = mScrapViews[i]; finalint scrapCount = scrap.size(); for (int j = 0; j < scrapCount; j++) { scrap.get(j).setDrawingCacheBackgroundColor(color); } } } // Just in case this is called during a layout pass final View[] activeViews = mActiveViews; finalint count = activeViews.length; for (int i = 0; i < count; ++i) { final View victim = activeViews[i]; if (victim != null) { victim.setDrawingCacheBackgroundColor(color); } } }
private View retrieveFromScrap(ArrayList scrapViews, int position){ finalint 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) { finallong id = mAdapter.getItemId(position); if (id == params.itemId) { return scrapViews.remove(i); } } elseif (params.scrappedFromPosition == position) { final View scrap = scrapViews.remove(i); clearScrapForRebind(scrap); return scrap; } } final View scrap = scrapViews.remove(size - 1); clearScrapForRebind(scrap); return scrap; } else { returnnull; } }
/** * Subclasses should NOT override this method but * {@link #layoutChildren()} instead. */ @Override protectedvoidonLayout(boolean changed, int l, int t, int r, int b){ super.onLayout(changed, l, t, r, b);
mInLayout = true;
finalint childCount = getChildCount();
// 如果 AbsListView 的大小或 position 发生了变化,则要强制所有的子 View 重新 layout if (changed) { for (int i = 0; i < childCount; i++) { getChildAt(i).forceLayout(); } // 回收站将 ScrapViews 也全部执行 forceLayout mRecycler.markChildrenDirty(); }
// Lay out child directly against the parent measure spec so that // we can obtain exected minimum width and height. measureScrapChild(child, 0, widthMeasureSpec, heightSize);
if (heightMode == MeasureSpec.AT_MOST) { // TODO: after first layout we should maybe start at the first visible position, not 0 heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1); }
// 如果是空数据,就清除所有可见的 view,将各种数据全部重置,然后返回 if (mItemCount == 0) { resetList(); invokeOnItemScrollListener(); return; } elseif (mItemCount != mAdapter.getCount()) { thrownew IllegalStateException("The content of the adapter has changed but" + "ListView did not receive a notification. Make sure the content of" + "your adapter is not modified from a background thread, but only from" + "the UI thread. Make sure your adapter calls notifyDataSetChanged()" + "when its content changes. [in ListView(" + getId() + "," + getClass() + ") with Adapter(" + mAdapter.getClass() + ")]"); }
// Remember which child, if any, had accessibility focus. This must // occur before recycling any views, since that will clear // accessibility focus. final ViewRootImpl viewRootImpl = getViewRootImpl(); if (viewRootImpl != null) { final View focusHost = viewRootImpl.getAccessibilityFocusedHost(); if (focusHost != null) { final View focusChild = getAccessibilityFocusedChild(focusHost); if (focusChild != null) { if (!dataChanged || isDirectChildHeaderOrFooter(focusChild) || (focusChild.hasTransientState() && mAdapterHasStableIds)) { // The views won't be changing, so try to maintain // focus on the current host and virtual view. accessibilityFocusLayoutRestoreView = focusHost; accessibilityFocusLayoutRestoreNode = viewRootImpl .getAccessibilityFocusedVirtualView(); }
// If all else fails, maintain focus at the same // position. accessibilityFocusPosition = getPositionForView(focusChild); } } }
// Take focus back to us temporarily to avoid the eventual call to // clear focus when removing the focused child below from messing // things up when ViewAncestor assigns focus back to someone else. final View focusedChild = getFocusedChild(); if (focusedChild != null) { // TODO: in some cases focusedChild.getParent() == null
// We can remember the focused view to restore after re-layout // if the data hasn't changed, or if the focused position is a // header or footer. if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild) || focusedChild.hasTransientState() || mAdapterHasStableIds) { focusLayoutRestoreDirectChild = focusedChild; // Remember the specific view that had focus. focusLayoutRestoreView = findFocus(); if (focusLayoutRestoreView != null) { // Tell it we are going to mess with it. focusLayoutRestoreView.dispatchStartTemporaryDetach(); } } requestFocus(); }
// 将所有 View 扔到回收站,这些 View 有可能会被重用 finalint firstPosition = mFirstPosition; final RecycleBin recycleBin = mRecycler; if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); } } else { recycleBin.fillActiveViews(childCount, firstPosition); }
final View transientView = mRecycler.getTransientStateView(position); // 1 if (transientView != null) { final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
// If the view type hasn't changed, attempt to re-bind the data. if (params.viewType == mAdapter.getItemViewType(position)) { final View updatedView = mAdapter.getView(position, transientView, this); //2
// If we failed to re-bind the data, scrap the obtained view. if (updatedView != transientView) { setItemViewLayoutParams(updatedView, position); mRecycler.addScrapView(updatedView, position); } }
outMetadata[0] = true;
// Finish the temporary detach started in addScrapView(). transientView.dispatchFinishTemporaryDetach(); return transientView; }
final View scrapView = mRecycler.getScrapView(position); // 3 final View child = mAdapter.getView(position, scrapView, this); // 4 if (scrapView != null) { if (child != scrapView) { // Failed to re-bind the data, return scrap to the heap. mRecycler.addScrapView(scrapView, position); // 5 } elseif (child.isTemporarilyDetached()) { outMetadata[0] = true;
// Finish the temporary detach started in addScrapView(). child.dispatchFinishTemporaryDetach(); } }
if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); }
if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); }
setItemViewLayoutParams(child, position);
if (AccessibilityManager.getInstance(mContext).isEnabled()) { if (mAccessibilityDelegate == null) { mAccessibilityDelegate = new ListItemAccessibilityDelegate(); } if (child.getAccessibilityDelegate() == null) { child.setAccessibilityDelegate(mAccessibilityDelegate); } }
privatevoidsetupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean isAttachedToWindow){ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");
// Respect layout params that are already in the view. Otherwise make // some up... AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams(); if (p == null) { p = (AbsListView.LayoutParams) generateDefaultLayoutParams(); } p.viewType = mAdapter.getItemViewType(position); p.isEnabled = mAdapter.isEnabled(position);
// Set up view state before attaching the view, since we may need to // rely on the jumpDrawablesToCurrentState() call that occurs as part // of view attachment. if (updateChildSelected) { child.setSelected(isSelected); }
if (updateChildPressed) { child.setPressed(isPressed); }
// If the view was previously attached for a different position, // then manually jump the drawables. if (isAttachedToWindow && (((AbsListView.LayoutParams) child.getLayoutParams()).scrappedFromPosition) != position) { child.jumpDrawablesToCurrentState(); } } else { p.forceAdd = false; if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { p.recycledHeaderFooter = true; } addViewInLayout(child, flowDown ? -1 : 0, p, true); // add view in layout will reset the RTL properties. We have to re-resolve them child.resolveRtlPropertiesIfNeeded(); }