ListView源码阅读:RecycleBin缓存机制以及二次onLayout

在阅读ListView源码之前,我们首先需要了解到ListView的缓存机制,也就是RecycleBin缓存,RecycleBin是属于AbsListView的内部类。这里看需要了解RecycleBin的一些成员变量和方法。

RecycleBin

 //在mActiveViews中存储的第一个视图的位置
   private int mFirstActivePosition;
 //缓存当前屏幕内View  
   private View[] mActiveViews = new View[0];
 //已Type为下标的项目视图集合数组  
   private ArrayList<View>[] mScrapViews;
 //项目视图Type数量  
   private int mViewTypeCount;
 //第一种Type的项目视图集合  
   private ArrayList<View> mCurrentScrap;
   private ArrayList<View> mSkippedScrap;
   private SparseArray<View> mTransientStateViews;
   private LongSparseArray<View> mTransientStateViewsById;

下面属于RecycleBin中一些重要的方法:

fillActiveViews:

这方法接收两个参数,childCount需要存储的View的数量,以及firstActivePosition当前ListView中第一个可见Item的position,RecycleBin调用该方法来存储指定位置的ListView元素到mActiveViews 数组中。

getActiveView

该方法接受一个指定的position值,将该position换算成指定的下标从mActiveViews 数组中获取一个View,该方法与fillActiveViews方法相对应,值得注意的是,获取到指定小标值后,当前位置view置null。也就是说该值只能被复用一次,否则下一次获取就是null

addScrapView

该方法接收两个参数分别是View和position,用于缓存废弃的View ,例如上下滑动滑出屏幕,通过mCurrentScrap和mScrapViews来存储指定type的view。

getScrapView

与addScrapView方法相对应,从废弃缓存中取出一个View,并从相对应的Type集合中获取,从最初添加的child进行获取,然后从集合中remove,看一看出每一个集合中的View也只能复用一次,然后移除。

//记录当前缓存的screen内View,包括位置
        void fillActiveViews(int childCount, int firstActivePosition) {
        //若当前数组length<childCount,重新new一个childCount长度数组
            if (mActiveViews.length < childCount) {
                mActiveViews = new View[childCount];
            }
            mFirstActivePosition = firstActivePosition;
            final View[] activeViews = mActiveViews;
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
                if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                    activeViews[i] = child;
                    //记住当前View的位置(未知根据当前View在Adapter中所有Item的相对位置)
                    lp.scrappedFromPosition = firstActivePosition + i;
                }
            }
        }
//获取指定位置的视图,获取之后从View数组中移除
        View getActiveView(int position) {
            int index = position - mFirstActivePosition;
            //activeViews和mActiveViews引用的地址是相同的
            final View[] activeViews = mActiveViews;
            if (index >=0 && index < activeViews.length) {
                final View match = activeViews[index];
                //获取到view后,当前位置view置null
                activeViews[index] = null;
                return match;
            }
            return null;
        }

根据当前View的Type和是否有瞬时状态来确定是否存储到mScrapViews中,这里代码过长因此省略了一些代码

     void addScrapView(View scrap, int position) {
            final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
            if (lp == null) {
                return;
            }
            lp.scrappedFromPosition = position;
            final int viewType = lp.viewType;
            //根据当前type判断当前scrap是否应该被回收
            if (!shouldRecycleViewType(viewType)) {
                if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                //添加到mSkippedScrap中
                    getSkippedScrap().add(scrap);
                }
                return;
            }
            ...

            //根据scrap是否有暂时状态进行存储
            final boolean scrapHasTransientState = scrap.hasTransientState();
            if (scrapHasTransientState) {
                ...
            } else {
                clearScrapForRebind(scrap);

                //根据指定Type添加到指定集合
                if (mViewTypeCount == 1) {
                    mCurrentScrap.add(scrap);
                } else {
                    mScrapViews[viewType].add(scrap);
                }

                if (mRecyclerListener != null) {
                    mRecyclerListener.onMovedToScrapHeap(scrap);
                }
            }
        }
//从废弃缓存中取出一个View,并从mScrapViews中删除
        View getScrapView(int position) {
        //根据位置获取当前child的type类型
            final int whichScrap = mAdapter.getItemViewType(position);
            if (whichScrap < 0) {
                return null;
            }
            //若只有这一种Type则直接从mCurrentScrap集合中获取child,并从集合中删除
            if (mViewTypeCount == 1) {
                return retrieveFromScrap(mCurrentScrap, position);
            } else if (whichScrap < mScrapViews.length) {
            //根据whichScrap 下表获取指定位置child,并从集合中删除
                return retrieveFromScrap(mScrapViews[whichScrap], position);
            }
            return null;
        }

通过setViewTypeCount方法可以看出来对于每一种Type的项目视图都分别创建了一个废弃视图集合来进行存储child
并在该方法中进行一些初始化,赋值等。

//为每种类型的数据项启动一个RecycleBin机制
        public void setViewTypeCount(int viewTypeCount) {
            if (viewTypeCount < 1) {
                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
            }
            //创建了一个元素为ArrayList的数组
            ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
            //为每一种ViewType创建一个ArrayList<View>
            for (int i = 0; i < viewTypeCount; i++) {
                scrapViews[i] = new ArrayList<View>();
            }
            mViewTypeCount = viewTypeCount;
            mCurrentScrap = scrapViews[0];
            mScrapViews = scrapViews;
        }

看这个方法时最好可以去看看forceLayout 和requestlayout,invalidate的区别

 //强制所有的项目视图执行一次绘制包含onMeasure,onLayout,onDraw
        public void markChildrenDirty() {
            if (mViewTypeCount == 1) {
                final ArrayList<View> scrap = mCurrentScrap;
                final int scrapCount = scrap.size();
                for (int i = 0; i < scrapCount; i++) {

                    //forceLayout强制重新执行onMeasure,onLayout,onDraw
                    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();
                    }
                }
            }
           ...
        }

RecycleBin还有一些其他的方法,但是只要把上面的五种方法看完了基本上就差不多,其余的方法也与之息息相关,很容易看懂,这里就不再叨唠了。

onLayout的两次调用

在这里onLayout会被调用两次,具体为什么会调用两次,这里给大家提供一篇博客写的比较详细的onLayout两次调用,作者写的比较详细,而且我也是看这篇文章的才知道的,尴尬了。
这里写图片描述

接下来看一下onLayout的第一次调用。至于为什么要从onLayout开始,其实大家可以看郭霖大神的Android ListView工作原理完全解析毫无疑问前辈写的就是好,我也是看着大神的博客一路走过来的。膜拜,下面就从第一次onLayout开始我的ListView源码阅读之旅。

第一次调用onLayout
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //默认会调用两次onLayout
        mInLayout = true;
       //child有适配器填充,因此第一次获取的count=0;
        final int childCount = getChildCount();
       //数据改变时,会调用 forceLayout方法强制子View重绘
        if (changed) {
            for (int i = 0; i < childCount; i++) {
                getChildAt(i).forceLayout();
            }
            //RecycleBin机制调用一下方法强制缓存的所有Type的集合元素重绘
            mRecycler.markChildrenDirty();
        }
        layoutChildren();
        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
        if (mFastScroll != null) {
            mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
        }
        mInLayout = false;
    }

值得注意的是getChildCount()方法获取的是当前ListView的子元素数量,在第一次获取时childCount =0,接下来调用layoutChildren()方法,该方法就是本次的重点,点进去发现该方法属于空类,因此是在子类中进行实现,接下来就看一看ListView的layoutChildren()方法。

@Override
    protected void layoutChildren() {
      ...
        try {
            super.layoutChildren();
            invalidate();
            if (mAdapter == null) {
                resetList();
                invokeOnItemScrollListener();
                return;
            }
            final int childrenTop = mListPadding.top;
            final int childrenBottom = mBottom - mTop - mListPadding.bottom;
            //获取当前childcount:第一次调用为0,第二次开始有值
            final int childCount = getChildCount();
           //正常情况下mLayoutMode=LAYOUT_NORMAL,所以我们只需要看default

            boolean dataChanged = mDataChanged;
            if (dataChanged) {
                handleDataChanged();
            }
           ...
            final int firstPosition = mFirstPosition;
            final RecycleBin recycleBin = mRecycler;

         //数据发生改变时,通过RecycleBin机制将所有的废弃View添加进集合中
            if (dataChanged) {
                for (int i = 0; i < childCount; i++) {
                    recycleBin.addScrapView(getChildAt(i), firstPosition+i);
                }
            } else {

                /**
                 * 第一次没有缓存childCount=0,从第二次开始缓存
                 *
                 * 数据没有改变,通过RecycleBin机制将所有的View缓存
                 */
                recycleBin.fillActiveViews(childCount, firstPosition);
            }
            //清除所有的View
            detachAllViewsFromParent();
            //recycleBin删除所有不存储在废弃View视图中的child
            recycleBin.removeSkippedScrap();

            switch (mLayoutMode) {
            ...
            default:
                /**
                 * 第一次onLayout,childcount=0,默认布局是从上往下
                 *
                 * 第二次开始,childcount开始>0
                 *
                 * mStackFromBottom:false表示从顶部向下,true表示从底部向上
                 */
                if (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 {

                    if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                        sel = fillSpecific(mSelectedPosition,
                                oldSel == null ? childrenTop : oldSel.getTop());
                    } else if (mFirstPosition < mItemCount) {
                        sel = fillSpecific(mFirstPosition,
                                oldFirst == null ? childrenTop : oldFirst.getTop());
                    } else {
                        sel = fillSpecific(0, childrenTop);
                    }
                }
                break;
            }
            /**
             *刷新上面没有被重用的缓存视图
             *
             *调用scrapActiveViews方法将mActiveViews中存储的View缓存到对应的Type类型的集合中
             *
             *注意每种Type的集合打大小不得超过mActiveViews的大小
             *
             */
            recycleBin.scrapActiveViews();

            ...
    }

因为代码比较长,所以我省略一部分非核心代码,接下来就从上往下进行分析

if (mAdapter == null) {
                resetList();
                invokeOnItemScrollListener();
                return;
            }

如果没有设置mAdapter ,则直接调用resetList()方法清除所有东西,直接return结束该方法。

final int childrenTop = mListPadding.top;
final int childrenBottom = mBottom - mTop - mListPadding.bottom;
//获取当前childcount:第一次调用为0,第二次开始有值
final int childCount = getChildCount();

因为时第一次调用onLayout方法,此时childcount==0,childrenTop 和childrenBottom 分别为ListView的可用空间的的Top和Bottom值。

 boolean dataChanged = mDataChanged;
            if (dataChanged) {
                handleDataChanged();
            }

数据发生改变时调用该方法,里面的代码还是很长的,有时间可以看一下。

 final int firstPosition = mFirstPosition;
            final RecycleBin recycleBin = mRecycler;
         //数据发生改变时,通过RecycleBin机制将所有的废弃View添加进集合中
            if (dataChanged) {
                for (int i = 0; i < childCount; i++) {
                    recycleBin.addScrapView(getChildAt(i), firstPosition+i);
                }
            } else {
                /**
                 * 第一次没有缓存childCount=0,从第二次开始缓存
                 *
                 * 数据没有改变,通过RecycleBin机制将所有的View缓存
                 */
                recycleBin.fillActiveViews(childCount, firstPosition);
            }

            // Clear out old views
            //清除所有的View
            detachAllViewsFromParent();

            //recycleBin删除所有不存储在废弃View视图中的child
            recycleBin.removeSkippedScrap();

获取RecycleBin 对象,因为是第一次onLayout,childCount==0.因此fillActiveViews方法没有起到作用,ListView到目前为止依然是空的,因此detachAllViewsFromParent也没有起到什么作用,接下来正常情况下mLayoutMode=LAYOUT_NORMAL,所以我们只需要看default

if (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 {

                    if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                        sel = fillSpecific(mSelectedPosition,
                                oldSel == null ? childrenTop : oldSel.getTop());
                    } else if (mFirstPosition < mItemCount) {
                        sel = fillSpecific(mFirstPosition,
                                oldFirst == null ? childrenTop : oldFirst.getTop());
                    } else {
                        sel = fillSpecific(0, childrenTop);
                    }
                }

此时childCount ==0, mStackFromBottom:false表示从顶部向下,true表示从底部向上,默认情况下mStackFromBottom==false。此时可以看到调用fillFromTop方法,反之调用fillUp方法。

 /**
     * nextTop表示ListView d的paddingTop有效的top距离
     *
     * mSelectedPosition默认为-1
     */
    private View fillFromTop(int nextTop) {
        mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
        mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
        if (mFirstPosition < 0) {
            mFirstPosition = 0;
        }

        //fillDown表示自顶向下填充ListView
        return fillDown(mFirstPosition, nextTop);
    }

fillFromTop方法内部调用的却是fillDown方法,可以看到两个方法fillDown和fillUp是相对应的,一个从顶部到底部,一个从底部到顶部。在这里就可以明白ListView的填充操作,在第一次调用onLayout时,就是通过fillUp和fillDown方法来实现的,这里我们来看一下fillDown方法

/**
     * pos 默认情况下=0
     *
     * nextTop代表ListView有效的顶部距离Top
     *
     * mItemCount:代表的是Adapter中数据的数量,而不是ListView中的数据数量
     *
     * getChildCount:代表的是ListView包含的子元素个数
     */
    private View fillDown(int pos, int nextTop) {
        View selectedView = null;

        int end = (mBottom - mTop);
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            end -= mListPadding.bottom;
        }

        /**
         *end是ListView底部减去顶部的像素值,mItemCount是Adapter中元素数量
         *刚开始nextTop一定小于end,且POS也小于mItemCount
         *循环一下nextTop增加,POS+1,当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);

            //循环一次高度增加:bottom+分隔符高度
            nextTop = child.getBottom() + mDividerHeight;
            if (selected) {
                selectedView = child;
            }
            //元素+1.向下遍历
            pos++;
        }

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

注释写的比较清楚,首先计算出了ListView的实际可用高度,通过判断nextTop < end && pos < mItemCount来确定是否超出屏幕,以及跳出循环。这里可以看出来ListView每次只加载一屏幕的子View。while 循环内部调用makeAndAddView方法,可以猜测内部实现了创建一个项目视图并填充ListView。

 private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        //ListView 初始化时,mRecycler中缓存的View为null
        if (!mDataChanged) {
            //第二次获取View是从缓存中读取
            final View activeView = mRecycler.getActiveView(position);

            /**
             *   在拉youtchild中第二次调用了recycleBin.fillActiveViews(childCount, firstPosition)缓存了
             *   屏幕显示的所有View,因此直接读取缓存复用,调用setupChild方法填充ListView
             */

            if (activeView != null) {
                setupChild(activeView, position, y, flow, childrenLeft, selected, true);
                return activeView;
            }
        }
        //返回一个当前位置的View,有缓存重用缓存View,没有则调用adapter的getView方法返回一个
        final View child = obtainView(position, mIsScrap);
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
        return child;
    }

可以看到首先通过mRecycler读取缓存在mActiveViews数组中的元素,因为第一次调用mActiveViews中数组值为null,因此调用obtainView(position, mIsScrap)方法来获取一个元素,可以看出该方法非常重要啊!因为该方法位于父类AbsListView中,点击去查看。

View obtainView(int position, boolean[] outMetadata) {
        ...
        //从废弃的缓存View列表中获取一个View,mRecycler会根据position获取Type来获取View
        final View scrapView = mRecycler.getScrapView(position);
        //调用Adapter的getView方法获取一个子View
        /**
         * 因为第一次调用时mRecycler钟缓存的废弃view==null,所以直接调用mAdapter.getView方法返回一个View
         *
         * 这里可以看出在我们自定义适配器时要判断convertView是否为null,
         */
        final View child = mAdapter.getView(position, scrapView, this);

        //列表滑出屏幕时,判断是否将scrapView添加进废弃View集合
        if (scrapView != null) {
            if (child != scrapView) {
                //view不匹配,重新缓存到废弃View集合中
                mRecycler.addScrapView(scrapView, position);
            } else if (child.isTemporarilyDetached()) {
                outMetadata[0] = true;
                child.dispatchFinishTemporaryDetach();
            }
        }
        ...
        return child;
    }

obtainView方法中首先从废弃缓存中根据位置获取复用View,因为我们是第一次调用,此时该方法返回空值,此时调用 mAdapter.getView方法来返回一个子元素,这个方法大家就熟悉了,估计也明白了为什么自定义适配器的时候要判断convertView为什么要判断是否为null,因为第一次测量时获取的scrapView为null,接下来直接返回child。大家可以看下上面的makeAndAddView方法,获取View后,调用了setupChild方法,看来该方法就是最后用于填充View到ListView的方法。

private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
            boolean selected, boolean isAttachedToWindow) {
        ...
        //根据Adapter中传入的一些参数设置View的参数
        p.viewType = mAdapter.getItemViewType(position);
        p.isEnabled = mAdapter.isEnabled(position);
        ...
        /**
         * 添加一个新的Item,isAttachedToWindow=false调用addViewInLayout方法
         *
         * 因为在layoutChild中我们detach了所有的子View,防止数据错乱,此时服用一个detach的
         *
         * item,isAttachedToWindow=true,调用attachViewToParent方法
         */

        if ((isAttachedToWindow && !p.forceAdd) || (p.recycledHeaderFooter
                && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
            //向上滑动flowDown=false  向下滑动flowDown=true
            //attachViewToParent中若index<0,则默认将child添加到底部
            attachViewToParent(child, flowDown ? -1 : 0, p);
            if (isAttachedToWindow
                    && (((AbsListView.LayoutParams) child.getLayoutParams()).scrappedFromPosition)
                            != position) {
                child.jumpDrawablesToCurrentState();
            }
        } else {
            p.forceAdd = false;
            if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                p.recycledHeaderFooter = true;
            }
            //调用addViewInLayout方法将child添加进ListView中
            addViewInLayout(child, flowDown ? -1 : 0, p, true);
            child.resolveRtlPropertiesIfNeeded();
        }
...
    }

因为代码过长,所以我只保留了一部分主要代码,这里需要注意到一个参数isAttachedToWindow,当我们添加的View复用与RecycleBin的mActiveViews数组时isAttachedToWindow=true,复用自废弃缓存中时isAttachedToWindow=false,分别调用attachViewToParent和addViewInLayout将View填充到ListView中,在这里我们就可以想到了虽说ListView里面的数据可能有无数条,但每次只显示一屏幕的视图,滑动时又会复用缓存的视图,因此虽然ListView数据很多,但显示的其实就是那么几个View,才能在有限的内存下显示那么多条数据。第一次onLayout就结束了,接下来看第二次

第二次调用onLayout
 final int firstPosition = mFirstPosition;
            final RecycleBin recycleBin = mRecycler;

          /**
           *数据发生改变时,通过RecycleBin机制将所有的废弃View添加进集合中
           */
            if (dataChanged) {
                for (int i = 0; i < childCount; i++) {
                    recycleBin.addScrapView(getChildAt(i), firstPosition+i);
                }
            } else {

                /**
                 * 第一次没有缓存childCount=0,从第二次开始缓存
                 *
                 * 数据没有改变,通过RecycleBin机制将所有的View缓存
                 */
                recycleBin.fillActiveViews(childCount, firstPosition);
            }

第二次调用layoutChildren方法,此时getChildCount()有数据,为一屏幕显示的视图个数,因此调用recycleBin.fillActiveViews来缓存当前屏幕上视图和位置。接下来看下面一句代码,我觉得很有必要解释一下

detachAllViewsFromParent();

调用该方法,来detach掉屏幕上的所有的View,防止数据的显示混乱,你们可能会说那样再次绘制数据不是消耗性能吗,这时就是RecycleBin缓存机制的作用体现了, 看到上面recycleBin.fillActiveViews(childCount, firstPosition)方法,此时已经将屏幕上的View缓存,在此展示时只需要再次调用缓存就行。

 if (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 {

                    if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                        sel = fillSpecific(mSelectedPosition,
                                oldSel == null ? childrenTop : oldSel.getTop());
                    } else if (mFirstPosition < mItemCount) {
                        sel = fillSpecific(mFirstPosition,
                                oldFirst == null ? childrenTop : oldFirst.getTop());
                    } else {
                        sel = fillSpecific(0, childrenTop);
                    }
                }
                break;

因为childCount有值,大于0的话将调用fillSpecific方法,看一下该方法

/**
     * 第二次调用onLayout,ListView包裹的子元素>0时会调用该方法
     *
     * position:选择的child,默认0
     *
     * top:当前选择的child的top,默认为listView的有效顶部top
     */
    private View fillSpecific(int position, int top) {

        boolean tempIsSelected = position == mSelectedPosition;

        //优先加载指定位置的View,然后加载该子View往上和往下的View,默认情况下加载第一个View,然后往下加载
        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;  //上一个View
        View below;  //下一个View

        final int dividerHeight = mDividerHeight;

        //判断现先加载该View上面的View还是下面的
        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;
        }
    }

可以看到该方法首先加载指定位置的View,然后判断是加载在该View上面或者下面的View,又是分别调用fillUp和fillDown方法,接下来看一下内部调用的makeAndAddView方法

if (!mDataChanged) {
            //第二次获取View是从缓存中读取
            final View activeView = mRecycler.getActiveView(position);

            /**
             *   在拉youtchild中第二次调用了recycleBin.fillActiveViews(childCount, firstPosition)缓存了
             *   屏幕显示的所有View,因此直接读取缓存复用,调用setupChild方法填充ListView
             */

            if (activeView != null) {
                // Found it. We're reusing an existing child, so it just needs
                // to be positioned like a scrap view.
                setupChild(activeView, position, y, flow, childrenLeft, selected, true);
                return activeView;
            }
        }

此时属于第二次调用onLayout方法,此时mRecycler.getActiveView(position)获取的缓存View不为null,因此直接复用缓存View,与我们刚才讲的相对应,若为null的话,则继续调用obtainView方法,

 //从废弃的缓存View列表中获取一个View,mRecycler会根据position获取Type来获取View
        final View scrapView = mRecycler.getScrapView(position);

        //调用Adapter的getView方法获取一个子View
        /**
         * 因为第一次调用时mRecycler钟缓存的废弃view==null,所以直接调用mAdapter.getView方法返回一个View
         *
         * 这里可以看出在我们自定义适配器时要判断convertView是否为null,
         *
         *
         */
        final View child = mAdapter.getView(position, scrapView, this);

        //列表滑出屏幕时,判断是否将scrapView添加进废弃View集合
        if (scrapView != null) {
            if (child != scrapView) {

                //view不匹配,重新缓存到废弃View集合中
                mRecycler.addScrapView(scrapView, position);
            } else if (child.isTemporarilyDetached()) {
                outMetadata[0] = true;

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

mRecycler.getScrapView(position)获取的废弃缓存若存在则复用,最后继续调用setupChild方法,还记得上面第一次调用时我们队addViewInLayout和attachViewToParent区别的讲解吗,简单来说就是添加一个新的子View则调用addViewInLayout方法,如果想复用一个detach掉的子View则调用attachViewToParent方法。关于ListView的两次调用,以及缓存的机制就先讲到这里了。下一次就是ListView的滑动策略了,下次再见!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值