ListView源码分析

1.概述

本文是阅读
http://www.cnblogs.com/qiengo/p/3628235.html
http://blog.csdn.net/iispring/article/details/50967445
http://blog.csdn.net/guolin_blog/article/details/44996879
http://www.jianshu.com/p/9c603a11b0c9
https://my.oschina.net/lorcan/blog/539215
的读书笔记
代码是 android N 的,记录下方便记忆。

ListView 继承结构 如下图
这里写图片描述

AbsListView 的子类(ListView,GridView)都有RecyleBin机制 可以 防止 OOM,先来看看 RecyleBin,接着看 ListView 的原理(ListView 最终集成自 View,View的子类 主要看 onMeasure()、onLayout(), onDraw())

2. RecyleBin

2.1 概述

RecyleBin是view的回收站。

    /**
     * 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,以便在下个layout中使用来避免创建新的
     * @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.
         */
        private int 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.
         */
        private View[] mActiveViews = new View[0];

        /**
         * Unsorted views that can be used by the adapter as a convert view.
         */
        private ArrayList<View>[] mScrapViews;
        ...
    }

ListView中有很多view,屏幕上能看到的view是onScreenView,是RecyleBin中的ActiveView,滑出屏幕的view是OffScreen的View,是RecyleBin中的ScrapView,scrap是废弃的意思。

ListView把scrapView全部删除,就不用绘制看不见的view了。ListView会把这些删除的ScrapView放入到RecycleBin中存起来,就像把暂时无用的资源放到回收站一样。

当ListView的底部需要显示新的View的时候,会从RecycleBin中取出一个ScrapView,将其作为convertView参数传递给Adapter的getView方法,从而达到View复用的目的,这样就不必在Adapter的getView方法中执行LayoutInflater.inflate()方法了。

RecyleBin 是防止 ListView 加载数据出现 OOM 的重要原因之一,RecyleBin 是 AbsListView 的内部类,RecycleBin的作用是帮助布局中的View的重用,它存储了两种类型的View:

  • mActiveViews 存储的是OnScreen的View,这些View很有可能被直接复用
  • mScrapViews 存储的是OffScreen的View,这些View主要是用来间接复用的,这就是传回getView中covertView的来源

2.2 RecycleBin变量

2.2.1 mRecyclerListener

RecyclerListener只有一个方法onMovedToScrapHeap(),addScrapView() 和 scrapActiveViews() 时,使用了该变量,如果注册了RecyclerListener,就调用onMovedToScrapHeap(),表示该view不再显示,这个view被回收到了scrap heap,该函数处理回收时view中的资源释放。

2.2.2 mFirstActivePosition

The position of the first view stored in mActiveViews.
存储在mActiveViews中的第一个view的位置,即AdapterView.getFirstVisiblePosition()返回值。

    /**
     * Returns the position within the adapter's data set for the first item
     * displayed on screen.
     *
     * @return The position within the adapter's data set
     */
    public int getFirstVisiblePosition() {
        return mFirstPosition;
    }

2.2.3 mActiveViews

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.
布局开始时屏幕显示的view,这个数组会在布局开始时填充,布局结束后所有view被移至mScrapViews。

2.2.4 mScrapViews

Unsorted views that can be used by the adapter as a convert view.
这个ArrayList就是adapter中getView方法中的参数convertView的来源。注意:这里是一个数组,因为如果adapter中数据有多种类型,那么就会有多个ScrapViews

2.2.5 mViewTypeCount

view类型总数,列表中可能有多种数据类型,比如内容数据和分割符

2.2.6 mCurrentScrap

private ArrayList mCurrentScrap;
默认情况下,mCurrentScrap = scrapViews[0];

2.2.7 mTransientStateViews

If the data hasn’t changed, we can reuse the views at their old positions.

2.2.8 mTransientStateViewsById

If the adapter has stable IDs,we can reuse the view forthe same data.

2.2.9 mSkippedScrap

addScrapView()中,如果view不能添加到 mTransientStateViews,mTransientStateViewsById 中,就添加到 mSkippedScrap 中
Otherwise, we’ll have to remove the view and start over.

2.3 RecycleBin方法

2.3.1 markChildrenDirty()

为每个子类调用forceLayout()。将mScrapView中回收回来的View设置一样标志,在下次被复用到ListView中时,告诉viewroot重新layout该view。forceLayout()方法只是设置标志,并不会通知其parent来重新layout。

        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 {
                final int typeCount = mViewTypeCount;
                for (int i = 0; i < typeCount; i++) {
                    final ArrayList<View> scrap = mScrapViews[i];
                    final int scrapCount = scrap.size();
                    for (int j = 0; j < scrapCount; j++) {
                        scrap.get(j).forceLayout();
                    }
                }
            }
            if (mTransientStateViews != null) {
                final int count = mTransientStateViews.size();
                for (int i = 0; i < count; i++) {
                    mTransientStateViews.valueAt(i).forceLayout();
                }
            }
            if (mTransientStateViewsById != null) {
                final int count = mTransientStateViewsById.size();
                for (int i = 0; i < count; i++) {
                    mTransientStateViewsById.valueAt(i).forceLayout();
                }
            }
        }

2.3.2 shouldRecycleViewType()

判断给定的view的viewType指明是否可以回收回。viewType < 0可以回收。指定忽略的( ITEM_VIEW_TYPE_IGNORE = -1),或者是 HeaderView / FootView(ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2)是不被回收的。如有特殊需要可以将自己定义的viewType设置为-1,否则,将会浪费内存,导致OOM

        public boolean shouldRecycleViewType(int viewType) {
            return viewType >= 0;
        }

2.3.3 clear()

清空废弃view堆,并将这些View从窗口中Detach

        /**
         * Clears the scrap heap.
         */
        void clear() {
            if (mViewTypeCount == 1) {
                final ArrayList<View> scrap = mCurrentScrap;
                clearScrap(scrap);
            } else {
                final int typeCount = mViewTypeCount;
                for (int i = 0; i < typeCount; i++) {
                    final ArrayList<View> scrap = mScrapViews[i];
                    clearScrap(scrap);
                }
            }

            clearTransientStateViews();
        }

2.3.4 fillActiveViews()

用AbsListView.的所有子view填充ActiveViews。从代码看该方法的处理逻辑为将当前AbsListView的0-childCount个子类中的非header、footer的view添加到mActiveViews数组中。当Adapter中的数据个数未发生变化时(用户滚动,或点击等操作),ListView中item的个数会发生变化,因此,需要将可视的item加入到mActiveView中来管理

        /**
         * Fill ActiveViews with all of the children of the AbsListView.
         * 
         * @param childCount The minimum number of views mActiveViews should hold . mActiveViews应该保存的最少的view数
         * @param firstActivePosition The position of the first view that will be stored in
         *        mActiveViews. mActiveViews中存储的首个view的位置,第一个可见的view的位置
         */
        void fillActiveViews(int childCount, int firstActivePosition) {
            if (mActiveViews.length < childCount) {
                mActiveViews = new View[childCount];
            }
            mFirstActivePosition = firstActivePosition;

            //noinspection MismatchedReadAndWriteOfArray
            final 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 heap
                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;
                    // Remember the position so that setupChild() doesn't reset state.
                    lp.scrappedFromPosition = firstActivePosition + i;
                }
            }
        }

2.3.5 getActiveView()

获取mActiveViews中指定位置的view,如果找到会将该view从mActiveViews中移除。position是adapter中的绝对下标值,mFirstActivePosition前面说过了,是当前可视区域的下标值,对应在adapter中的绝对值,如果找到,则返回找到的View,并将mActiveView对应的位置设置为null

        /**
         * Get the view corresponding to the specified position. The view will be removed from
         * mActiveViews if it is found.
         * 根据position在mActiveViews中查找view
         * @param position The position to look up in mActiveViews
         * @return The view if it is found, null otherwise
         */
        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; // 找到了把该view从mActiveViews中移除,mActiveViews中的View不能重复利用
                return match;
            }
            return null;
        }

2.3.6 clearTransientStateViews()

清掉当前处于transient(瞬时)状态的所有保存的view。内部为mTransientStateViews和mTransientStateViewsById的clear()调用

        /**
         * Dumps and fully detaches any currently saved views with transient
         * state.
         */
        void clearTransientStateViews() {
            final SparseArray<View> viewsByPos = mTransientStateViews;
            if (viewsByPos != null) {
                final int 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) {
                final int N = viewsById.size();
                for (int i = 0; i < N; i++) {
                    removeDetachedView(viewsById.valueAt(i), false);
                }
                viewsById.clear();
            }
        }

2.3.7 addScrapView()

将view放入scrapview list中,有transient状态的view不会被scrap(废弃),会被加入mTransientStateViewsById,mTransientStateViews 或 调用getSkippedScrap() 加入mSkippedScrap中

        /**
         * 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.
         * 缓存废弃的view,RecycleBin当中使用 mScrapViews 和 mCurrentScrap 这两个List来存储废弃View
         * @param scrap The view to add
         * scrap是要添加的view
         * 
         * @param position The view's position within its parent
         * position是view在父类中的位置
         */
        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;
            }

            // 设置它的scrappedFromPosition,然后从窗口中detach该view,并根据viewType加入到mScrapView中
            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) {
                // RecyclerListener可以通过AbsListView.setRecyclerListener()设置
                    mRecyclerListener.onMovedToScrapHeap(scrap);
                }
            }
        }

2.3.8 getScrapView()

        /**
         * @return A view from the ScrapViews collection. These are unordered.
         * 从废弃缓存( mCurrentScrap 或 mScrapViews )中取出一个View,废弃缓存中的View是无序的
         */
        View getScrapView(int position) {
            final int whichScrap = mAdapter.getItemViewType(position);
            if (whichScrap < 0) {
                return null;
            }
            if (mViewTypeCount == 1) {
                return retrieveFromScrap(mCurrentScrap, position); // 从末尾返回一个View
            } else if (whichScrap < mScrapViews.length) {
                return retrieveFromScrap(mScrapViews[whichScrap], position);
            }
            return null;
        }

2.3.9 retrieveFromScrap()

        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.
                for (int i = 0; i < size; i++) {
                    final View view = scrapViews.get(i);
                    final AbsListView.LayoutParams params =
                            (AbsListView.LayoutParams) view.getLayoutParams();

                    if (mAdapterHasStableIds) {
                        final long id = mAdapter.getItemId(position);
                        if (id == params.itemId) {
                            return scrapViews.remove(i);
                        }
                    } else if (params.scrappedFromPosition == position) {
                        // params.scrappedFromPosition == position,返回该view
                        final View scrap = scrapViews.remove(i);
                        clearAccessibilityFromScrap(scrap);
                        return scrap;
                    }
                }
                final View scrap = scrapViews.remove(size - 1);// 从末尾返回一个View
                clearAccessibilityFromScrap(scrap);
                return scrap;
            } else {
                // 缓存中没有view,返回null
                return null;
            }
        }
  • 返回null的情况举例:
    ListView第一次加载完整屏item,此时mScrapView中是没有缓存view的,新的view将会显示,此时listview会调用Adapter.getView,但是缓存中没有,因此convertView是null,所以,我们得分配一块内存来创建新的convertView;

  • 返回scrapViews最后一个view的情况:
    接着上边的情况,我们继续向上滚动,第一个view完全移出屏幕(假设还没有加载新的item),此时,第一个view就会被detach,并被加入到mScrapView中;然后,我们还继续向上滚动,直接后面又将要显示新的item view时,此时,系统会从mScrapView中找position对应的View,显然是找不到的,则将从mScrapView中,取最后一个缓存的view传递给convertView

  • 根据position返回view:
    接着上边的情况,第一个被完全移出 加入到mScrapView中,假设此时还没有新增的item到listview中,此时缓存中就只有第一个view;然后,现在向下滑动,则之前的第一个item,将被显示出来,此时,从缓存中查找position对应的view有没有,当然,肯定是找到了,就直接返回了

2.3.10 removeSkippedScrap()

清空 mSkippedScrap

        /**
         * 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();
        }

2.3.11 scrapActiveViews()

Move all views remaining in mActiveViews to mScrapViews.
将mActiveView中未使用的view回收(因为,此时已经移出可视区域了)。会调用mRecyclerListener.onMovedToScrapHeap(scrap);回收view的资源

2.3.12 pruneScrapViews()

scrapActiveViews()调用了该方法,
pruneScrapViews() 确保mScrapViews 的数目不会超过mActiveViews的数目 (This can happen if an adapter does not recycle its views)。
mScrapView中每个ScrapView数组大小不应该超过mActiveView的大小,如果超过,系统认为程序并没有复用convertView,而是每次都是创建一个新的view,为了避免产生大量的闲置内存且增加OOM的风险,系统会在每次回收后,去检查一下,将超过的部分释放掉,节约内存降低OOM风险。

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

2.3.13 reclaimScrapViews()

将mScrapView中所有的缓存view全部添加到指定的view list中,只看到有AbsListView.reclaimViews有调用到,但没有其它方法使用这个函数,可能在特殊情况下会使用到,但目前从framework中,看不出来。

        /**
         * Puts all views in the scrap heap into the supplied list.
         */
        void reclaimScrapViews(List<View> views) {
            if (mViewTypeCount == 1) {
                views.addAll(mCurrentScrap);
            } else {
                final int viewTypeCount = mViewTypeCount;
                final ArrayList<View>[] scrapViews = mScrapViews;
                for (int i = 0; i < viewTypeCount; ++i) {
                    final ArrayList<View> scrapPile = scrapViews[i];
                    views.addAll(scrapPile);
                }
            }
        }

2.3.14 setCacheColorHint()

Updates the cache color hint of all known views.
更新view的缓存颜色提示setDrawingCacheBackgroundColor。为所有的view绘置它们的背景色。

3.加载第一屏,第一次 layout

界面显示一个view,经过三个阶段:onMeasure()->onLayout()->onDraw()

  • onMeasure()用于测量View的大小,占用的大小通常是整个屏幕
  • onDraw()用于将View绘制到界面上
  • onLayout()用于确定View的布局

AdapterView继承自ViewGroup,ViewGroup通过addView()添加View,AdapterView重写了addView()方法,禁用了该方法:
android\widget\AdapterView.java中

    /**
     * This method is not supported and throws an UnsupportedOperationException when called.
     *
     * @param child Ignored.
     *
     * @throws UnsupportedOperationException Every time this method is invoked.
     */
    @Override
    public void addView(View child) {
        throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
    }

AdapterView把addView方法给禁用了,那么ListView怎么向其中添加child呢?通过onLayout中调用layoutChildren(),AbsListView的主要实现也在 onLayout中,

layoutChildren()关于RecyleBin主要干了3件事:

  1. ListView的children放到RecycleBin中
  2. ListView清空children
  3. RecycleBin中缓存的view复用,变成ListView的children

下面来看 onLayout 流程
这里写图片描述

3.1 AbsListView.onLayout()

    /**
     * Subclasses should NOT override this method but
     *  {@link #layoutChildren()} instead.
     * 做了2件事:
     * 1.判断 changed,如果ListView的大小或者位置发生了变化,子布局重绘
     * 2.调用子类的 layoutChildren()
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        mInLayout = true;

        final int childCount = getChildCount();
        if (changed) {
            // 如果变化了(大小或位置),子布局重绘
            for (int i = 0; i < childCount; i++) {
                getChildAt(i).forceLayout();
            }
            mRecycler.markChildrenDirty();
        }

        layoutChildren(); // 由子类实现
        mInLayout = false;

        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;

        // TODO: Move somewhere sane. This doesn't belong in onLayout().
        if (mFastScroll != null) {
            mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
        }
    }



    /**
     * Subclasses must override this method to layout their children.
     */
    protected void layoutChildren() {
    }

onLayout() 代码不多,判断 changed,如果ListView的大小或者位置发生了变化,子布局重绘,调用子类的 layoutChildren()

3.2 ListView.layoutChildren()

    @Override
    protected void layoutChildren() {
        ...
        final int childCount = getChildCount();// 第一次加载时,ListView中还没有item,getChildCount() 返回0
        ...

        // Pull all children into the RecycleBin.
        // These views will be reused if possible
        final int firstPosition = mFirstPosition;// //mFirstPosition是ListView的成员变量,存储着第一个显示的child所对应的adapter的position
        final RecycleBin recycleBin = mRecycler;
        // 如果Adapter调用了notifyDataSetChanged方法,那么AdapterView就会知道Adapter的数据源发生了变化,此时dataChanged变量就为true
        if (dataChanged) {
            for (int i = 0; i < childCount; i++) {
                // 这里如果数据发生了改变,将把所有现在的child放到scrapView中
                recycleBin.addScrapView(getChildAt(i), firstPosition+i);
            }
        } else {
            // 如果数据没有发生改变,将把所有现在的child放到activeViews中
            // 数据没有变化时,走这里 缓存子view,第一次加载时 ListView中没有子view,该行不起作用
            recycleBin.fillActiveViews(childCount, firstPosition);
        }

        // Clear out old views
        detachAllViewsFromParent();
        recycleBin.removeSkippedScrap();

        // 根据不同情况 增删子view,这些case的代码逻辑大部分最终调用了fillDown、fillUp等方法
        // mLayoutMode的默认值是 LAYOUT_NORMAL, 走 default
        switch (mLayoutMode) {
        ...
            default:
                if (childCount == 0) {
                    // 第一次进入时 childCount = 0
                    if (!mStackFromBottom) {
                        final int position = lookForSelectablePosition(0, true);
                        setSelectedPositionInt(position);
                        sel = fillFromTop(childrenTop);
                    } else {
                       ...
                    }
                } else {
                ...
                }
                break;
        }

        // Flush any cached views that did not get reused above
        recycleBin.scrapActiveViews();//废弃activeViews,会把activeview给废弃并移入scrapview中
        ...
    }

layoutChildren() 调用 fillFromTop() 加载 item 的布局

3.3 ListView.fillFromTop()

    /**
     * Fills the list from top to bottom, starting with mFirstPosition
     * 从 mFirstPosition开始,从上到下 填充
     * @param nextTop The location where the top of the first item should be
     *        drawn. 第一个子元素顶部距离整个ListView顶部的像素值
     *
     * @return The view that is currently selected
     */
    private View fillFromTop(int nextTop) {
        mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
        mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
        if (mFirstPosition < 0) {
            mFirstPosition = 0;
        }
        // 确定 mFirstPosition,调用 fillDown()
        return fillDown(mFirstPosition, nextTop);
    }

fillFromTop() 确定 mFirstPosition,调用 fillDown(),没做具体加载 子布局的工作,来看 fillDown()

3.4 ListView.fillDown()

fillDown用子View从指定的position自上而下填充ListView,,fillUp则是自下而上填充

    /**
     * Fills the list from pos down to the end of the list view.
     *
     * @param pos The first position to put in the list
     * pos 表示列表中第一个要绘制的item的position,其对应着Adapter中的索引
     *
     * @param nextTop The location where the top of the item associated with pos
     *        should be drawn
     * nextTop表示第一个要绘制的item在ListView中实际的位置, 
     * 
     * @return The view that is currently selected, if it happens to be in the
     *         range that we draw.
     */
    private View fillDown(int pos, int nextTop) {
        View selectedView = null;

        int end = (mBottom - mTop); // end是ListView底部减去顶部所得的像素值,是ListView的高度
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            end -= mListPadding.bottom;
        }

        // nextTop 是下一个子item的头部,end是listveiw的底部的高度,
        // 比较nextTop 和 end,判断是否继续填充下一个item,nextTop < end确保了我们只要将新增的子View能够覆盖ListView的界面,nextTop >= end 时,子元素超出屏幕
        // mItemCount 是Adapter 元素的数量,pos < mItemCount确保了我们新增的子View在Adapter中都有对应的数据源item,pos >= mItemCount 时,adapter元素都被遍历完了
        while (nextTop < end && pos < mItemCount) {
            // is this the selected item?
            boolean selected = pos == mSelectedPosition;
            // 将pos和nextTop传递给makeAndAddView方法
            View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
            // child的bottom值表示的是该child的底部到ListView顶部的距离,将该child的bottom作为下一个child的top,nextTop保存着下一个child的top值
            nextTop = child.getBottom() + mDividerHeight;
            if (selected) {
                selectedView = child;
            }
            pos++; // 循环一次,pos加1,position指针下移
        }

        setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
        return selectedView;
    }

fillDown() 是加载item 布局的主要实现,while循环 保证了 只加载一屏,超出的数据不会加载

  • nextTop < end确保了我们只要将新增的子View能够覆盖ListView的界面就可以了,比如ListView的高度最多显示10个子View,我们没必要向ListView中加入11个子View。
  • pos < mItemCount确保了我们新增的子View在Adapter中都有对应的数据源item,比如ListView的高度最多显示10个子View,但是我们Adapter中一共才有5条数据,这种情况下只能向ListView中加入5个子View,从而不能填充满ListView的全部高度。

再看看 每个item的 布局 是怎么加载的

3.5 ListView.makeAndAddView()

该方法会创建View, 返回这个View作为child,,并把该子View添加到ListView的children中

    /**
     * Obtain the view and add it to our list of children. The view can be made
     * fresh, converted from an unused view, or used as is if it was in the
     * recycle bin.
     *
     * @param position Logical position in the list
     * position表示的是数据源item在Adapter中的索引
     * 
     * @param y Top or bottom edge of the view to add
     * y表示要生成的View的top值或bottom值
     * 
     * @param flow If flow is true, align top edge to y. If false, align bottom
     *        edge to y.
     * flow是true,那么y表示top值,否则表示bottom值
     * 
     * @param childrenLeft Left edge where children should be positioned
     * @param selected Is this position selected?
     * @return View that was added
     */
    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        View child;

        if (!mDataChanged) {
            // 如果数据没有变化,尝试用该position从RecycleBin的mActiveViews中获取可复用的View
            // Try to use an existing view for this position
            child = mRecycler.getActiveView(position);
            // 第一次进入时 ,child 是null
            if (child != null) {
                // 如果child 不为空,说明我们找到了一个已经存在的child,
                // 这样mActiveViews中存储的View就被直接复用了
                // 调用setupChild,对child进行定位
                // Found it -- we're using an existing child
                // This just needs to be positioned
                setupChild(child, position, y, flow, childrenLeft, selected, true);

                return child;
            }
        }

        // 如果数据变化了,新建一个View,或者如果可能的话去ScrapView中拿一个缓存的view
        // Make a new view for this position, or convert an unused view if possible
        child = obtainView(position, mIsScrap);

        // 这个方法负责进行定位和量算,把View放到ListView中合适的位置
        // This needs to be positioned and measured
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

        return child;
    }

makeAndAddView() 有两种方式加载一个view,一个是 mRecycler.getActiveView(),一个是 obtainView()
第一次加载时,activeview仅仅在第一次layoutChildren中赋值且为空值,所以走 obtainView() 加载了 一个View,加载完的View传入 setupChild() 来显示

3.6 AbsListView.obtainView()

如果没能够从mActivieViews中直接复用View,那么就要调用obtainView方法获取View,该方法尝试间接复用RecycleBin中的mScrapViews中的View,如果不能间接复用,则创建新的View。

    /**
     * Get a view and have it show the data associated with the specified
     * position. This is called when we have already discovered that the view is
     * not available for reuse in the recycle bin. The only choices left are
     * converting an old view or making a new one.
     *
     * @param position The position to display
     * @param isScrap Array of at least 1 boolean, the first entry will become true if
     *                the returned view was taken from the "temporary detached" scrap heap, false if
     *                otherwise.
     *
     * @return A view displaying the data associated with the specified position
     */
    View obtainView(int position, boolean[] isScrap) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

        isScrap[0] = false;

        // Check whether we have a transient state view. Attempt to re-bind the
        // data and discard the view if we fail.
        final View transientView = mRecycler.getTransientStateView(position);
        if (transientView != null) {
            ...
            return transientView;
        }

        final View scrapView = mRecycler.getScrapView(position); // 尝试获取一个废弃缓存中的View
        final View child = mAdapter.getView(position, scrapView, this); // 调用了getView方法,去初始化我们的covertView
        // 第一次加载时,scrapView = null
    ...
    }

obtainView() 要返回一个View,这个view可能是从废弃缓存或 Adapter的getView()中获取的,第一次加载时 mRecycler.getTransientStateView()返回值是null,所以obtainView() 返回的是view是 mAdapter.getView()

获取到view,makeAndAddView() 接着往下执行,调用 setupChild(),obtainView() 返回的View也传入 setupChild() 中

3.7 ListView.setupChild()

    /**
     * Add a view as a child and make sure it is measured (if necessary) and
     * positioned properly.
     *
     * @param child The view to add
     * @param position The position of this child
     * @param y The y position relative to which this view will be positioned
     * @param flowDown If true, align top edge to y. If false, align bottom
     *        edge to y.
     * @param childrenLeft Left edge where children should be positioned
     * @param selected Is this position selected?
     * @param recycled Has this view been pulled from the recycle bin? If so it
     *        does not need to be remeasured.
     */
    private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
            boolean selected, boolean recycled) {
            ...
            addViewInLayout(child, flowDown ? -1 : 0, p, true);
            ...
            }

setupChild() 调用 ViewGroup.addViewInLayout() 显示 这个item的View

3.8 ViewGroup.addViewInLayout()

    /**
     * Adds a view during layout. This is useful if in your onLayout() method,
     * you need to add more views (as does the list view for example).
     *
     * If index is negative, it means put it at the end of the list.
     *
     * @param child the view to add to the group
     * @param index the index at which the child must be added or -1 to add last
     * @param params the layout parameters to associate with the child
     * @param preventRequestLayout if true, calling this method will not trigger a
     *        layout request on child
     * @return true if the child was added, false otherwise
     */
    protected boolean addViewInLayout(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }
        child.mParent = null;
        addViewInner(child, index, params, preventRequestLayout);
        child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
        return true;
    }

3.9 AbsListView.RecyleBin.scrapActiveViews()

加载完view,ListView.layoutChildren() 都到了 RecyleBin.scrapActiveViews()

        /**
         * Move all views remaining in mActiveViews to mScrapViews.
         * 将mActiveViews废弃到mScrapViews中
         */
        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;

                    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);// 将 victim 添加到 mTransientStateViewsById 中
                        } else if (!mDataChanged) {
                            if (mTransientStateViews == null) {
                                mTransientStateViews = new SparseArray<View>();
                            }
                            mTransientStateViews.put(mFirstActivePosition + i, victim); // 将 victim 添加到 mTransientStateViews 中
                        }
                        ...
        }

RecyleBin.scrapActiveViews()中有2个重要的赋值操作,是将view添加到 瞬间状态的数组中,瞬态的来源是view中hasTransientState方法,View.hasTransientState() 如下,其方法作用看源码解释就行,item中如果有view正在进行动画之类的动态改变发生。

    /**
     * Indicates whether the view is currently tracking transient state that the
     * app should not need to concern itself with saving and restoring, but that
     * the framework should take special note to preserve when possible.
     *
     * <p>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.</p>
     *
     * @return true if the view has transient state
     */
    @ViewDebug.ExportedProperty(category = "layout")
    public boolean hasTransientState() {
        return (mPrivateFlags2 & PFLAG2_HAS_TRANSIENT_STATE) == PFLAG2_HAS_TRANSIENT_STATE;
    }

第一次layoutchildern 结束了

4.加载第二屏,第二次 layout

这里写图片描述

4.1 ListView.layoutChildren()

    @Override
    protected void layoutChildren() {
        ...
        final int childCount = getChildCount();
        // 第一次加载时,ListView中还没有item,getChildCount() 返回0
        // 第二次加载时,getChildCount()  = 一屏可以显示的item的个数
        ...
        // Pull all children into the RecycleBin.
        // These views will be reused if possible
        final int firstPosition = mFirstPosition;
        final RecycleBin recycleBin = mRecycler;
        if (dataChanged) {
            for (int i = 0; i < childCount; i++) {
            recycleBin.addScrapView(getChildAt(i), firstPosition+i);
            }
        } else {
            // 数据没有变化时,走这里 缓存子view,第一次加载时 ListView中没有子view,该行不起作用
            // 第二次加载时,ListView中已经有子view了,会缓存到 RecyleBin 的 mActiveViews 数组中
            recycleBin.fillActiveViews(childCount, firstPosition);
            }

        // Clear out old views
        detachAllViewsFromParent(); // 将所有ListView中的子View清除掉,保证第二次layout不会产生重复数据,从 RecyleBin中取缓存的子View,不会重新inflate
        recycleBin.removeSkippedScrap();// //将子view和listview给detach掉

        // 默认的布局模式是 LAYOUT_NORMAL, 走 default
        switch (mLayoutMode) {
            ...
            default:
            if (childCount == 0) {
                // 第一次进入时 childCount = 0
                if (!mStackFromBottom) {
                    final int position = lookForSelectablePosition(0, true);
                    setSelectedPositionInt(position);
                    sel = fillFromTop(childrenTop);
                } else {
                    final int position = lookForSelectablePosition(mItemCount - 1, false);
                    setSelectedPositionInt(position);
                    sel = fillUp(mItemCount - 1, childrenBottom);
                }
            } else {
                // 第二次 childCount != 0
                if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                    // 默认情况,我们没有选中子view,不走该分支
                    sel = fillSpecific(mSelectedPosition,
                            oldSel == null ? childrenTop : oldSel.getTop());
                } else if (mFirstPosition < mItemCount) {
                    // mFirstPosition一开始是0,只要Adapter中数据大于0,该条件就成立了
                    sel = fillSpecific(mFirstPosition,
                            oldFirst == null ? childrenTop : oldFirst.getTop());
                } else {
                    sel = fillSpecific(0, childrenTop);
                }
            }
            break;
            }
        ...
    }

第二次加载时
recycleBin.fillActiveViews() 给activeview添加数据。将第一次加载的view放在了activeViews中
detachAllViewsFromParent() 清除掉 ListView中的子View,防止数据重复,ListView中所有的子View都是处于detach状态,后续从RecyleBin中获取缓存的view
进入switch (mLayoutMode),LayoutMode还是LAYOUT_NORMAL,进入default分支,getChildCount() 不再是0了,是 一屏显示的item的个数
调用 fillSpecific() 加载view

4.2 recycleBin.fillActiveViews()

        /**
         * 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
         */
        void fillActiveViews(int childCount, int firstActivePosition) {
            if (mActiveViews.length < childCount) {
                mActiveViews = new View[childCount];
            }
            mFirstActivePosition = firstActivePosition;

            //noinspection MismatchedReadAndWriteOfArray
            final 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 heap
                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;
                    // Remember the position so that setupChild() doesn't reset state.
                    lp.scrappedFromPosition = firstActivePosition + i;
                }
            }
        }

4.3 ViewGroup.detachAllViewsFromParent():

将所有的子View从ListView中分离,也就是清空了children

    /**
     * Detaches all views from the parent. Detaching a view should be followed
     * either by a call to
     * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
     * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
     * temporary; reattachment or removal should happen within the same drawing cycle as
     * detachment. When a view is detached, its parent is null and cannot be retrieved by a
     * call to {@link #getChildAt(int)}.
     *
     * @see #detachViewFromParent(View)
     * @see #detachViewFromParent(int)
     * @see #detachViewsFromParent(int, int)
     * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)
     * @see #removeDetachedView(View, boolean)
     */
    protected void detachAllViewsFromParent() {
        final int count = mChildrenCount;
        if (count <= 0) {
            return;
        }

        final View[] children = mChildren;
        mChildrenCount = 0;

        for (int i = count - 1; i >= 0; i--) {
            children[i].mParent = null;
            children[i] = null;
        }
    }

4.4 recycleBin.removeSkippedScrap()

        /**
         * 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();
        }

4.5 ListView.fillSpecific()

    /**
     * Put a specific item at a specific location on the screen and then build
     * up and down from there.
     *
     * @param position The reference view to use as the starting point
     * @param top Pixel offset from the top of this view to the top of the
     *        reference view.
     *
     * @return The selected view, or null if the selected view is outside the
     *         visible area.
     */
    private View fillSpecific(int position, int top) {
        boolean tempIsSelected = position == mSelectedPosition;
        View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
        // Possibly changed again in fillUp if we add rows above this one.
        mFirstPosition = position;

        View above;// above为头部item
        View below;// below为底部的item

        final int dividerHeight = mDividerHeight;
        if (!mStackFromBottom) {
            above = fillUp(position - 1, temp.getTop() - dividerHeight);
            // This will correct for the top of the first view not touching the top of the list
            adjustViewsUpOrDown();
            below = fillDown(position + 1, temp.getBottom() + dividerHeight);
            int childCount = getChildCount();
            if (childCount > 0) {
                correctTooHigh(childCount);
            }
        } else {
            below = fillDown(position + 1, temp.getBottom() + dividerHeight);
            // This will correct for the bottom of the last view not touching the bottom of the list
            adjustViewsUpOrDown();
            above = fillUp(position - 1, temp.getTop() - dividerHeight);
            int childCount = getChildCount();
            if (childCount > 0) {
                 correctTooLow(childCount);
            }
        }

        if (tempIsSelected) {
            return temp;
        } else if (above != null) {
            return above;
        } else {
            return below;
        }
    }

fillSpecific()方法会优先将指定位置的子View先加载到屏幕上,然后再加载该子View往上以及往下的其它子View。那么由于这里我们传入的position就是第一个子View的位置,于是fillSpecific()方法的作用就基本上和fillDown()方法是差不多的了,接着看 makeAndAddView()

4.6 ListView.makeAndAddView()

    /**
     * Obtain the view and add it to our list of children. The view can be made
     * fresh, converted from an unused view, or used as is if it was in the
     * recycle bin.
     *
     * @param position Logical position in the list
     * @param y Top or bottom edge of the view to add
     * @param flow If flow is true, align top edge to y. If false, align bottom
     *        edge to y.
     * @param childrenLeft Left edge where children should be positioned
     * @param selected Is this position selected?
     * @return View that was added
     */
    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        View child;


        if (!mDataChanged) {
            // Try to use an existing view for this position
            child = mRecycler.getActiveView(position);// 从 RecyleBin 中获取一个active view
            // 第一次进入时 ,child 是null
            // 第二次进入时,getActiveView()会返回一个View,child != null
            if (child != null) {
                // Found it -- we're using an existing child
                // This just needs to be positioned
                setupChild(child, position, y, flow, childrenLeft, selected, true);

                return child;
            }
        }

        // Make a new view for this position, or convert an unused view if possible
        child = obtainView(position, mIsScrap);

        // This needs to be positioned and measured
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

        return child;
    }

layoutChildren() 中调用 recycleBin.fillActiveViews 缓存了子View
makeAndAddView() 中 调用 mRecycler.getActiveView() 取出缓存的子View
调用 setupChild(),最后一个参数 是true,表示是从RecyleBin中取出的缓存的view
第二次 layout时,不走obtainView(),会节省一些时间

4.7 RecyleBin.getActiveView()

举一个例子,假设在某一时刻ListView中显示了10个子View,position依次为从0到9。然后我们手指向上滑动,且向上滑动了一个子View的高度,ListView需要绘制下一帧。这时候ListView在layoutChildren方法中把这10个子View都放入到了RecycleBin的mActiveViews数组中了,然后清空了children数组,然后调用fillDown方法,向ListView中依次添加position1到10的子View,在添加position为1的子View的时候,由于在上一帧中position为1的子View已经被放到mActiveViews数组中了,这次直接可以将其从mActiveViews数组中取出来,这样就是直接复用子View,所以说RecycleBin的mActiveViews数组主要是用于直接复用的。

        /**
         * 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
         */
        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; // 将activeview的对应item给置空,说明activeview仅仅只能使用一次
                return match;
            }
            return null;
        }

4.8 ListView.setupChild()

    /**
     * Add a view as a child and make sure it is measured (if necessary) and
     * positioned properly.
     *
     * @param child The view to add
     * @param position The position of this child
     * @param y The y position relative to which this view will be positioned
     * @param flowDown If true, align top edge to y. If false, align bottom
     *        edge to y.
     * @param childrenLeft Left edge where children should be positioned
     * @param selected Is this position selected?
     * @param recycled Has this view been pulled from the recycle bin? If so it
     *        does not need to be remeasured.第二次加载时,recycled = true
     */
    private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
            boolean selected, boolean recycled) {
        ...
        if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter
            && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
            // 第二次加载时调用
            attachViewToParent(child, flowDown ? -1 : 0, p);
        } else {
            p.forceAdd = false;
            if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                p.recycledHeaderFooter = true;
            }
            // 第一次加载时调用
            addViewInLayout(child, flowDown ? -1 : 0, p, true);
        }
        ...
    }

setupChild() 调用 attachViewToParent() ,将view添加到 viewGroup中

4.9 ViewGroup.attachViewToParent()

    /**
     * Attaches a view to this view group. Attaching a view assigns this group as the parent,
     * sets the layout parameters and puts the view in the list of children so that
     * it can be retrieved by calling {@link #getChildAt(int)}.
     * <p>
     * This method is intended to be lightweight and makes no assumptions about whether the
     * parent or child should be redrawn. Proper use of this method will include also making
     * any appropriate {@link #requestLayout()} or {@link #invalidate()} calls.
     * For example, callers can {@link #post(Runnable) post} a {@link Runnable}
     * which performs a {@link #requestLayout()} on the next frame, after all detach/attach
     * calls are finished, causing layout to be run prior to redrawing the view hierarchy.
     * <p>
     * This method should be called only for views which were detached from their parent.
     *
     * @param child the child to attach
     * @param index the index at which the child should be attached
     * @param params the layout parameters of the child
     *
     * @see #removeDetachedView(View, boolean)
     * @see #detachAllViewsFromParent()
     * @see #detachViewFromParent(View)
     * @see #detachViewFromParent(int)
     */
    protected void attachViewToParent(View child, int index, LayoutParams params) {
        child.mLayoutParams = params;

        if (index < 0) {
            index = mChildrenCount;
        }

        addInArray(child, index);

        child.mParent = this;
        child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK
                        & ~PFLAG_DRAWING_CACHE_VALID)
                | PFLAG_DRAWN | PFLAG_INVALIDATED;
        this.mPrivateFlags |= PFLAG_INVALIDATED;

        if (child.hasFocus()) {
            requestChildFocus(child, child.findFocus());
        }
        dispatchVisibilityAggregated(isAttachedToWindow() && getWindowVisibility() == VISIBLE
                && isShown());
    }

5.AbsListView.obtainView()

5.1 AbsListView.obtainView()

    /**
     * Get a view and have it show the data associated with the specified
     * position. This is called when we have already discovered that the view is
     * not available for reuse in the recycle bin. The only choices left are
     * converting an old view or making a new one.
     *
     * @param position The position to display
     * @param isScrap Array of at least 1 boolean, the first entry will become true if
     *                the returned view was taken from the "temporary detached" scrap heap, false if
     *                otherwise.
     *
     * @return A view displaying the data associated with the specified position
     */
    View obtainView(int position, boolean[] isScrap) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

        isScrap[0] = false;

        // Check whether we have a transient state view. Attempt to re-bind the
        // data and discard the view if we fail.
        final View transientView = mRecycler.getTransientStateView(position);
        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);

                // If we failed to re-bind the data, scrap the obtained view.
                if (updatedView != transientView) {
                    setItemViewLayoutParams(updatedView, position);
                    mRecycler.addScrapView(updatedView, position);
                }
            }

            isScrap[0] = true;

            // Finish the temporary detach started in addScrapView().
            transientView.dispatchFinishTemporaryDetach();
            return transientView;
        }

        // trackMotionScroll()中,一旦有子view移除屏幕,就加入到废弃缓存中
        final View scrapView = mRecycler.getScrapView(position); // 尝试获取一个废弃缓存中的View
        final View child = mAdapter.getView(position, scrapView, this); // 通过 Adapter.getView() 获取一个 View
        // 第一次加载时,scrapView = null
        if (scrapView != null) {
            // 如果不相等,这种情况就是我们写Adapter.getView()的covertView为null的情况
            if (child != scrapView) {
                // Failed to re-bind the data, return scrap to the heap.
                mRecycler.addScrapView(scrapView, position);
            } else {
                if (child.isTemporarilyDetached()) {
                    isScrap[0] = true;

                    // Finish the temporary detach started in addScrapView().
                    child.dispatchFinishTemporaryDetach();
                } else {
                    // we set isScrap to "true" only if the view is temporarily detached.
                    // if the view is fully detached, it is as good as a view created by the
                    // adapter
                    isScrap[0] = false;
                }

            }
        }

        ...

        return child;
    }

5.2 RecyclerBin.getTransientStateView()

        View getTransientStateView(int position) {
            if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
                long id = mAdapter.getItemId(position);
                View result = mTransientStateViewsById.get(id);// 从 mTransientStateViewsById 中返回view
                mTransientStateViewsById.remove(id);
                return result;
            }
            if (mTransientStateViews != null) {
                final int index = mTransientStateViews.indexOfKey(position);
                if (index >= 0) {
                    View result = mTransientStateViews.valueAt(index); // 从 mTransientStateViews 中返回view
                    mTransientStateViews.removeAt(index);
                    return result;
                }
            }
            return null;
        }

getTransientStateView() 从 mTransientStateViewsById 或 mTransientStateViews 返回一个view

5.3 瞬态的view是从哪来的

mTransientStateViewsById , mTransientStateViews 在哪赋值的
scrapActiveViews() 和 addScrapView() 中

AbsListView.RecyleBin.scrapActiveViews()

        /**
         * Move all views remaining in mActiveViews to mScrapViews.
         * 将mActiveViews废弃到mScrapViews中
         */
        void scrapActiveViews() {
            ...
                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);// 将 victim 添加到 mTransientStateViewsById 中
                        } else if (!mDataChanged) {
                            if (mTransientStateViews == null) {
                                mTransientStateViews = new SparseArray<View>();
                            }
                            mTransientStateViews.put(mFirstActivePosition + i, victim); // 将 victim 添加到 mTransientStateViews 中
                        } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                            // The data has changed, we can't keep this view.
                            removeDetachedView(victim, false);
                        }
                } 
            ...
            }

AbsListView.RecyleBin.addScrapView()

        /**
         * 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
         */
        void addScrapView(View scrap, int position) {
            ...
            // 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);//将 scrap添加到 mTransientStateViewsById 中
                } 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);// 将 scrap 添加到 mTransientStateViews 中
                } else {
                    // Otherwise, we'll have to remove the view and start over.
                    getSkippedScrap().add(scrap);
                }
            }
            ... 
        }

5.4 添加 view 的依据:View.hasTransientState()

    /**
     * Indicates whether the view is currently tracking transient state that the
     * app should not need to concern itself with saving and restoring, but that
     * the framework should take special note to preserve when possible.
     *
     * <p>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.</p>
     *
     * @return true if the view has transient state
     */
    @ViewDebug.ExportedProperty(category = "layout")
    public boolean hasTransientState() {
        return (mPrivateFlags2 & PFLAG2_HAS_TRANSIENT_STATE) == PFLAG2_HAS_TRANSIENT_STATE;
    }

transient state类似 view 的一个属性,该属性的作用就是标注当前view是否正在变化中。
ListView的item 在加载数据时或者 播放动画时,view处于 瞬态 transient state。

obtainView()中final View updatedView = mAdapter.getView(position, transientView, this);
mAdapter.getView() 获得的view是静态的view,没有发生任何动态改变和展现任何正在发生中的动画的item。
接着下边 mRecycler.addScrapView(updatedView, position);添加到RecyleBin中的是这个静态的view

    /**
     * Get a view and have it show the data associated with the specified
     * position. This is called when we have already discovered that the view is
     * not available for reuse in the recycle bin. The only choices left are
     * converting an old view or making a new one.
     *
     * @param position The position to display
     * @param isScrap Array of at least 1 boolean, the first entry will become true if
     *                the returned view was taken from the "temporary detached" scrap heap, false if
     *                otherwise.
     *
     * @return A view displaying the data associated with the specified position
     */
    View obtainView(int position, boolean[] isScrap) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

        isScrap[0] = false;

        // Check whether we have a transient state view. Attempt to re-bind the
        // data and discard the view if we fail.
        final View transientView = mRecycler.getTransientStateView(position);
        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);// 获取一个静态的view

                // If we failed to re-bind the data, scrap the obtained view.
                if (updatedView != transientView) {
                    setItemViewLayoutParams(updatedView, position);
                    mRecycler.addScrapView(updatedView, position);// 将这个静态的view添加到RecyleBin中
                }
            }

            isScrap[0] = true;

            // Finish the temporary detach started in addScrapView().
            transientView.dispatchFinishTemporaryDetach();
            return transientView;
        }
    ...
    }

6.滑动加载更多

上边介绍了 怎么加载数据,下边将是 ListView最神奇的部分,滑动加载更多,
比如说我们的Adapter当中有1000条数据,但是第一屏只显示了10条,ListView中也只有10个子View而已,那么剩下的990是怎样工作并显示到界面上的呢?
ListView 和 GridView 都支持 滑动加载数据,所以滑动的代码在 AbsListView 中
onTouchEvent() 中代码非常多,逻辑也复杂
滑动部分对应的 ACTION_MOVE,我们看 ACTION_MOVE 的逻辑
这里写图片描述

6.1 AbsListView.onTouchEvent()

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        ...
        switch (actionMasked) {
            ...
            case MotionEvent.ACTION_MOVE: {
                onTouchMove(ev, vtev);
                break;
            }
            ...
        }
    }

ACTION_MOVE 调用了 onTouchMove()

6.2 AbsListView.onTouchMove()

    private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
        ...
        // 滑动时对应的 touchMode 是 TOUCH_MODE_SCROLL
        switch (mTouchMode) {
            ...
            case TOUCH_MODE_SCROLL:
            case TOUCH_MODE_OVERSCROLL:
                scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
                break;
        }
    }

手指上滑时,TouchMode是TOUCH_MODE_SCROLL,onTouchMove() 调用了 scrollIfNeeded()

6.3 AbsListView.scrollIfNeeded()

    private void scrollIfNeeded(int x, int y, MotionEvent vtev) {
        ...
        if (incrementalDeltaY != 0) {
            atEdge = trackMotionScroll(deltaY, incrementalDeltaY); // 跟踪移动,滑动屏幕时,trackMotionScroll() 会被多次调用
       }
       ...
    }

scrollIfNeeded() 中 屏幕有移动就会调用 trackMotionScroll(),滑动屏幕时,trackMotionScroll() 会被多次调用

6.4 AbsListView.trackMotionScroll()

    /**
     * Track a motion scroll
     *
     * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
     *        began. Positive numbers mean the user's finger is moving down the screen.
     * deltaY 表示 从手指按下时的位置 到 当前手指位置的距离
     * @param incrementalDeltaY Change in deltaY from the previous event.
     * incrementalDeltaY 表示据上次触发event事件 手指在 y方向上的改变量,通过正负值判断向上或向下滑动
     * @return true if we're already at the beginning/end of the list and have nothing to do.
     */
    boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
        ...
        // incrementalDeltaY < 0 是向下滑动,否则是向上滑动
        if (incrementalDeltaY < 0) {
            incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
        } else {
            incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
        }
        ...
        final boolean down = incrementalDeltaY < 0;
        ...

        if (down) {
            // 手指上滑
            int top = -incrementalDeltaY;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                top += listPadding.top;
            }
            for (int i = 0; i < childCount; i++) {
                // 循环 从上往下依次获取子view
                final View child = getChildAt(i);
                if (child.getBottom() >= top) {
                    break;
                } else {
                    // 子view已经移出屏幕了
                    count++; // count 用于记录有多少个子View被移出了屏幕
                    int position = firstPosition + i;
                    if (position >= headerViewsCount && position < footerViewsStart) {
                        // The view will be rebound to new data, clear any
                        // system-managed transient state.
                        child.clearAccessibilityFocus();
                        mRecycler.addScrapView(child, position);// 将该view加入到废弃缓存中
                    }
                }
            }
        } else {
            // 手指下滑
            int bottom = getHeight() - incrementalDeltaY;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                bottom -= listPadding.bottom;
            }
            for (int i = childCount - 1; i >= 0; i--) {
                // 从下往上 依次获取子view
                final View child = getChildAt(i);
                if (child.getTop() <= bottom) {
                    break;
                } else {
                    // 子view已经移出屏幕了
                    start = i;
                    count++;
                    int position = firstPosition + i;
                    if (position >= headerViewsCount && position < footerViewsStart) {
                        // The view will be rebound to new data, clear any
                        // system-managed transient state.
                        child.clearAccessibilityFocus();
                        mRecycler.addScrapView(child, position);
                    }
                }
            }
        }

        mMotionViewNewTop = mMotionViewOriginalTop + deltaY;

        mBlockLayoutRequests = true;

        if (count > 0) {
            detachViewsFromParent(start, count);// 把移出屏幕的子view全detach掉,ListView中看不到的就不保存了,毕竟有那么多数据等着加载
            mRecycler.removeSkippedScrap();
        }

        // invalidate before moving the children to avoid unnecessary invalidate
        // calls to bubble up from the children all the way to the top
        if (!awakenScrollBars()) {
           invalidate();
        }

        offsetChildrenTopAndBottom(incrementalDeltaY);// 所有的子View按照 incrementalDeltaY 进行偏移,实现了随着手指拖动,ListView随着滚动的效果

        if (down) {
            mFirstPosition += count;
        }

        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
        if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
            // ListView的最后一个子View的底部已经移入屏幕 或者 第一个子View的顶部已经移入屏幕
            fillGap(down);
        }
        ...
    }

通过 down 来判断 上滑 或下滑,遍历子View,移出屏幕的子View 通过 mRecycler.addScrapView() 添加到 RecyleBin的废弃缓存中
detachViewsFromParent() ListView 中移出屏幕的子view全detach掉
offsetChildrenTopAndBottom() 使view随着 incrementalDeltaY 偏移,实现ListView随着手指动的效果
listView滑到头了,调用 fillGap(),fillGap() 是一个抽象方法,由子类实现

    /**
     * Fills the gap left open by a touch-scroll. During a touch scroll, children that
     * remain on screen are shifted and the other ones are discarded. The role of this
     * method is to fill the gap thus created by performing a partial layout in the
     * empty space.
     *
     * @param down true if the scroll is going down, false if it is going up
     */
    abstract void fillGap(boolean down);

6.5 ListView.fillGap()

    /**
     * {@inheritDoc}
     */
    @Override
    void fillGap(boolean down) {
        final int count = getChildCount();
        if (down) {
            // 手指上滑
            int paddingTop = 0;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                paddingTop = getListPaddingTop();
            }
            final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :
                    paddingTop;
            fillDown(mFirstPosition + count, startOffset);
            correctTooHigh(getChildCount());
        } else {
            // 手指下滑
            int paddingBottom = 0;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                paddingBottom = getListPaddingBottom();
            }
            final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
                    getHeight() - paddingBottom;
            fillUp(mFirstPosition - 1, startOffset);
            correctTooLow(getChildCount());
        }
    }

fillGap() 中调用 fillDown() 或 fillUp()

6.6 ListView.fillDown() 或 fillUp()

    /**
     * Fills the list from pos down to the end of the list view.
     *
     * @param pos The first position to put in the list
     *
     * @param nextTop The location where the top of the item associated with pos
     *        should be drawn
     *
     * @return The view that is currently selected, if it happens to be in the
     *         range that we draw.
     */
    private View fillDown(int pos, int nextTop) {
        View selectedView = null;

        int end = (mBottom - mTop); // end是ListView底部减去顶部所得的像素值
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            end -= mListPadding.bottom;
        }

        // mItemCount 是Adapter 元素的数量, nextTop >= end 时,子元素超出屏幕,pos >= mItemCount 时,adapter元素都被遍历完了
        while (nextTop < end && pos < mItemCount) {
            // is this the selected item?
            boolean selected = pos == mSelectedPosition;
            View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);

            nextTop = child.getBottom() + mDividerHeight;
            if (selected) {
                selectedView = child;
            }
            pos++; // 循环一次,pos加1
        }

        setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
        return selectedView;
    }
    /**
     * Fills the list from pos up to the top of the list view.
     *
     * @param pos The first position to put in the list
     *
     * @param nextBottom The location where the bottom of the item associated
     *        with pos should be drawn
     *
     * @return The view that is currently selected
     */
    private View fillUp(int pos, int nextBottom) {
        View selectedView = null;

        int end = 0;
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            end = mListPadding.top;
        }

        while (nextBottom > end && pos >= 0) {
            // is this the selected item?
            boolean selected = pos == mSelectedPosition;
            View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
            nextBottom = child.getTop() - mDividerHeight;
            if (selected) {
                selectedView = child;
            }
            pos--;
        }

        mFirstPosition = pos + 1;
        setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
        return selectedView;
    }

fillDown() 和 fillUp() 都调用了 makeAndAddView()

6.7 ListView.makeAndAddView()

    /**
     * Obtain the view and add it to our list of children. The view can be made
     * fresh, converted from an unused view, or used as is if it was in the
     * recycle bin.
     *
     * @param position Logical position in the list
     * @param y Top or bottom edge of the view to add
     * @param flow If flow is true, align top edge to y. If false, align bottom
     *        edge to y.
     * @param childrenLeft Left edge where children should be positioned
     * @param selected Is this position selected?
     * @return View that was added
     */
    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        View child;


        if (!mDataChanged) {
            // Try to use an existing view for this position
            child = mRecycler.getActiveView(position);// 从 RecyleBin 中获取一个active view
            // 第一次进入时 ,child 是null
            // 第二次进入时,getActiveView()会返回一个View,child != null
            // 根据RecycleBin的机制,mActiveViews是不能够重复利用的, 第二次加载时 获取过,再调用 getActiveView 返回的是 null
            if (child != null) {
                // Found it -- we're using an existing child
                // This just needs to be positioned
                setupChild(child, position, y, flow, childrenLeft, selected, true);

                return child;
            }
        }

        // Make a new view for this position, or convert an unused view if possible
        child = obtainView(position, mIsScrap);

        // This needs to be positioned and measured
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

        return child;
    }

makeAndAddView() 中 调用 mRecycler.getActiveView() 返回的是 null (因为第二次加载时,已经调用了 getActiveView(), mActiveViews是不能够重复利用的)
调用 obtainView(),setupChild()

6.8 AbsListView.obtainView()

    /**
     * Get a view and have it show the data associated with the specified
     * position. This is called when we have already discovered that the view is
     * not available for reuse in the recycle bin. The only choices left are
     * converting an old view or making a new one.
     *
     * @param position The position to display
     * @param isScrap Array of at least 1 boolean, the first entry will become true if
     *                the returned view was taken from the "temporary detached" scrap heap, false if
     *                otherwise.
     *
     * @return A view displaying the data associated with the specified position
     */
    View obtainView(int position, boolean[] isScrap) {
        ...
        // trackMotionScroll()中,一旦有子view移除屏幕,就加入到废弃缓存中
        final View scrapView = mRecycler.getScrapView(position); // 尝试获取一个废弃缓存中的View
        final View child = mAdapter.getView(position, scrapView, this); // 通过 Adapter.getView() 获取一个 View
        // 第一次加载时,scrapView = null
        if (scrapView != null) {
            if (child != scrapView) {
                // Failed to re-bind the data, return scrap to the heap.
                mRecycler.addScrapView(scrapView, position);
            } else {
                if (child.isTemporarilyDetached()) {
                    isScrap[0] = true;

                    // Finish the temporary detach started in addScrapView().
                    child.dispatchFinishTemporaryDetach();
                } else {
                    // we set isScrap to "true" only if the view is temporarily detached.
                    // if the view is fully detached, it is as good as a view created by the
                    // adapter
                    isScrap[0] = false;
                }

            }
        }
     ...
    }

mRecycler.getScrapView() 从废弃缓存中获取一个view
从缓存中拿到子View之后再调用setupChild()方法将它重新attach到ListView当中

getView()例子

public View getView(int position, View convertView, ViewGroup parent){
    ViewHolder holder;

    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.list_item, parent, false);
        holder = new ViewHolder();
        holder.text = (TextView)convertView.findViewById(R.id.text);
        holder.icon = (ImageView)convertView.findViewById(R.id.icon);

        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    holder.text.setText(DATA[position]);
    holder.icon.setImageBitmap(Icons[position]);

    return convertView;
}

public static class ViewHolder{
    TextView text;
    ImageView icon;
}

第二个参数就是我们最熟悉的convertView呀,难怪平时我们在写getView()方法是要判断一下convertView是不是等于null,如果等于null才调用inflate()方法来加载布局,不等于null就可以直接利用convertView,因为convertView就是我们之间利用过的View,只不过被移出屏幕后进入到了废弃缓存中,现在又重新拿出来使用而已。然后我们只需要把convertView中的数据更新成当前位置上应该显示的数据,那么看起来就好像是全新加载出来的一个布局一样,这背后的道理你是不是已经完全搞明白了?

6.9 ListView.setupChild()

    /**
     * Add a view as a child and make sure it is measured (if necessary) and
     * positioned properly.
     *
     * @param child The view to add
     * @param position The position of this child
     * @param y The y position relative to which this view will be positioned
     * @param flowDown If true, align top edge to y. If false, align bottom
     *        edge to y.
     * @param childrenLeft Left edge where children should be positioned
     * @param selected Is this position selected?
     * @param recycled Has this view been pulled from the recycle bin? If so it
     *        does not need to be remeasured.第二次加载时,recycled = true
     */
    private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
            boolean selected, boolean recycled) {
        ...
        if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter
            && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
            // 第二次加载时调用
            attachViewToParent(child, flowDown ? -1 : 0, p);
        } else {
            p.forceAdd = false;
            if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                p.recycledHeaderFooter = true;
            }
            // 第一次加载时调用
            addViewInLayout(child, flowDown ? -1 : 0, p, true);
        }
    ...
    }

setupChild() 调用 ViewGroup.attachViewToParent() 加载子view
这里写图片描述

7. RecycleBin的调用

最后再来看看 RecyleBin 是怎么用的,上边涉及到RecyleBin的操作如下图
这里写图片描述

下边介绍下 ListView、AbsListView中是怎么使用RecyleBin的

7.1 ListView调用RecyleBin

7.1.1 ListView.layoutChildren()

    @Override
    protected void layoutChildren() {
    ...
            // Pull all children into the RecycleBin.
            // These views will be reused if possible
            final int firstPosition = mFirstPosition;
            final RecycleBin recycleBin = mRecycler;
            if (dataChanged) {
                for (int i = 0; i < childCount; i++) {
                    recycleBin.addScrapView(getChildAt(i), firstPosition+i);
                }
            } else {
                // 数据没有变化时,走这里 缓存子view,第一次加载时 ListView中没有子view,该行不起作用
                // 第二次加载时,ListView中已经有子view了,会缓存到 RecyleBin 的 mActiveViews 数组中
                recycleBin.fillActiveViews(childCount, firstPosition);
            }

            // Clear out old views
            detachAllViewsFromParent(); // 将所有ListView中的子View清除掉,保证第二次layout不会产生重复数据,从 RecyleBin中取缓存的子View,不会重新inflate
            recycleBin.removeSkippedScrap();

            // 默认的布局模式是 LAYOUT_NORMAL, 走 default
            switch (mLayoutMode) {
            ...
            }

            // Flush any cached views that did not get reused above
            recycleBin.scrapActiveViews();
    }
  1. 判断 dataChanged,如果数据发生变化,就将所有view加入到mScrapView中(recycleBin.addScrapView());否则,将所有view放到mActiveView中(fillActiveViews());
  2. recycleBin.removeSkippedScrap();//移除所有old views,根据mLayoutMode,给ListView添加子view
  3. recycleBin.scrapActiveViews();//刷新缓存,将当前的ActiveVies 移动到 ScrapViews。

7.1.2 ListView.makeAndAddView()

如果数据没变,调 mRecycler.getActiveView() 获取一个active view

    /**
     * Obtain the view and add it to our list of children. The view can be made
     * fresh, converted from an unused view, or used as is if it was in the
     * recycle bin.
     *
     * @param position Logical position in the list
     * @param y Top or bottom edge of the view to add
     * @param flow If flow is true, align top edge to y. If false, align bottom
     *        edge to y.
     * @param childrenLeft Left edge where children should be positioned
     * @param selected Is this position selected?
     * @return View that was added
     */
    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        View child;


        if (!mDataChanged) {
            // Try to use an existing view for this position
            child = mRecycler.getActiveView(position);// 从 RecyleBin 中获取一个active view
            // 第一次进入时 ,child 是null
            // 第二次进入时,getActiveView()会返回一个View,child != null
            // 根据RecycleBin的机制,mActiveViews是不能够重复利用的, 第二次加载时 获取过,再调用 getActiveView 返回的是 null
            if (child != null) {
                // Found it -- we're using an existing child
                // This just needs to be positioned
                setupChild(child, position, y, flow, childrenLeft, selected, true);

                return child;
            }
        }

        // Make a new view for this position, or convert an unused view if possible
        child = obtainView(position, mIsScrap);

        // This needs to be positioned and measured
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

        return child;
    }

7.1.3 ListView.setAdapter()

    /**
     * Sets the data behind this ListView.
     *
     * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
     * depending on the ListView features currently in use. For instance, adding
     * headers and/or footers will cause the adapter to be wrapped.
     *
     * @param adapter The ListAdapter which is responsible for maintaining the
     *        data backing this list and for producing a view to represent an
     *        item in that data set.
     *
     * @see #getAdapter() 
     */
    @Override
    public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null && mDataSetObserver != null) {
            // 移除了与当前listview的adapter绑定数据集观察者DataSetObserver
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        // 重置listview,主要是清除所有的view,改变header、footer的状态
        resetList();
        mRecycler.clear();// 清除掉RecycleBin对象mRecycler中所有缓存的view

        // 判断是否有headerview和footview
        if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }

        mOldSelectedPosition = INVALID_POSITION;
        mOldSelectedRowId = INVALID_ROW_ID;

        // AbsListView#setAdapter will update choice mode states.
        super.setAdapter(adapter);

        if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();
            checkFocus();

            mDataSetObserver = new AdapterDataSetObserver();// 注册headerview的观察者
            mAdapter.registerDataSetObserver(mDataSetObserver);

            // 在RecycleBin对象mRecycler记录下item类型的数量
            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());

            int position;
            if (mStackFromBottom) {
                position = lookForSelectablePosition(mItemCount - 1, false);
            } else {
                position = lookForSelectablePosition(0, true);
            }
            setSelectedPositionInt(position);
            setNextSelectedPositionInt(position);

            if (mItemCount == 0) {
                // Nothing selected
                checkSelectionChanged();
            }
        } else {
            mAreAllItemsSelectable = true;
            checkFocus();
            // Nothing selected
            checkSelectionChanged();
        }

        requestLayout();
    }

7.1.4 scrollListItemsBy()

Scroll the children by amount, adding a view at the end and removing views that fall off as necessary.
对子view滑动一定距离,添加view到底部或者移除顶部的不可见view。从注释看,不可见的item 的自动移除是在scrollListItemsBy中进行的。

Android中view回收的计算是其父view中不再显示的,如果scrollview中包含了一个wrap_content属性的listview,里面的内容并不会有任何回收,引起listview 的getheight函数获取的是一个足以显示所有内容的高度。

7.2 AbsListView调用RecyleBin

7.2.1 AbsListView.obtainView()

当这个方法被调用时,说明Recycle bin中的view已经不可用了,那么,现在唯一的方法就是,convert一个老的view,或者构造一个新的view

    /**
     * Get a view and have it show the data associated with the specified
     * position. This is called when we have already discovered that the view is
     * not available for reuse in the recycle bin. The only choices left are
     * converting an old view or making a new one.
     *
     * @param position The position to display
     * @param isScrap Array of at least 1 boolean, the first entry will become true if
     *                the returned view was taken from the "temporary detached" scrap heap, false if
     *                otherwise.
     *
     * @return A view displaying the data associated with the specified position
     */
    View obtainView(int position, boolean[] isScrap) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

        isScrap[0] = false;

        // Check whether we have a transient state view. Attempt to re-bind the
        // data and discard the view if we fail.
        final View transientView = mRecycler.getTransientStateView(position);
        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);

                // If we failed to re-bind the data, scrap the obtained view.
                if (updatedView != transientView) {
                    setItemViewLayoutParams(updatedView, position);
                    mRecycler.addScrapView(updatedView, position);
                }
            }

            isScrap[0] = true;

            // Finish the temporary detach started in addScrapView().
            transientView.dispatchFinishTemporaryDetach();
            return transientView;
        }

        // trackMotionScroll()中,一旦有子view移除屏幕,就加入到废弃缓存中
        final View scrapView = mRecycler.getScrapView(position); // 尝试获取一个废弃缓存中的View
        final View child = mAdapter.getView(position, scrapView, this); // 通过 Adapter.getView() 获取一个 View
        // 第一次加载时,scrapView = null
        if (scrapView != null) {
            if (child != scrapView) {
                // 如果重用的scrapView和adapter获得的view是不一样的,将scrapView进行回收  
                // Failed to re-bind the data, return scrap to the heap.
                mRecycler.addScrapView(scrapView, position);// 将 scrapView 回收
            } else {
                // 如果重用的view和adapter获得的view是一样的,将isScrap[0]值为true,否则默认为false
                if (child.isTemporarilyDetached()) {
                    isScrap[0] = true;

                    // Finish the temporary detach started in addScrapView().
                    child.dispatchFinishTemporaryDetach();
                } else {
                    // we set isScrap to "true" only if the view is temporarily detached.
                    // if the view is fully detached, it is as good as a view created by the
                    // adapter
                    isScrap[0] = false;
                }

            }
        }

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

        Trace.traceEnd(Trace.TRACE_TAG_VIEW);

        return child;
    }

7.2.2 trackMotionScroll()

    /**
     * Track a motion scroll
     *
     * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
     *        began. Positive numbers mean the user's finger is moving down the screen.
     * deltaY 表示 从手指按下时的位置 到 当前手指位置的距离
     * @param incrementalDeltaY Change in deltaY from the previous event.
     * incrementalDeltaY 表示据上次触发event事件 手指在 y方向上的该变量,通过正负值判断向上或向下滑动
     * @return true if we're already at the beginning/end of the list and have nothing to do.
     */
    boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
    ...
        // 滚动时,不在可见范围内的item放入回收站
        if (down) {
            // 手指上滑
            int top = -incrementalDeltaY;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                top += listPadding.top;
            }
            for (int i = 0; i < childCount; i++) {
                // 循环 从上往下依次获取子view
                final View child = getChildAt(i);
                if (child.getBottom() >= top) {
                    break;
                } else {
                    // 子view已经移出屏幕了
                    count++;
                    int position = firstPosition + i;
                    if (position >= headerViewsCount && position < footerViewsStart) {
                        // The view will be rebound to new data, clear any
                        // system-managed transient state.
                        child.clearAccessibilityFocus();
                        mRecycler.addScrapView(child, position);// 将该view加入到废弃缓存中
                    }
                }
            }
        } else {
            // 手指下滑
            int bottom = getHeight() - incrementalDeltaY;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                bottom -= listPadding.bottom;
            }
            for (int i = childCount - 1; i >= 0; i--) {
                // 从下往上 依次获取子view
                final View child = getChildAt(i);
                if (child.getTop() <= bottom) {
                    break;
                } else {
                    // 子view已经移出屏幕了
                    start = i;
                    count++;
                    int position = firstPosition + i;
                    if (position >= headerViewsCount && position < footerViewsStart) {
                        // The view will be rebound to new data, clear any
                        // system-managed transient state.
                        child.clearAccessibilityFocus();
                        mRecycler.addScrapView(child, position);
                    }
                }
            }
        }

        mMotionViewNewTop = mMotionViewOriginalTop + deltaY;

        mBlockLayoutRequests = true;

        if (count > 0) {
            detachViewsFromParent(start, count);// 把移出屏幕的子view全detach掉,看不到的就不保存了
            mRecycler.removeSkippedScrap();
        }

        // invalidate before moving the children to avoid unnecessary invalidate
        // calls to bubble up from the children all the way to the top
        if (!awakenScrollBars()) {
           invalidate();
        }

        offsetChildrenTopAndBottom(incrementalDeltaY);// 所有的子View按照 incrementalDeltaY 进行偏移,实现了随着手指拖动,ListView随着滚动的效果

        if (down) {
            mFirstPosition += count;
        }

        final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
        if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
            // ListView的最后一个子View的底部已经移入屏幕 或者 第一个子View的顶部已经移入屏幕
            fillGap(down);
        }

        mRecycler.fullyDetachScrapViews();
    ...
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值