ListView原理分析

来源:互联网 发布:光盘刻录大师for mac 编辑:程序博客网 时间:2024/05/11 04:54

类继承关系

类继承关系


View、ViewGoup

View、ViewGoup不用多说,是视图和视图容器的基类。

AdapterView

通过观察者模式来控制view的一些基本动作。

AbsListView

通过RecycleBin类,负责处理view的生成和回收,没有子视图的空间定位相关,并负责处理滑动事件相关的东西。

ListView

垂直列表显示数据,具体控制列表项的排布和位移。

 

RecycleBin机制

下面来介绍下RecycleBin机制:

RecycleBin机制是用来回收和生成view的,因为存在这个机制listView能加载成千上百条数据都不会oom。

RecycleBin最出要的是包含两个变量,mActiveViews和mScrapViews。

mActiveViews是一个View[]的view数组,它表示缓存目前视图活跃的view。

mScrapViews是一个ArrayList<View>[]的数组它会依据viewTypeCount来生成一个list对每个viewtype都会生成1个view[],它是一个废弃缓存,表示将这种viewtype移除页面的view缓存在对于的view[]里面。

主要方法介绍:

  • fillActiveViews() 这个方法接收两个参数,第一个参数表示要存储的view的数量,第二个参数表示ListView中第一个可见元素的position值。RecycleBin当中使用mActiveViews这个数组来存储View,调用这个方法后就会根据传入的参数来将ListView中的指定元素存储到mActiveViews数组当中。
  • getActiveView() 这个方法和fillActiveViews()是对应的,用于从mActiveViews数组当中获取数据。该方法接收一个position参数,表示元素在ListView当中的位置,方法内部会自动将position值转换成mActiveViews数组对应的下标值。需要注意的是,mActiveViews当中所存储的View,一旦被获取了之后就会从mActiveViews当中移除,下次获取同样位置的View将会返回null,也就是说mActiveViews不能被重复利用。
  • addScrapView() 用于将一个废弃的View进行缓存,该方法接收一个View参数,当有某个View确定要废弃掉的时候(比如滚动出了屏幕),就应该调用这个方法来对View进行缓存,RecycleBin当中使用mScrapViews和mCurrentScrap这两个List来存储废弃View。
  • getScrapView 用于从废弃缓存中取出一个View,这些废弃缓存中的View是没有顺序可言的,因此getScrapView()方法中的算法也非常简单,就是直接从mCurrentScrap当中获取尾部的一个scrap view进行返回。
  • setViewTypeCount() 我们都知道Adapter当中可以重写一个getViewTypeCount()来表示ListView中有几种类型的数据项,而setViewTypeCount()方法的作用就是为每种类型的数据项都单独启用一个RecycleBin缓存机制。实际上,getViewTypeCount()方法通常情况下使用的并不是很多,所以我们只要知道RecycleBin当中有这样一个功能就行了。


布局机制

布局机制从onLayout开始,进入onLayout方法后,主要进入listview的layoutChildren函数,layoutChildren函数有很多代码,主要进入到fillFromTop函数里进行从上往下顺序的布局,fillFromTop函数里没有什么太多的内容,进入fillDown函数。


 

fillDown函数里使用了一个while循环来执行重复逻辑,一开始nextTop的值是第一个子元素顶部距离整个ListView顶部的像素值,pos则是刚刚传入的mFirstPosition的值,而end是ListView底部减去顶部所得的像素值,mItemCount则是Adapter中的元素数量。因此一开始的情况下nextTop必定是小于end值的,并且pos也是小于mItemCount值的。那么每执行一次while循环,pos的值都会加1,并且nextTop也会增加,当nextTop大于等于end时,也就是子元素已经超出当前屏幕了,或者pos大于等于mItemCount时,也就是所有Adapter中的元素都被遍历结束了,就会跳出while循环。

在while循环里进入makeAndAddView方法。


这里尝试从RecycleBin当中快速获取一个active view,如果目前RecycleBin当中还没有缓存任何的View,所以这里得到的值肯定是null。那么取得了null之后就会继续向下运行,接下来会调用obtainView()方法来再次尝试获取一个View,obtainView()方法里通过Adapter的getView方法是可以保证一定返回一个View的,如果RecycleBin当中缓存了相关View也是进入setupView方法,下面立刻将获取到的View传入到了setupChild()方法当中。



 

setupChild()方法当中的代码虽然比较多,但是我们只看核心代码的话就非常简单了,如果recycled参数为false,这里调用了addViewInLayout()方法将它添加到了ListView当中。

如果recycled是true,会执行attachViewToParent()方法。这两个方法最大的区别在于,如果我们需要向ViewGroup中添加一个新的子View,应该调用addViewInLayout()方法,而如果是想要将一个之前detach的View重新attach到ViewGroup上,就应该调用attachViewToParent()方法。如果多次onLayout,会调用detachAllViewsFromParent()方法,这样ListView中所有的子View都是处于detach状态的,会使用attachViewToParent()。

基于这样的layout机制,会让子元素View将整个ListView控件填满然后就跳出,也就是说即使我们的Adapter中有一千条数据,ListView也只会加载第一屏的数据,剩下的数据反正目前在屏幕上也看不到,所以不会去做多余的加载工作,这样就可以保证ListView中的内容能够迅速展示到屏幕上。

 

滑动机制

滑动机制就是listview如何在滑动中进行布局。

要看滑动机制,首页的看onTouchEvent()函数,它在AbsListView当中



当手指在屏幕上滑动时,这一个事件对应的是ACTION_MOVE这个动作。

进入onTouchMove()这个函数。


这里有一个switch语句,当手指在屏幕上滑动时,TouchMode是等于TOUCH_MODE_SCROLL。

进入scrollIfNeeded()方法,scrollIfNeeded方法内容有点多,这里就不贴出来了,然后会在scrollIfNeed方法里进入trackMotionScroll()方法。trackMotionScroll()这个方法只要手指在屏幕上稍微有一点点移动,这个方法就会被调用,而如果是正常在屏幕上滑动的话,那么这个方法就会被调用很多次。




这个方法接收两个参数,deltaY表示从手指按下时的位置到当前手指位置的距离,incrementalDeltaY则表示据上次触发event事件手指在Y方向上位置的改变量,那么其实我们就可以通过incrementalDeltaY的正负值情况来判断用户是向上还是向下滑动的了。如果incrementalDeltaY小于0,说明是向下滑动,否则就是向上滑动。

下面将会进行一个边界值检测的过程,可以看到,当ListView向下滑动的时候,就会进入一个for循环当中,从上往下依次获取子View,如果该子View的bottom值已经小于top值了,就说明这个子View已经移出屏幕了,所以会调用RecycleBin的addScrapView()方法将这个View加入到废弃缓存当中,并将count计数器加1,计数器用于记录有多少个子View被移出了屏幕。那么如果是ListView向上滑动的话,其实过程是基本相同的,只不过变成了从下往上依次获取子View,然后判断该子View的top值是不是大于bottom值了,如果大于的话说明子View已经移出了屏幕,同样把它加入到废弃缓存中,并将计数器加1。

接下来在 if (count > 0)这行,会根据当前计数器的值来进行一个detach操作,它的作用就是把所有移出屏幕的子View全部detach掉,在ListView的概念当中,所有看不到的View就没有必要为它进行保存,因为屏幕外还有成百上千条数据等着显示呢,一个好的回收策略才能保证ListView的高性能和高效率。紧接着调用了offsetChildrenTopAndBottom()方法,并将incrementalDeltaY作为参数传入,这个方法的作用是让ListView中所有的子View都按照传入的参数值进行相应的偏移,这样就实现了随着手指的拖动,ListView的内容也会随着滚动的效果。

然后在会进行判断,如果ListView中最后一个View的底部已经移入了屏幕,或者ListView中第一个View的顶部移入了屏幕,就会调用fillGap()方法,那么因此我们就可以猜出fillGap()方法是用来加载屏幕外数据的。

fillGap是在ListView.java中实现的


down参数用于表示ListView是向下滑动还是向上滑动的,可以看到,如果是向下滑动的话就会调用fillDown()方法,而如果是向上滑动的话就会调用fillUp()方法。那么这两个方法我们都已经非常熟悉了,内部都是通过一个循环来去对ListView进行填充。

进入fillUp或者进入fillDown之后,仍然会进入makeAndAddView,由它来负责加载view,逻辑和布局机制里讲的类似,这里就不再赘述。 

好了,listview的一些主要逻辑到这里就分析完毕。这里的截图使用的是android 23的api,讲得比较简略。


注:本文大段文字依赖了这么下面两个博客

http://blog.csdn.net/mba16c35/article/details/43638793

http://blog.csdn.net/chenkai19920410/article/details/49310315


0 0
原创粉丝点击