ListView源码分析

最近读了很多关于ListView源码的文章,一是面试的是时候面试官喜欢问一下源码的问题,二来源码里面包含了开发者丰富的设计思想,特别是代码写多了,不应该再用API来堆代码了,应该去了解一下为什么要这样做。呃呃,一下子扯远了。ListView源码的文章,我比较推荐郭神的《AndroidListView工作原理完全解析,带你从源码的角度彻底理解》,讲的很清晰,这篇文章是自己的一些总结。

   继承关系

ListView继承结构如下图:


从继承关系上来说,ListView直接继承自AbsListView,AbsListView有两个直接子类,一个是ListView,另一个是GridView。从用法上他们两就有点相似,源码上更是有很多相通之处。

   缓存机制RecycleBin

ListView真是有了缓存机制,才使得ListView加载成千上万条数据不会出现OOM。ListView的缓存机制是靠RecycleBin实现的。RecycleBin中有两个最重要的变量:

*/
private View[] mActiveViews = new View[0];

/**
 * Unsorted views that can be used by the adapter as a convert view.
 */
private ArrayList<View>[] mScrapViews;

这两个变量是实现缓存机制的关键,其中mActiveViews保存屏幕中存活的Item,mScrapViews存放已经废弃的View;然后RecycleBin中定义了关于操作mActiveViews和mScrapViews的几个函数:

  • 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当中有这样一个功能就行了。

   绘图流程

Android View的绘制流程分三步:测量(measureàonMeasure),布局(layoutàonLayout),绘制(drawàonDraw)。Listview作为一个View,也要经历这些过程,onMeasure()并没有什么特殊的地方,因为它终归是一个View。onDraw()在ListView当中也没有什么意义,因为ListView本身也不负责绘制,而是由ListView当中的子元素来进行绘制的。那么ListView大部分的神奇功能其实都是在onLayout()方法中进行的了ListView的onLayout流程还是比较复杂,具体流程可以去看郭神的博客,我这里就简单总结一下,

ListView的onLayout会去调用layoutChildren,layoutChildrenàfillFromTopàfillDownàmakeAndAddViewàsetupChild


  再谈缓存机制

在上一下节中,我们将ListView的绘制流程弄清楚了,ListView的缓存机制体现在makeAndAddView,makeAndAddView的作用是构造一个Item View,并将其添加到ListView,makeAndAddView内部流程是:数据集是否有变化,如果没有直接从mActiveVie获取View,如果有变化,会调用obtainView获取item view,obtainView内部通过getScrapView()方法来从mScrapView尝试获取一个废弃缓存中的View,如果有就复用这个View,没有就调用mAdapter的getView()方法来去获取一个View,那么mAdapter是什么呢?当然就是当前ListView关联的适配器了。而getView()方法又是什么呢?还用说吗,这个就是我们平时使用ListView时最最经常重写的一个方法了,这里getView()方法中传入了三个参数,分别是position,null和this。

那么我们平时写ListView的Adapter时,getView()方法通常会怎么写呢?这里我举个简单的例子:

@Override

public ViewgetView(int position, View convertView, ViewGroup parent) {

       Fruit fruit = getItem(position);

       View view;

       if (convertView == null) {

              view =LayoutInflater.from(getContext()).inflate(resourceId, null);

       } else {

              view = convertView;

       }

       ImageView fruitImage = (ImageView)view.findViewById(R.id.fruit_image);

       TextView fruitName = (TextView)view.findViewById(R.id.fruit_name);

       fruitImage.setImageResource(fruit.getImageId());

       fruitName.setText(fruit.getName());

       return view;

}

getView()方法接受的三个参数,第一个参数position代表当前子元素的的位置,我们可以通过具体的位置来获取与其相关的数据。第二个参数convertView,刚才传入的是null,说明没有convertView可以利用,因此我们会调用LayoutInflater的inflate()方法来去加载一个布局。接下来会对这个view进行一些属性和值的设定,最后将view返回。那么这个View也会作为obtainView()的结果进行返回,并最终传入到setupChild()方法当中

总结起来,ListVie的View从三个方面获得,一是从mActiveViews中获取,二是mAdapter中获取,三是从mScrapViews中获取了,最后在调用setupChild方法把makeview添加到ListView上去。流程如下:


滑动加载

ListView滑动过程中 ListView中的视图是会随时变化的ListView滑动加载过程是在他的父类AbsListView中的onTouchEvent中实现的,具体过程可以去看郭神的博客,这里还是总结一下。onTouchEvent中会判断滑动方向和滑动距离,ListView的子View根据bottom值和top值判断是否滑出屏幕,view如果滑出屏幕会调用RecycleBinaddScrapView()方法将这个View加入到废弃缓存当中,接着会调用detachViewFromParent将需要移除的 view移出 ListView,还会将仍在布局中的View调用调用了offsetChildrenTopAndBottom()方法根据滑动距离做出移动,最后如果有View移出屏幕,则通过 fillGap方法充新的 View。在fillGap中又会调用fillDown fillUp方法,又回到了第二节中ListView绘制过程,最终用makeAndAddView方法来获取View,这次obtainView会从RecycleBin中的废弃View中取出需要的 View,由于滑出屏幕的 View已经添加到RecycleBin中,所以这里不为 null,在Adapter.getView时就可以重用被回收的View,实现了 ListView虽然有千百条数据,但是真正的 itemView确只有很少的几个。



文中部分内容来源于:

http://blog.csdn.net/guolin_blog/article/details/44996879

http://www.jianshu.com/p/ad621b49735e

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值