本博客参考了地址:点击打开链接
在刚开始接触学习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