ListView无疑是Android开发中使用最多的组件之一了,可以肯定是99%以上的应用中都是用了ListView,不过ListView也不是万能的,很多时候你会觉得ListView提供给我们的功能并不够,我们需要扩展ListView,或者重新自定义一个支持滚动,view重用特性的组件。如果我们能够了解ListView的内部实现原理,相信对于更好地利用ListView以及对其进行扩展都是不错的。
转载请注明出处: http://blog.youkuaiyun.com/qinjunni2014/article/details/45653747
1. view的重用
ListView最重要的特性就是对view进行了重用,我们只要理解了view的重用原理,就对listview有了很大程度的了解。我们来看看它是如何做的呢?在将这个之前,让我们简要看看ListView的继承结构,ListView继承自AbsListView,而AbsListView继承自AdapterView,AdapterView继承自ViewGroup,因此我们从AdapterView说起就比较明了了。
AdapterView是一个抽象类,里面定义很多接口方法,有待子类去实现,不过也实现了一部分的方法。如下图所示
其中有一部分是继承自ViewGroup的,比如AddView系列以及removeView系列,Accessibility系列。这里我们就不多讲了,相信大家也很熟悉,除此之外就是AdapterView自己的函数,AdapterView顾名思义肯定是根据一个Adapter来生成它内部的view。说到这,我们又不得不引出Adapter这个接口,注意这是个接口,它只是定义了一些接口函数。
这些函数规定了适配器内部每个item的类型,id,每个item对应的view,以及总公的item数量。此外有一个函数需要注意的是hasStableIds(), 这个函数返回一个bool值,如果为true,代表同一个id总是对应同一个item。这个函数在稍后我们将view重用时会用到。AdapterView接口规定了使用Adapter来生成view的函数。
2. RecycheBin
关于view重用的代码大部分都在AbsListView里面,那是因为不光是ListView, 还有GridView也继承了AbsListView,他们都是使用了相同的view重用的原理。我们直接看AbsListView,这个类比较复杂,不过不要担心,我们就从view重用这一点慢慢展开,我们可以看到它有一个内部类叫RecycleBin,正是这个类实现了view的重用,看名字也可以看出。它的成员比较简单,就这些:
private int mFirstActivePosition;
private View[] mActiveViews = new View[0];
private ArrayList<View>[] mScrapViews;
private int mViewTypeCount;
private ArrayList<View> mCurrentScrap;
private ArrayList<View> mSkippedScrap;
private SparseArray<View> mTransientStateViews;
private LongSparseArray<View> mTransientStateViewsById;
mActiveViews代表了在每一次layout开始的时候,位于屏幕上的view, mFirstActivePosition指定了第一个active的view的位置。对于mActiveViews通常的操作为,在每次layout开始,AbsListView会将位于屏幕上的view全部填充到RecycleBin的mActiveViews中去。layout过程中,将下一轮即将显示在屏幕上得view从RecycleBin中取出来,最后如果mActiveViews中还有元素,就在layout结束时将它们统统转移到mScrapView中去。这个流程可以从ListView中的layoutChildren看出,每次layout时,onlayout最终会调用这个函数
@Override
protected void layoutChildren() {
//....忽略一大坨代码
// layout开始前将所有已经存在的子view放入recycleBin中
// 如果dataChanged为true,就放入mActiveViews中,否则放入mScrapViews中去
final int firstPosition = mFirstPosition;
final RecycleBin recycleBin = mRecycler;
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
} else {
recycleBin.fillActiveViews(childCount, firstPosition);
}
// Clear out old views
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();
//重新填充子view...
// 将所有没有被重用到的view从mActiveViews转移到mScrapViews中去
recycleBin.scrapActiveViews();
}
从layout的过程就可以看出来,scrapview实际上是一个存放备用view的回收池,每次layout完,有多余的view会存储到池子里,以后可能会用到。那这是layout时候做的事情,如果是scroll的情况呢,情况其实类似。我们来看看scroll的流程图
scroll事件从onTouchEvent函数发起,大家肯定知道的,中间经过一些判断,最终带着deltaY和incrementalDetalY到达trackMotionScroll函数,我们的分析从这个函数开始.