前两天看到葱花同学的文章RecyclerView 和 ListView 使用对比分析,加上之前在公司做code reiview的时候也遇到过RecyclerView和ListView比较的问题,所以想在这里写一篇文章。
这篇文章的重点不会放在两者使用上的不同,因为前面提到的RecyclerView 和 ListView 使用对比分析这篇文章已经写的非常完美了,我就不重复造轮子了,主要想说明的问题和标题一样,就是我们开发者[为什么]要使用RecyclerView去代替ListView。
前言
说起RecyclerView,大家的第一反应就是它是一个威力加强版的ListView,所以在很多时候,我们会对其造成一定的误解——什么?界面顿卡了?上RecyclerView!什么?组件出bug了?上RecyclerView!等等等等…….
首先我们还是先看看Google官方是如何定义RecyclerView的。
The RecyclerView widget is a more advanced and flexible version of ListView. This widget is a container for displaying large data sets that can be scrolled very efficiently by maintaining a limited number of views. Use the RecyclerView widget when you have data collections whose elements change at runtime based on user action or network events.
这段解释意境很清楚,我们确实可以把它看作一个[advanced]的ListView,但是这里我想说的是,千万不要把RecyclerView看成能和ListView[等价替换]的一个组件,更不要把它看做是拯救你滑动组件的救星。
使用过RecyclerView的同学都知道,这个组件是[比较难用]的,为什么这么说呢,我们来想想原来使用ListView的时候,好像一切都已经封装好了,一个api就能添加header和footer,点击事件有对应的listener等等。而RecyclerView好像什么都没有,没有点击事件,没有header和footer,甚至连最起码的divider都没有,什么都要自己写,用起来简直让人抓狂,好像它唯一的优点就是那个无法从代码中体现的[性能]了。
其实并不是这样的,这篇文章也是想让阐述这样的一个观点,让大家知道Google之所以这样做是有他们这样做的道理的。
首先,让我们从性能这一方面入手,看看两个组件在它们各自[回收机制]上有什么区别。
收回机制比较
首先先看ListView的,想要搞清楚回收机制,我们当然要去看它的onTouchEvent中对应的ACTION_MOVE方法。
ListView继承自AbsListView,所以其滑动逻辑会在AbsListView中处理,最终会走到trackMotionScroll这个方法。
1 | boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { |
逻辑非常的多,我们看主要的。这个方法的两个入参scrollY表示从开始滑动的时候到当前滑动的y轴的距离,incrementalDeltaY表示滑动时y轴的增量值,这个值可以通过判断正负来确定是向上滑还是向下滑。这里我们以一个方向为准,如果整个滑动是向下滑的,也就是局部变量down为true,那么在这种情况下,如果child.getBottom() < top,也就是说bottom值小于top值,那么说明在向下滑动这种场景下,这个child已经滑出屏幕不在显示范围内了,于是,AbsListView就调用了mRecycler.addScrapView(child, position);这个函数将其加入到了mRecycler中。这里有一个很重要的点,就是mRecycler,它也是AbsListView和它的子类(ListView GridView等等)能正常运作最主要原因。
1 | final RecycleBin mRecycler = new RecycleBin(); |
可以看到它其实是一个RecycleBin对象。
1 | class RecycleBin { |
可以看到刚刚说起的那个addScrapView方法,其中有一段方法:
1 | if (mViewTypeCount == 1) { |
根据ViewTypeCount去添加已经废弃的View。从这个方法就可以说明,ListView的回收机制是和ViewType有关的,每一个ViewType对应一个对应的废弃list。
现在我们可以知道,在AsbListView中,如果一个View已经滑出了屏幕,那么说明它已经被废弃(scrap)了,就会被添加到RecycleBin中(transient的View除外,这个忽略)。那么我们什么时候去取出这里面存储的View呢?让我们回到onTouchEvent函数。
我们可以看到在trackMotionScroll函数中有一个fillGap方法。
1 | abstract void fillGap(boolean down); |
这是一个抽象方法,这是合理的,因为AbsListView的子类显示情况都不一样,ListView有ListView的显示,GridView有GridView的显示,所以需要它们自己去判断。
1 | void fillGap(boolean down) { |
在这个函数中,通过down这个参数判断滑动方向,如果向下滑,则调用fillDown方法,否则调用fillUp方法。而在这两个方法中,都会去调用obtainView方法从对应的RecycleBin中取出scrapView填充到AbsListView中。
1 | View obtainView(int position, boolean[] isScrap) { |
这下我们就清楚了,原来通过RecycleBin这样一个东西,就算有再多的item,AbsListView中永远只会存在那么几个,滑出屏幕的child回收,然后再重用,于是就尽可能的避免了oom的发生。
接下去,让我们看看RecyclerView是怎么做的,在RecyclerView的ACTION_MOVE方法中,实际会调用其内部的LayoutManager的scrollBy方法去完成滑动,这里我们看LinearLayoutManager的实现。
1 | int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { |
其中的fill方法最终会调用到layoutChunk方法,而在这个方法中,会去recycler中取废弃的View进行填充。说到这儿大家可以发现,其实RecyclerView和AbsListView的回收机制在大致框架上是一致的,就是通过一个类似Recycler的回收器去存储和获取废弃的View,下面让我们看看RecyclerView的回收器。
1 | public final class Recycler { |
可以看到在Recycler中存这么多的List,而在获取View的过程中,Recycler会先从scrap的List中获取,如果没有获取到就从cache中获取。另外还有ViewCacheExtension和RecycledViewPool这两个额外的选项需要开发者手动去设置。
从这里我们可以看出,RecyclerView提供了比AbsListView更加完善的回收机制,配以细节的优化和postOnAnimation方法所保证的”Android 16ms”机制,RecyclerView在滑动性能上确实会比AbsListView更出色。
但是虽然经过了优化,但是就像我前面说的RecyclerView并不是万能,对于一些非常复杂的item布局,一旦处理不好,RecyclerView所表现出来的性能也ListView是相差无几的,所以[使用RecyclerView代替ListView]并不仅仅应该体现在这里。
为什么要使用RV
在回答这个问题之前,让我们先思考另外一个问题,为什么在Google推出了RecyclerView并且经过了这么多个版本迭代之后,ListView没有被标明为deprecated呢?在ViewPager和HorizontalScrollView推出以后,Gallery就惨遭deprecated的命运,为毛ListView就没有呢?难道因为它是Google的亲儿子?
在我看来,RecyclerView虽然被标上了加强版AbsListView的标记,但是它们其实是两个不同的控件。AbsListView的子类们有着完善的功能,如果你的滑动组件只想简单的使用滑动显示这个功能,并且想轻松的使用divider,header,footer或者点击事件这些功能,那么使用AbsListView是完全没有问题的。
RecyclerView的重点应该放在[flexible]上,灵活是它[最大]的特点,由于AbsListView的功能完善,所以你想要定制它其实是很困难的,换句话说,AbsLisView已经强耦合了很多和[滑动,回收]无关的功能。这个时候RecyclerView的强大之处就显示出来了,LayoutManager,Adapter,ItemAnimator,ItemDecoration等等各司其职,这使得RecyclerView能够实现深度的定制化。系统提供的三种LayoutManager可以无缝衔接ListView和GridView,瀑布流的实现也变得无分简单。滑动删除和长按交换只需要添加几个类就可以实现。
除此之外,RecyclerView的动画配以局部刷新也是它比较出色的地方,在AbsListView时代,只有一个notifyDatasetChanged方法,想要做局部刷新需要自己去实现,动画更是难做,但是在RecyclerView中,有很多适配局部刷新的api,还有ItemAnimator这样的神器去支持动画,谁用谁知道。
至于点击事件,我在想Google将RecyclerView取名叫这个名字的原因就是想让这个组件只关注[Recycle],关于点击事件,在ViewHolder中添加是轻而易举的事,封装起来也不难,而且如果把这个逻辑写在组件内部,它的position和动画将会比较难处理,AbsListView里就花了比较多的精力去处理这一方面的逻辑。此外,在我们使用ListView的过程中,如果item中有可点击组件,例如button,那么点击事件的冲突也是一个让开发者很烦恼的事情,但是RecyclerView的好处就是把点击事件的控制权完全的交给开发者,避免了这样的痛苦。
最后,RecyclerView天生支持嵌套滑动,可以很好的配合NestedScrollView或者CoordinatorLayout,而AbsListView则是需要在一定的版本上才支持这个机制,这也算是RV的一个优势吧。