1.什么是ListView
在android开发中ListView是非常常用的组件,它以列表的形式来表示,即使是数据够大,也能根据数据的长度进行自适应来显示。在一定条件下,其加载大量的数据也不会发生OOM。
在android开发文档可以看到ListView是直接继承于AbsListView,AbsListView又继承AdapterView,AdapterView继承ViewGroup:
用过listview的开发者都知道adapter,至于为什么要出现adapter呢,试想一下:如果没有adapter,让listview和数据源直接接触,那么listview就要做大量的适配工作,因为数据源的类型有太多,有可能是一个数组,也有可能是一个集合等等。如果listview去和每一种数据源进行适配的话会导致代码极其臃肿并且违背了它所显示的原则。这时候很有必要需要一个东西去做适配工作,android就出现了adapter这个机制,原理如下图:
adapter中除了要适配数据源的工作以外还要写一些逻辑是在getView()。
2.ListView工作原理
ListView的工作原理是:例如当屏幕需要显示9个item时,那么ListView只会创建10个视图,当第一个item离开屏幕时,那么第一个item就会拿来复用,用来显示第10个item,这种设计是非常棒的。最重要的一个原因是RecycleBin机制,RecycleBin是作为内部类在AbsListView里面,因此ListView会用到其机制。那么现在仔细分析RecycleBin:
(1)成员变量:
(2)setViewTypeCount(int viewTypeCount)是初始化
(3)fillActiveViews(int childCount,int firstActivePosition)来存放可见的view,根据参数意思:childCount存放View的数量,firstActivePosition是第一个可见的位置
(4)addScrapView(View scrap,int position)来存放不在于屏幕内的View
(5)getScrapView(int position)从缓存的View获取
(6)getActiveView(int position)这个方法是得到屏幕上某个位置的显示的view
(7)getTransientStateView(int position) 获取具有TransientState状态的View,获取后会移除
(8)retrieveFromScrap(ArrayList<View> scrapViews,int position)根据参数可知 是从缓存数组获取对应的view
(9)clearTransientStateViews() 清除具有TransientStateViews的View
(10)pruneScrapViews()当缓存的数组大于屏幕上活跃的数组后就会走这个方法,在scrapActiveViews()这个方法内调用,至于为什么要清理呢?主要是因为TransientStateView数组中的View的transient属性消失后,会缓存两次,一遍是具有transientState的view,另一遍是View不具有transientState又会缓存一遍,这样缓存中就会导致活跃中的数组,因此需要处理。
(11)markChildrenDirty()可以看到里面调用缓存的view的forceLayout,这个方法只会执行自己的onMeasure()和onLayout()方法,当ListView的size改变时进行调用。
3.ListView的绘制
因为ListView 和 GridView都是继承View的,那缓存的逻辑应该写在AbsListView里面,那里面肯定有onMeasure()方法和onLayout()方法,去看看:
可以看见onLayout()方法其实就是当ListVIew发生变化后,要求子布局进行重绘。
在看ListView中的layoutChildren();这个方法源码就不贴了,这个方法主要是确定布局模式,并且确定布局顺序,从顶到底去填充ListView,里面有fillFromTop(int nextTop)这个方法里面调用fillDown(int pos,int nextTop)这个方法主要是遍历当前显示在屏幕上的view,并且缓存起来通过执行makeAndAddView()方法,里面在调用setupChild()方法,最后实际上调用obtainView()方法去获取一个view,里面实际上调用了我们最熟悉的getView(),参数有三个分别是位置,convertView,还有this,也就是说当填满整个屏幕后,就不在绘制了。另外第二次Layout郭霖博客已经描述很清晰了,这里不再叙述。具体是:先调用detachAllViewsFromParent()方法清掉所有的view,然后再用缓存的view去添加进行加载,最后不会执行obtainView(),因为如果又要infalte,使效率大大降低,最后重新attachViewToParent()方法。
简单的流程图如下: