RecyclerView
RecyclerView是什么?
RecyclerView是Android常用的列表展示控件- 使用方面,
RecyclerView提供的适配器模式使得我们只需要关注如何将数据转换为ViewHolder即可 - 性能方面,
RecyclerView具有高效的回收复用机制,以提高列表的性能
RecyclerView 的基本使用
参考# 揭开RecyclerView的神秘面纱(一):RecyclerView的基本使用:
这里加入个人对 RecyclerView.Adapter抽象类中几个抽象方法的理解。
public static abstract class Adapter<VH extends ViewHolder> {
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); // 1
public abstract void onBindViewHolder(VH holder, int position); // 2
public abstract int getItemCount(); // 3
------- 分割线
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private List<String> mDataSet;
//构造器,接受数据集
public MyAdapter(List<String> data){
mDataSet = data;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//加载布局文件
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.itemlayout,parent,false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
//将数据填充到具体的view中
holder.mTextView.setText(mDataSet.get(position));
}
@Override
public int getItemCount() {
return mDataSet.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
public TextView mTextView;
public ViewHolder(View itemView) {
super(itemView);
//由于itemView是item的布局文件,我们需要的是里面的textView,因此利用itemView.findViewById获
//取里面的textView实例,后面通过onBindViewHolder方法能直接填充数据到每一个textView了
mTextView = (TextView) itemView.findViewById(R.id.num);
}
}
}
}
个人理解
首先理解 ViewHolder,存储 View 实例对象的一个容器。
1: 返回值为 ViewHolder,屏幕能展示几个子项,就会调用几次onCreateViewHolder方法,创建几个VH 实例。同时内部应该有一个类似于数组的结构,第 0 号位置对应position为 0 的子项,数组的值为 ViewHolder 的地址。
2:当能显示在屏幕的子项,会调用几次这个方法。传入 1 里面对应 position 的 ViewHolder,然后将数据填进去。
3:返回数据的数量。
RecyclerView 的绘制流程
参考:# RecyclerView 源码分析(一) - RecyclerView的三大流程 juejin.cn/post/698071…
面试官:讲一讲RecyclerView的原理吧:
RecyclerView作为一个开发中常使用的列表控件,他提供的适配者模式使得开发者只需要关心如何将数据转换为Adapter即可,并且其具有高效的缓存复用机制提高了列表的性能。
在他的使用上有几个很重要的类,Adapter负责提供数据,也包括创建ViewHolder以及绑定数据、LayoutManager负责ItemView的测量和布局、ItemDecoration负责每个ItemView的间隙。这种插拔式的设计让RecyclerView的变化多端。
RecyclerView与一个简单的控件(如Button)相比看起来很复杂,使用起来也很复杂,但总归是一个View,也一定会经历三大流程,measure、layout、draw。
-
measure:分为几种情况
- 判断到mlayout变量为空,这也是比如作为一个android初学者会犯的错误就是不给RecyclerView设置LayoutManager,这时候只会去处理RecyclerView的宽高便return了,没有后续操作,也就是不展示出来数据。
- 当LayoutManager开启了自动测量:1.调用mLayout.onMeasure。在这个方法中,如果MeasureSpec的模式为Exactly(如matchparent或者写定值)则测量方法结束。 2.如果宽高为wrapcontent,往下执行
dispatchLayoutStep1()和dispatchLayoutStep2(),就是遍历测量ItemView的大小从而确定RecyclerView的宽高,这种情况真正的测量操作都是在dispatchLayoutStep2()中完成。如果STATE == State.STEPSTART就会执行dispatchLayoutStep1,然后执行dispatchLayoutStep2。
-
layout:会直接调用dispatchLayout(),这个方法做的事就是,如果没在onMeasure阶段提前测量子ItemView,即RecyclerView宽高为
match_parent或者精确值时,调用dispatchLayoutStep1()和dispatchLayoutStep2()测量itemView宽高。- dispatchLayoutStep1():这一步与ItemAnimator有很大关系,也就是动画相关的。同时也会进行一些预布局。(不是很懂
- dispatchLayoutStep2():这个方法会真正测量和布局Children。1.调用Adapter.getItemCount 2.调用
LayoutManager.onLayoutChild,这个方法一般是LayoutManager的子类会实现,以LinearLayout为例。3.onLayoutChild中会执行detachAndScrapAttachedViews方法,会将已经附加上RecyclerView的ItemView移除掉,缓存Scrap中,即发生改变的ViewHolder缓存到mChangedScrap中,不发生改变的ViewHolder存放到mAttachedScrap中;剩下ViewHolder的会按照mCachedViews>RecycledViewPool的优先级缓存到mCachedViews或者RecycledViewPool中。 - 依旧是在onLayoutChild:执行完上面的回收相关的内容。就要开始执行复用相关的。
- 调用fill()方法进行布局填充。里面的核心是一个while循环,循环执行一个
layoutChunk方法。(根据锚点信息的不同可能会fill两次) - layoutChunk:1.调用layoutSate.next从缓存中获取当前position的View,缓存没有则创建。 2.调用addView方法将View加入RecyclerView 3.测量子View的尺寸和位置
在RecyclerView重新布局
onLayoutChildren()或者填充布局fill()的时候,会先把必要的item与屏幕分离或者移除,并做好标记,保存到list中,在重新布局时,再将ViewHolde拿出来重新一个个放到新的位置上去。(1)如果是RecyclerView不滚动情况下缓存(比如删除item),重新布局时,把屏幕上的ViewHolder与屏幕分离下来,存放到
Scrap中,即发生改变的ViewHolder缓存到mChangedScrap中,不发生改变的ViewHolder存放到mAttachedScrap中;剩下ViewHolder的会按照mCachedViews>RecycledViewPool的优先级缓存到mCachedViews或者RecycledViewPool中。(2)如果是RecyclerVIew滚动情况下缓存(比如滑动列表),在滑动时填充布局,先移除滑出屏幕的item,第一级缓存
mCachedViews优先缓存这些ViewHolder,但是mCachedViews最大容量为2,当mCachedViews满了以后,会利用先进先出原则,把旧的ViewHolder存放到RecycledViewPool中后移除掉,腾出空间,再将新的ViewHolder添加到mCachedViews中,最后剩下的ViewHolder都会缓存到终极回收池RecycledViewPool中,它是根据itemType来缓存不同类型的ArrayList<ViewHolder>,最大容量为5。
(1)当RecyclerView要拿一个复用的ViewHolder时,如果是预加载,则会先去
mChangedScrap中精准查找(分别根据position和id)对应的ViewHolder,如果有就返回;(2)如果没有就再去
mAttachedScrap和mCachedViews中精确查找(先position后id)是不是原来的ViewHolder,如果是说明ViewHolder是刚刚被移除的;(3)如果不是,则最终去
mRecyclerPool找,如果itemType类型匹配对应的ViewHolder,那么返回实例,让它重新绑定数据;(4)如果
mRecyclerPool也没有返回ViewHolder才会调用createViewHolder()重新去创建一个。这里需要注意:在
mChangedScrap、mAttachedScrap、mCachedViews中拿到的ViewHolder都是精准> 匹配,但是mChangedScrap的是发生了变化的,需要调用onBindViewHolder()重新绑定数据,>mAttachedScrap和mCachedViews没有发生变化,是直接使用的,不需要重新绑定数据,而mRecyclerPool中的ViewHolder的内容信息已经被抹除,需要重新绑定数据。所以在RecyclerView来回滚动时,mCachedViews缓存池的使用效率最高。
总的来说:
RecyclerView着重在两个场景缓存和回收的优化,一是:在数据更新时,使用Scrap进行局部更新,尽可能复用原来viewHolder,减少绑定数据的工作;二是:在滑动的时候,重复利用原来的ViewHolder,尽可能减少重复创建ViewHolder和绑定数据的工作。最终思想就是,能不创建就不创建,能不重新绑定就不重新绑定,尽可能减少重复不必要的工作。
- 最终调用ondraw去绘制
RecyclerView缓存复用机制
RecyclerView回收复用什么?
RecyclerView 回收复用的为 ViewHolder 对象
ViewHolder 主要保存:要展示的 View 、在 RecyclerView 中的位置信息、自身状态信息
RecyclerView 回收复用的核心思想是:对于 ViewHolder 来说,能不创建就不创建,能不重新绑定就不重新绑定,尽可能减少不必要的重复工作
ViewHolder被回收到哪?从哪获取复用的ViewHodler?
RecyclerView 的回收复用机制由内部类 Recycler 提供
以下是四级缓存:
-
mChangedScrap/mAttachedScrap (Scarp)
Scrap是RecyclerView最轻量的缓存,包括mAttachedScrap和mChangedScrap,它不参与列表滚动时的回收复用,作为重新布局时的临时缓存,它的作用是,缓存当界面重新布局前和界面重新布局后都出现的ViewHolder,这些ViewHolder是无效、未移除、未标记的。在这些无效、未移除、未标记的ViewHolder之中,mAttachedScrap负责保存其中没有改变的ViewHolder;剩下的由mChangedScrap负责保存。mAttachedScrap和mChangedScrap也只是分工合作保存不同ViewHolder而已。final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); ArrayList<ViewHolder> mChangedScrap = null;当子项中某一项被移除了,就会临时缓存到mATtachedScrap中,等到RecyclerView重新开始布局显示其子视图后,再遍历mAttachedScrap找到对应position的ViewHolder对象进行复用。
-
mCachedViews
mCachedViews主要用于存放已被移出屏幕、但有可能很快重新进入屏幕的列表项。其同样是以ArrayList的形式持有着每个列表项的ViewHolder对象,默认大小限制为2。
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); int mViewCacheMax = DEFAULT_CACHE_SIZE; static final int DEFAULT_CACHE_SIZE = 2;比如像朋友圈这种,我们经常会在快速滑动中确定是否有自己感兴趣的内容,当意识到刚才滑走的内容可能比较有趣时,我们往往就会将上一条内容重新滑回来查看。
在这种情况下,为了效率更高,用户的体验更好,我们肯定不希望又去回调创建viewHolder以及绑定数据的操作,所以缓存就起了大用处。
比如向上滑动两个item的高度时,第0号、1号的item会进入mCachedViews结构中,此时如果我们改变滑动方向,向下滑动,目的是想要第1号展示出来,那么Recycler类就会去缓存中找,先找mAttachedScrap(肯定是没有的),再去找mCachedViews,对比缓存中ViewHolder的position值与待复用的position值是否一致,如果一致直接使用并在缓存中移除。
-
mViewsCachedExtension
-
mViewCacheExtension主要用于提供额外的、可由开发人员自由控制的缓存层级,属于非常规使用的情况,因此这里暂不展开讲。
-
mRecyclerPool
由于mCachedViews的默认大小限制为2,由于mCachedViews默认的大小限制仅为2,因此,当滑出屏幕的列表项超过2个后,就会按照先进先出的顺序,依次将ViewHolder对象从mCachedViews移出,并按itemType放入RecycledViewPool中的不同ArrayList。
如果准备展示的item,在前面的三级缓存里找不到的话,就去mRecyclerPool中找,根据itemType找对应的ArrayList,重用里面的ViewHolder,但是需要重新onBindViewHolder。