AbsListView的缓存行为主要由内部类RecycleBin的addScrapView方法实现
这是一个缺省修饰的方法 用于回收指定position的itemView(scrap)
/**
* Puts a view into the list of scrap views.
*
* If the list data hasn't changed or the adapter has stable IDs, views
* with transient state will be preserved for later retrieval.
将视图放入废料视图列表中。
如果列表数据未更改或适配器具有稳定的ID,则将保留具有瞬态状态的视图以供以后检索。
*
* @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)) {
// 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) {
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);
} else if (!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.
getSkippedScrap().add(scrap);
}
} else {
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
}
注释 /**
* Set whether this view is currently tracking transient state that the
* framework should attempt to preserve when possible. This flag is reference counted,
* so every call to setHasTransientState(true) should be paired with a later call
* to setHasTransientState(false).
*
*
A view with transient state cannot be trivially rebound from an external
* data source, such as an adapter binding item views in a list. This may be
* because the view is performing an animation, tracking user selection
* of content, or similar.
设置此视图当前是否跟踪瞬时状态,该框架在可能时应尝试保存。这个标志是引用计数的,所以每一个电话,setHasTransientState(真正的)应该搭配之后调用setHasTransientState(假)。注意这里一定是成对出现的!!!
一种暂态观不能平凡反弹从外部数据源,如一个适配器绑定项目视图列表中。这可能是因为视图正在执行动画,跟踪用户选择内容,或类似的内容。
@param hastransientstate如果这种观点具有瞬态
豁然开朗~~~ 属性动画里的开始结束都拿这个标志位做控制
拉回主题,判断itemView完是否还有动画之后 回收策略分为两大分支:
先看有动画的:
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);
} else if (!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.
getSkippedScrap().add(scrap);
}
}
如果列表是stableIds的 就按id为key值存 在LongSparseArray类型的mTransientStateViewsById里
如果不是稳定id的就要掂量下这个数据还能不能用,这里用AbsListView的父类AdpterView中声明的列表是否数据更新了标记位mDataChanged做判断,如果!mDataChanged,即没改变,是新的可用数据,就按position为key值存在SparseArray类型的mTransientStateViews里。
查阅了一下能改变mDataChanged为true的地方有 :
notifyDataSetChanged -->observer.onChange
onFocusChanged
resetList
onSizeChanged
onAttachedToWindow
invalidateViews
setItemChecked
onRestoreInstanceState
否则mDataChanged为true,就只好废弃掉了
(注:这里源码没有直接detach并删除引用,而是将itemView加入到了一个如下列表里)
private ArrayList getSkippedScrap() {
if (mSkippedScrap == null) {
mSkippedScrap = new ArrayList<>();
}
return mSkippedScrap;
}
RecycleBin也只提供了一个清空的方法
/**
* Finish the removal of any views that skipped the scrap heap.
*/
void removeSkippedScrap() {
if (mSkippedScrap == null) {
return;
}
final int count = mSkippedScrap.size();
for (int i = 0; i < count; i++) {
removeDetachedView(mSkippedScrap.get(i), false);
}
mSkippedScrap.clear();
}
实际上也就是等待被清空。
看完了有动画的分支 接下来就是无动画的回收处理
非常简单,viewType唯一的存ArrayList
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
最后的最后
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
如果有回收行为监听,通知回调
参考引用:ListView回收机制相关分析