本博客参考了地址:点击打开链接
在刚开始接触学习Android基础的时候,ListView算是一个比较神奇的控件了,因为那时候好多效果都可以用它实现,而且用它就得用到一个设计模式,[适配器].结果昨天遗留下来一个bug,带这个解决这个Bug去翻看了5.0.1 API22的ListView部分源码分析复用.
复用到底有什么用.?简单的举个例子,假如你想要展示一万条item,作为手机不可能一下将一万条同时加载进去,这样肯定会OOM的,所以Google开发者想到了复用,也算是ListView高级的一个特点.
竟然复用的作用明白了,那Android到底是怎么复用的啊,?先看看ListView的结构图.
可以看到ScrollView,ListView,ExpanableListView,GridView都是继承于ViewGroup,说到这儿我想起一个隐藏的CallBack:OverScrollBy();
该方法算是Google隐藏起来了的,应该是为了模仿IOS的回弹阻尼效果,结果....
不过通过该方法还是能很轻易的实现,当然在新版本的RecycleView并不会回调该方法了.
关系中复用的核心类主要是放在了:AbsListView中的RecycleBin类中.该类源码中也详细说了两个重要的对象:ActiveViews,ScrapViews.先看说明然后再分析两个对象.
/** * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the * start of a layout. By construction, they are displaying current information. At the end of * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that * could potentially be used by the adapter to avoid allocating views unnecessarily. * * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) * @see android.widget.AbsListView.RecyclerListener */ActiveViews顾名思义:当前活动,什么叫当前活动的,就是当前屏幕上可视的VIew,并且第一次被创建.
ScrapViews:被废弃回收的视图,就是指当前手指滑出屏幕外的被回收的视图.
可以看出来这两个对象非常重要,该类中的一些方法:
/** * Puts all views in the scrap heap into the supplied list. */ void reclaimScrapViews(List<View> views) { if (mViewTypeCount == 1) { // 这的Type是默认的,表示说如果你的Item只有一种类型,就只有一个集合回收, 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); } } }
/** * Returns the height of the view for the specified position. * * @param position the item position * @return view height in pixels */ int getHeightForPosition(int position) { final int firstVisiblePosition = getFirstVisiblePosition(); final int childCount = getChildCount(); final int index = position - firstVisiblePosition; // 计算索引 if (index >= 0 && index < childCount) { // 在Childs范围内 // Position is on-screen, use existing view. final View view = getChildAt(index);// 直接查找 return view.getHeight(); } else { // Position is off-screen, obtain & recycle view. final View view = obtainView(position, mIsScrap); // 超出屏幕外,Obtain, view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);//测量子孩子. final int height = view.getMeasuredHeight(); mRecycler.addScrapView(view, position); // 添加到废弃的Views中, return height; } }
/** * Scroll the children by amount, adding a view at the end and removing * views that fall off as necessary. * * @param amount The amount (positive or negative) to scroll. */ private void scrollListItemsBy(int amount) { offsetChildrenTopAndBottom(amount); final int listBottom = getHeight() - mListPadding.bottom;//底部位置 final int listTop = mListPadding.top;// 顶部位置 final AbsListView.RecycleBin recycleBin = mRecycler; if (amount < 0) { // shifted items up // may need to pan views into the bottom space int numChildren = getChildCount(); View last = getChildAt(numChildren - 1); // 获取最后一个item while (last.getBottom() < listBottom) {// 最后一个Item的底部比较. final int lastVisiblePosition = mFirstPosition + numChildren - 1; if (lastVisiblePosition < mItemCount - 1) { last = addViewBelow(last, lastVisiblePosition);//将新的View添加到下面, numChildren++; } else { break; } } // may have brought in the last child of the list that is skinnier // than the fading edge, thereby leaving space at the end. need // to shift back if (last.getBottom() < listBottom) { offsetChildrenTopAndBottom(listBottom - last.getBottom()); } // top views may be panned off screen View first = getChildAt(0); while (first.getBottom() < listTop) {//顶部的View的比较 AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams(); if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) { recycleBin.addScrapView(first, mFirstPosition); // 顶部是直接添加的了回收对象集合中去了. } detachViewFromParent(first);//并从ViewGroup集合中移除该View,这的移除待会分析. first = getChildAt(0); // 重新获取第一个位置循环,这里的第一个已经是下一个的意思了. mFirstPosition++; } } else { // 下滑 // shifted items down View first = getChildAt(0); // may need to pan views into top while ((first.getTop() > listTop) && (mFirstPosition > 0)) { first = addViewAbove(first, mFirstPosition); mFirstPosition--; } // may have brought the very first child of the list in too far and // need to shift it back if (first.getTop() > listTop) { offsetChildrenTopAndBottom(listTop - first.getTop()); } int lastIndex = getChildCount() - 1; View last = getChildAt(lastIndex); // bottom view may be panned off screen while (last.getTop() > listBottom) { AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams(); if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) { recycleBin.addScrapView(last, mFirstPosition+lastIndex); } detachViewFromParent(last); last = getChildAt(--lastIndex); } } }
// This method also sets the child's mParent to null private void removeFromArray(int index) { final View[] children = mChildren; if (!(mTransitioningViews != null && mTransitioningViews.contains(children[index]))) { children[index].mParent = null; // 并未直接Remove,等待GC去回收. } final int count = mChildrenCount; if (index == count - 1) { children[--mChildrenCount] = null; } else if (index >= 0 && index < count) { System.arraycopy(children, index + 1, children, index, count - index - 1); children[--mChildrenCount] = null; } else { throw new IndexOutOfBoundsException(); } if (mLastTouchDownIndex == index) { mLastTouchDownTime = 0; mLastTouchDownIndex = -1; } else if (mLastTouchDownIndex > index) { mLastTouchDownIndex--; } }
所以添加到ScrapViews中后,该View仅仅是处于游离状态.
ListView可以设置setRecyclerListener,该接口会调用到RecycleBin中的reclaimView中,也是上面分析,
@Override public void onMovedToScrapHeap(View view) { //view是Item的Viewgroup. }
参考Demo:http://blog.csdn.net/u010316858/article/details/47440841
版权声明:本文为博主原创文章,未经博主允许不得转载。