/**
* 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.
*
* @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
* @see android.widget.AbsListView.RecyclerListener
*/
class RecycleBin {
private RecyclerListener mRecyclerListener;
/**
* The position of the first view stored in mActiveViews.
*/privateint 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.
* 当前 listView 可见的 item views array
*/private View[] mActiveViews = new View[0];
/**
* Unsorted views that can be used by the adapter as a convert view.
* 对于每种 type 都进行缓存的 view list array
*/private ArrayList<View>[] mScrapViews;
privateint mViewTypeCount; // type 总数private ArrayList<View> mCurrentScrap; // 如果只有一种类型,就缓存在这里面了。private ArrayList<View> mSkippedScrap; // 非头尾,并且不应该被缓存的 view 存储的地方private SparseArray<View> mTransientStateViews; // 具有 瞬态 属性的 view 存储 (pos,view)private LongSparseArray<View> mTransientStateViewsById; // 具有 瞬态 属性的 view 存储 (itemID,view)/*
[这个方法难度级别:0]
设置 view 类型的总数。必须大于0 ,否则抛异常;
然后创建一个 ArrayList<View>[] scrapViews ,每种 type 对应的是一个数值元素,每个元素是一个 List<View> 对象。
(这里可以大致猜到,对于view回收复用,是根据不同的type来进行的。
type=1 的view 回收之后,也是给 type=1 的view 去复用;
type=2 的view 回收之后,也是给 type=2 的view 去复用的。)
默认当前的 type是 第一个,然后 对应的 List<view> 也就是数组第一个元素。
*/publicvoidsetViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
thrownew IllegalArgumentException("Can't have a viewTypeCount < 1");
}
//noinspection unchecked
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;
}
/*
[这个方法难度级别:0]
首先,把 mScrapViews 里面的全部 view 进行强制显示;
然后,把 mTransientStateViews 里面的全部 view 进行强制显示;
最后,把 mTransientStateViewsById 里面的全部 view 进行强制显示;
*/publicvoidmarkChildrenDirty() {
if (mViewTypeCount == 1) {
final ArrayList<View> scrap = mCurrentScrap;
finalint scrapCount = scrap.size();
for (int i = 0; i < scrapCount; i++) {
scrap.get(i).forceLayout();
}
} else {
finalint typeCount = mViewTypeCount;
for (int i = 0; i < typeCount; i++) {
final ArrayList<View> scrap = mScrapViews[i];
finalint scrapCount = scrap.size();
for (int j = 0; j < scrapCount; j++) {
scrap.get(j).forceLayout();
}
}
}
if (mTransientStateViews != null) {
finalint count = mTransientStateViews.size();
for (int i = 0; i < count; i++) {
mTransientStateViews.valueAt(i).forceLayout();
}
}
if (mTransientStateViewsById != null) {
finalint count = mTransientStateViewsById.size();
for (int i = 0; i < count; i++) {
mTransientStateViewsById.valueAt(i).forceLayout();
}
}
}
/*
[这个方法难度级别:0]
type >=0 的,都返回 true。 也就是任何的type 都要被缓存复用。
但是 ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2; ,估计是 header,footer view 不会被缓存复用
*/publicbooleanshouldRecycleViewType(int viewType) {
return viewType >= 0;
}
/**
[这个方法难度级别:1]
* Clears the scrap heap.
* 先是对每个保存在 mScrapViews 里面的 view 全部调用 clearScrap(view)方法,
* 然后是调用 clearTransientStateViews();
*/void clear() {
if (mViewTypeCount == 1) {
final ArrayList<View> scrap = mCurrentScrap;
clearScrap(scrap);
} else {
finalint typeCount = mViewTypeCount;
for (int i = 0; i < typeCount; i++) {
final ArrayList<View> scrap = mScrapViews[i];
clearScrap(scrap);
}
}
clearTransientStateViews();
}
/**
[这个方法难度级别:1]
* 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
* fillActiveViews 理解起来也很简单:
* 1. 根据传入的 count, 创建一个数组,长度为 count , 赋值给 mActiveViews
* 2. 遍历子view ,把子view 全部存放到数组 mActiveViews 里面
* 3. 同时,设置 lp.scrappedFromPosition 的值。(该值具体是做什么的,先不管)
* (注意,这里遍历子view,不包含 header 和 footer 。)
*/void fillActiveViews(int childCount, int firstActivePosition) {
if (mActiveViews.length < childCount) {
mActiveViews = new View[childCount];
}
mFirstActivePosition = firstActivePosition;
//noinspection MismatchedReadAndWriteOfArrayfinal 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 heapif (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;
}
}
}
/**
[这个方法难度级别:0]
* 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
根据 pos 去获取 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;
}
returnnull;
}
/*
[这个方法难度级别:1]
1. 根据 pos 去获取集合 mTransientStateViewsById 里面对应的 view, 并从集合中移除该view.并返回该 view
2. 如果mTransientStateViewsById 里面没有,就去集合 mTransientStateViews 里面获取,获取到了,就从集合里面移除该view , 并返回该 view
3. 如果以上两个集合里面都没有,就返回空
*/
View getTransientStateView(int position) {
if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
long id = mAdapter.getItemId(position);
View result = mTransientStateViewsById.get(id);
mTransientStateViewsById.remove(id);
return result;
}
if (mTransientStateViews != null) {
finalint index = mTransientStateViews.indexOfKey(position);
if (index >= 0) {
View result = mTransientStateViews.valueAt(index);
mTransientStateViews.removeAt(index);
return result;
}
}
returnnull;
}
/**
[这个方法难度级别:0]
* Dumps and fully detaches any currently saved views with transient
* state.
* 1. 对 mTransientStateViews 里面的每个 view 调用 viewgroup. removeDetachedView(view,animate); 并从集合中移除 该 view
* 2. 对 mTransientStateViewsById 执行同样的处理。
*/void clearTransientStateViews() {
final SparseArray<View> viewsByPos = mTransientStateViews;
if (viewsByPos != null) {
finalint N = viewsByPos.size();
for (int i = 0; i < N; i++) {
removeDetachedView(viewsByPos.valueAt(i), false);
}
viewsByPos.clear();
}
final LongSparseArray<View> viewsById = mTransientStateViewsById;
if (viewsById != null) {
finalint N = viewsById.size();
for (int i = 0; i < N; i++) {
removeDetachedView(viewsById.valueAt(i), false);
}
viewsById.clear();
}
}
/**
[这个方法难度级别:0]
* @return A view from the ScrapViews collection. These are unordered.
* 如果 type <0 (比如 footer/header == -2) , 就返回空。
* 然后是看当前的type count ,如果是1,说明只有一种类型,就是当前类型。
* 那就从当前类型的缓存里面去获取view;
* 如果不是一种类型,就是对应的类型里面去获取 view.
* [如果没有获取到,就返回空]
*/
View getScrapView(int position) {
finalint whichScrap = mAdapter.getItemViewType(position);
if (whichScrap < 0) {
returnnull;
}
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} elseif (whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
returnnull;
}
/**
[这个方法难度级别:2]
* 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
* 这个方法比较啰嗦。各种煞笔的判断。
* 1. 如果获取 lp 为空,就直接返回,不添加 该 scrap view
* 2. 如果 view type <0 , (走 if(!shouldXX)里面的逻辑,)此时,如果 type 不是 head/footer 类型的,
* 就添加到 mSkippedScrap 数组里面。然后返回。 此时也没有添加 scrap view
* 3. 判断 scrap view 是 scrapHasTransientState , 如果是,并且 mAdapterHasStableIds,
* 就把 该 view 添加到 mTransientStateViewsById 里面,
* 如果非 mAdapterHasStableIds,并且非 mDataChanged ,
* 就把该 view 添加到 mTransientStateViews 里面。
* 4. 如果 非 scrapHasTransientState , 就把该view 添加到 mScrapViews 里面,或者 mCurrentViews 里面。
*/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.finalint viewType = lp.viewType;
if (!shouldRecycleViewType(viewType)) {
// 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.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 ArrayList<View> getSkippedScrap() {
if (mSkippedScrap == null) {
mSkippedScrap = new ArrayList<>();
}
return mSkippedScrap;
}
/**
* Finish the removal of any views that skipped the scrap heap.
* 将 mSkippedScrap 里面的全部 view 调用 viewgroup. removeDetachedView(view,animate); 并清空该集合
*/void removeSkippedScrap() {
if (mSkippedScrap == null) {
return;
}
finalint count = mSkippedScrap.size();
for (int i = 0; i < count; i++) {
removeDetachedView(mSkippedScrap.get(i), false);
}
mSkippedScrap.clear();
}
/**
* Move all views remaining in mActiveViews to mScrapViews.
* 把 mActiveViews 缓存到 mScrapViews 里面 (需要满足一定条件)
* 1. 首先是遍历 mActiveViews 这个数组,得到每一个 view(是反向遍历的,从最后一个元素开始的)
* 2. 如果这个 view 是空,就下一个 view 了,没有任何的逻辑
* 3. 如果不为空,并且非 hasTransientState 并且viewType> 0,就把 view 添加到mScrapViews里面 (mScrapViews[type].add(view);)
最后,遍历结束了,执行一下 pruneScrapViews();(如果缓存多了,就丢掉多余的)
*/void scrapActiveViews() {
final View[] activeViews = mActiveViews;
finalboolean hasListener = mRecyclerListener != null;
finalboolean multipleScraps = mViewTypeCount > 1;
ArrayList<View> scrapViews = mCurrentScrap;
finalint 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();
finalint 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);
} elseif (!mDataChanged) {
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArray<View>();
}
mTransientStateViews.put(mFirstActivePosition + i, victim);
} elseif (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
// The data has changed, we can't keep this view.
removeDetachedView(victim, false);
}
} elseif (!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];
}
lp.scrappedFromPosition = mFirstActivePosition + i;
removeDetachedView(victim, false);
scrapViews.add(victim);
if (hasListener) {
mRecyclerListener.onMovedToScrapHeap(victim);
}
}
}
}
pruneScrapViews();
}
/**
* At the end of a layout pass, all temp detached views should either be re-attached or
* completely detached. This method ensures that any remaining view in the scrap list is
* fully detached.
* 遍历mScrapViews,得到每一个 view, 对其调用 viewgroup. removeDetachedView(view,animate);
*/void fullyDetachScrapViews() {
finalint viewTypeCount = mViewTypeCount;
final ArrayList<View>[] scrapViews = mScrapViews;
for (int i = 0; i < viewTypeCount; ++i) {
final ArrayList<View> scrapPile = scrapViews[i];
for (int j = scrapPile.size() - 1; j >= 0; j--) {
final View view = scrapPile.get(j);
if (view.isTemporarilyDetached()) {
removeDetachedView(view, false);
}
}
}
}
/**
* Makes sure that the size of mScrapViews does not exceed the size of
* mActiveViews, which can happen if an adapter does not recycle its
* views. Removes cached transient state views that no longer have
* transient state.
* 1. 获取 mActiveViews.length
* 2. 遍历 mScrapViews ,发现只要有长度超过 mActiveViews.length 的,就剪短长度到等于 mActiveViews.length 为止。
* 3. 遍历 mTransientStateViews , 发现只要里面的view 并不是 v.hasTransientState 的,就移除该 view.
* 4. 遍历 mTransientStateViewsById ,发现只要里面的view 并不是 v.hasTransientState 的,就移除该 view.
*/privatevoidpruneScrapViews() {
finalint maxViews = mActiveViews.length;
finalint viewTypeCount = mViewTypeCount;
final ArrayList<View>[] scrapViews = mScrapViews;
for (int i = 0; i < viewTypeCount; ++i) {
final ArrayList<View> scrapPile = scrapViews[i];
int size = scrapPile.size();
while (size > maxViews) {
scrapPile.remove(--size);
}
}
final SparseArray<View> 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<View> 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--;
}
}
}
}
/**
* Puts all views in the scrap heap into the supplied list.
这个方法就很有意思了。
如果当前只要一种 type , 就把缓存的这种类型的view ,全部添加到 views 里面;
如果当前有N种 type , 就把每种的 缓存view ,全部添加 到 views 里面去。
*/void reclaimScrapViews(List<View> views) {
if (mViewTypeCount == 1) {
views.addAll(mCurrentScrap);
} else {
finalint viewTypeCount = mViewTypeCount;
final ArrayList<View>[] scrapViews = mScrapViews;
for (int i = 0; i < viewTypeCount; ++i) {
final ArrayList<View> scrapPile = scrapViews[i];
views.addAll(scrapPile);
}
}
}
/**
* Updates the cache color hint of all known views.
*
* @param color The new cache color hint.
对 mCurrentScrap,mScrapViews,activeViews 这3个容器里面的每个 view 调用 view.setDrawingCacheBackgroundColor(color)
*/void setCacheColorHint(int color) {
if (mViewTypeCount == 1) {
final ArrayList<View> 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<View> 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 passfinal 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);
}
}
}
/*
1. 如果传入的 scrapViews size<=0 , 返回空;
2. 如果是 mAdapterHasStableIds,并且根据 pos 获取的 adapterID==params.itemId , 就 从集合中移除该view,并返回该view
3. 如果非 mAdapterHasStableIds,params.scrappedFromPosition == position , 就从集合中移除该view,并返回该view。
4. 如果 2,3 都不满足,就获取集合里面最后一个view,返回该view。
*/private View retrieveFromScrap(ArrayList<View> 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 viewfor (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;
}
}
/*
对传入的 ArrayList<View> scrap , 进行遍历,移除 scrap 里面的每个元素,
并对每个元素(view) ,调用 removeDetachedView(view)方法。
*/privatevoidclearScrap(final ArrayList<View> scrap) {
finalint scrapCount = scrap.size();
for (int j = 0; j < scrapCount; j++) {
// 调用 viewgroup.removeDetachedView(child,animate);
removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
}
}
/*
清除状态
*/privatevoidclearScrapForRebind(View view) {
view.clearAccessibilityFocus();
view.setAccessibilityDelegate(null);
}
/*
调用 viewgroup.removeDetachedView(child,animate);
*/privatevoidremoveDetachedView(View child, boolean animate) {
child.setAccessibilityDelegate(null);
AbsListView.this.removeDetachedView(child, animate);
}
}