AbsListView的重用机制——“存”方法分析

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);
                }
            }
        }

方法里先判断  boolean scrapHasTransientState = scrap.hasTransientState(); 这里的View下的hasTransientState方法是个用于标记是否处于动画中的标记位方法,我们来看一下对应的set方法:

AbsListView的重用机制——“存”方法分析_第1张图片

注释 /**
     * 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.


     *
     * @param hasTransientState true if this view has transient state
     */

设置此视图当前是否跟踪瞬时状态,该框架在可能时应尝试保存。这个标志是引用计数的,所以每一个电话,setHasTransientState(真正的)应该搭配之后调用setHasTransientState(假)。注意这里一定是成对出现的!!!
一种暂态观不能平凡反弹从外部数据源,如一个适配器绑定项目视图列表中。这可能是因为视图正在执行动画,跟踪用户选择内容,或类似的内容。
@param hastransientstate如果这种观点具有瞬态

再来看一下这个所谓的成对调用 到底跟动画有嘛关系?AbsListView的重用机制——“存”方法分析_第2张图片

豁然开朗~~~ 属性动画里的开始结束都拿这个标志位做控制


拉回主题,判断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;
        }

这里不是特别理解,这么做有什么用。 事实上这个列表并没有再输出itemView的方法

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 mCurrentScrap里;不唯一的话也存在ArrayList里 不过要按viewType下标放在mScrapViews数组里

    if (mViewTypeCount == 1) {
                    mCurrentScrap.add(scrap);
                } else {
                    mScrapViews[viewType].add(scrap);
                }

最后的最后

	if (mRecyclerListener != null) {
                    mRecyclerListener.onMovedToScrapHeap(scrap);
                }
如果有回收行为监听,通知回调


参考引用:ListView回收机制相关分析




你可能感兴趣的:(Android源码分析)