Recyclerview相较于listview更灵活,可以实现多种常用的页面。垂直的列表,横向的列表,瀑布流,grid页面等。甚至我们能自定义一个layoutmanager实现垂直横向都能scroll的grid。但是没有如listview一样的支持header的api,不过没有关系,我们自己可以使用viewtype实现。
listview中的getview方法中可以使用convertview来实现view的重用,或者用其它的优化策略---例如,使用settag,gettag来减少findviewbyid的调用从而提高效率,使得列表更顺滑。这里我们对listview不做更多的讨论,主要分析recyclerview中对viewholder的缓存策略。
我们查看recyclerview的源码,找到
getViewForPosition(int position, boolean dryRun)
方法,顺着代码往下看,我们可以了解到很多recyclerview的一些内部机制。例如,viewhodler recycling,hidden views,predictive animations,stable ids。
当layoutmanager请求recyclerview根据position给它一个合适的view的时候,recyclerview会做以下几步工作:
- 查找 changed scrap
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
- 查找 attached scrap
// Try first for an exact, non-invalid match from scrap.
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
- 查找 没有被移除的 hidden views
View view = mChildHelper.findHiddenNonRemovedView(position);
- 查找 view cache
- 如果adapter有stable ids,根据提供的id查找attached scrap和view cache
- 查找 ViewCacheExtension
- 查找RecycledViewPool
代码就不一一列出了。如果上述步骤都不能得到一个view,那么就会调用adapter中的onCreateViewHolder方法,然后调用onBindViewHolder方法绑定数据,然后返回给layoutmanager。
看了源码,可能会有更多的疑惑,为什么除了一个pool之外,还需要其它各种cache?下面我们就一个一个来分析
RecycledViewPool
支持多种viewtype,每个viewtype默认的容量是5,但是我们可以自定义,这样就提供了一种灵活性。
recyclerView.getRecycledViewPool()
.setMaxRecycledViews(SOME_VIEW_TYPE, POOL_CAPACITY);
getRecyclerView putRecycledView clear等方法都是public的,我们可以直接操作pool中的view,但是不提倡这样做,应该总是使用onCreateViewHolder方法。
另外一个特性就是多个recyclerview可以共用一个recycledViewPool。
pool的策略
- recyclerview scrolling的时候,childview已经超出其边界(这里的边界跟view cache有关,并不是严格意义的边界,后面会解释)
- 数据集变化,引起某些view不再展示,那么这些view在动画完成后就会被加入pool
- view cache中的view更新或移除后会被加入pool
- 在查找viewholder的过程中,我们根据position在scrap或者cache中找到一个viewHolder,但是最后因为viewtype或者id错误而发现不合适
- layoutmanager在pre-layout时加入了一个view,但是在post-layout的时候并没有加入它
pre-layout, post-layout and predictive animations
思考下列情形,我们有三个item a,b,c。a和b正好显示并占满整个recyclerview的屏幕空间。当我们删除b的时候,c就会被带到recyclerview的屏幕空间(可见)。
我们就会看到c从底部平滑的滑动到屏幕上。但是这是怎么发生的呢?我们知道最终状态下c的位置,但是我们并不知道开始状态下c的位置。因为recyclerview的机制并不会每次都提前layout多余的item,这样就会浪费资源。
google提供的解决办法如下。当adapter notifychange的时候,recyclerview向layoutmanager请求俩次layout,而不是一次。第一次-pre-layout,它会根据adapter的初始状态layout items,在这个过程中就会layout额外的view。在上面的例子中,我们知道b会被删除,那么我们就会把c也进行layout操作,虽然c并没有在屏幕上,但是它即将展示到屏幕上。 第二次 -- post-layout,adapter执行完notifychange操作后的状态就是正常状态,只要根据adapter正常的layout view即可。
现在我们比较c在pre-layout和post-layout中的位置,我们就能合适的执行对它的动画操作。
我们考虑另外一种情形---b不是被删除,而是数据有改变。
layoutmanager照样会在执行pre-layout操作时layout c。因为b的改变可能会使得b的高度变小,从而使得c在最终状态中部分可见,谁知道会不会发生这种情况呢。所以最好的处理方式是在pre-layout layout它(额外量),在执行post-layout后,我们可能发现最后b的高度并没有变化,可能b中只是更改某个texview的text,那么c在这种情况下就不需要layout,那么c就会被放入到pool中,这种情形就是我们上面说的pool策略最后一点。
View Cache
RecyclerView.Recycler 中mCachedViews
- 如果viewholder在所有的cache中还有pool中都没有查找到,那么就会调用adapter创建并绑定数据
- 如果viewholder在pool中找到,那么它会重新绑定数据
- 如果viewholder在cache中找到,那么什么额外的操作都不会有,直接展示在界面上。
Filling pool and cache
上面的图只是使用默认的容量。填充的时序示意图
先填充cache,cache满了之后才会填充pool
Pool and Cache in Action
考虑scrolling的情形如果该篇文章有任何问题,希望您能在百忙之中指出并联系我nanhuaqq@gmail.com