ListView相比RecyclerView,有一些优点

  • addHeaderView(), addFooterView()添加头视图和尾视图。
  • 通过”android:divider”设置自定义分割线。
  • setOnItemClickListener()setOnItemLongClickListener()设置点击事件和长按事件。

这些功能在RecyclerView中都没有直接的接口,要自己实现(虽然实现起来很简单),因此如果只是实现简单的显示功能,ListView无疑更简单。

RecyclerView相比ListView,有一些明显的优点

  • 默认实现View复用,不需要类似if(convertView == null)的实现,而且回收机制更加完善。
  • 默认支持局部刷新
  • 容易实现添加item、删除item动画效果。
  • 容易实现拖拽、侧滑删除等功能。

RecyclerView是一个插件式的实现,对各个功能进行解耦,从而扩展性比较好。

局部刷新

ListView实现局部刷新

我们都知道ListView通过adapter.notifyDataSetChanged()实现ListView的更新,这种更新方法的缺点是全局更新,即对每个Item View都进行重绘。但事实上很多时候,我们只是更新了其中一个Item的数据,其他Item其实可以不需要重绘。

这里给出ListView实现局部更新的方法:

public void updateItemView(ListView listview, int position, Data data){int firstPos = listview.getFirstVisiblePosition();int lastPos = listview.getLastVisiblePosition();if(position >= firstPos && position <= lastPos){  //可见才更新,不可见则在getView()时更新//listview.getChildAt(i)获得的是当前可见的第i个item的viewView view = listview.getChildAt(position - firstPos);VH vh = (VH)view.getTag();vh.text.setText(data.text);}
}

可以看出,我们通过ListView的getChildAt()来获得需要更新的View,然后通过getTag()获得ViewHolder,从而实现更新。

RecyclerView实现局部刷新

RecyclerView提供了notifyItemInserted(),notifyItemRemoved(),notifyItemChanged()等API更新单个或某个范围的Item视图。

缓存机制

ListView与RecyclerView缓存机制原理大致相似,如下图所示:

image

过程中,离屏的ItemView即被回收至缓存,入屏的ItemView则会优先从缓存中获取,只是ListView与RecyclerView的实现细节有差异.(这只是缓存使用的其中一个场景,还有如刷新等)

缓存机制对比

1. 层级不同:

RecyclerView比ListView多两级缓存,支持多个离ItemView缓存,支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。

具体来说:
ListView(两级缓存):

image

RecyclerView(四级缓存):

image

ListView和RecyclerView缓存机制基本一致:

1). mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView;

2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用.

3). RecyclerView的优势在于

  • mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;
  • mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势。

客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。

2. 缓存不同:

1). RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为:
View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);
2). ListView缓存View。

缓存不同,二者在缓存的使用上也略有差别,具体来说:
ListView获取缓存的流程:

image

RecyclerView获取缓存的流程:

image

1). RecyclerView中mCacheViews(屏幕外)获取缓存时,是通过匹配pos获取目标位置的缓存,这样做的好处是,当数据源数据不变的情况下,无须重新bindView:

而同样是离屏缓存,ListView从mScrapViews根据pos获取相应的缓存,但是并没有直接使用,而是重新getView(即必定会重新bindView),相关代码如下:

//AbsListView源码:line2345
//通过匹配pos从mScrapView中获取缓存
final View scrapView = mRecycler.getScrapView(position);
//无论是否成功都直接调用getView,导致必定会调用createView
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {if (child != scrapView) {mRecycler.addScrapView(scrapView, position);} else {...}
}

2). ListView中通过pos获取的是view,即pos–>view;
RecyclerView中通过pos获取的是viewholder,即pos –> (view,viewHolder,flag);
从流程图中可以看出,标志flag的作用是判断view是否需要重新bindView,这也是RecyclerView实现局部刷新的一个核心.

局部刷新

由上文可知,RecyclerView的缓存机制确实更加完善,但还不算质的变化,RecyclerView更大的亮点在于提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView.

(RecyclerView和ListView添加,移除Item效果对比)

结合RecyclerView的缓存机制,看看局部刷新是如何实现的:
以RecyclerView中notifyItemRemoved(1)为例,最终会调用requestLayout(),使整个RecyclerView重新绘制,过程为:
onMeasure()–>onLayout()–>onDraw()

其中,onLayout()为重点,分为三步:

  1. dispathLayoutStep1():记录RecyclerView刷新前列表项ItemView的各种信息,如Top,Left,Bottom,Right,用于动画的相关计算;
  2. dispathLayoutStep2():真正测量布局大小,位置,核心函数为layoutChildren();
  3. dispathLayoutStep3():计算布局前后各个ItemView的状态,如Remove,Add,Move,Update等,如有必要执行相应的动画.

其中,layoutChildren()流程图:

image

image

当调用notifyItemRemoved时,会对屏幕内ItemView做预处理,修改ItemView相应的pos以及flag(流程图中红色部分):

image

当调用fill()中RecyclerView.getViewForPosition(pos)时,RecyclerView通过对pos和flag的预处理,使得bindview只调用一次.

需要指出,ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是”一锅端”,将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView。

回收机制源码分析

ListView回收机制

ListView为了保证Item View的复用,实现了一套回收机制,该回收机制的实现类是RecycleBin,他实现了两级缓存:

  • View[] mActiveViews: 缓存屏幕上的View,在该缓存里的View不需要调用getView()
  • ArrayList<View>[] mScrapViews;: 每个Item Type对应一个列表作为回收站,缓存由于滚动而消失的View,此处的View如果被复用,会以参数的形式传给getView()

接下来我们通过源码分析ListView是如何与RecycleBin交互的。其实ListView和RecyclerView的layout过程大同小异,ListView的布局函数是layoutChildren(),实现如下:

void layoutChildren(){//1. 如果数据被改变了,则将所有Item View回收至scrapView  //(而RecyclerView会根据情况放入Scrap Heap或RecyclePool);否则回收至mActiveViewsif (dataChanged) {for (int i = 0; i < childCount; i++) {recycleBin.addScrapView(getChildAt(i), firstPosition+i);}} else {recycleBin.fillActiveViews(childCount, firstPosition);}//2. 填充switch(){case LAYOUT_XXX:fillXxx();break;case LAYOUT_XXX:fillXxx();break;}//3. 回收多余的activeViewmRecycler.scrapActiveViews();
}

其中fillXxx()实现了对Item View进行填充,该方法内部调用了makeAndAddView(),实现如下:

View makeAndAddView(){if (!mDataChanged) {child = mRecycler.getActiveView(position);if (child != null) {return child;}}child = obtainView(position, mIsScrap);return child;
}

其中,getActiveView()是从mActiveViews中获取合适的View,如果获取到了,则直接返回,而不调用obtainView(),这也印证了如果从mActiveViews获取到了可复用的View,则不需要调用getView()

obtainView()是从mScrapViews中获取合适的View,然后以参数形式传给了getView(),实现如下:

View obtainView(int position){final View scrapView = mRecycler.getScrapView(position);  //从RecycleBin中获取复用的Viewfinal View child = mAdapter.getView(position, scrapView, this);
}

接下去我们介绍getScrapView(position)的实现,该方法通过position得到Item Type,然后根据Item Type从mScrapViews获取可复用的View,如果获取不到,则返回null,具体实现如下:

class RecycleBin{private View[] mActiveViews;    //存储屏幕上的Viewprivate ArrayList<View>[] mScrapViews;  //每个item type对应一个ArrayListprivate int mViewTypeCount;            //item type的个数private ArrayList<View> mCurrentScrap;  //mScrapViews[0]View getScrapView(int position) {final int whichScrap = mAdapter.getItemViewType(position);if (whichScrap < 0) {return null;}if (mViewTypeCount == 1) {return retrieveFromScrap(mCurrentScrap, position);} else if (whichScrap < mScrapViews.length) {return retrieveFromScrap(mScrapViews[whichScrap], position);}return null;}private View retrieveFromScrap(ArrayList<View> scrapViews, int position){int size = scrapViews.size();if(size > 0){return scrapView.remove(scrapViews.size() - 1);  //从回收列表中取出最后一个元素复用} else{return null;}}
}

RecyclerView回收机制

RecyclerView和ListView的回收机制非常相似,但是ListView是以View作为单位进行回收RecyclerView是以ViewHolder作为单位进行回收
Recycler是RecyclerView回收机制的实现类,他实现了四级缓存:

  • mAttachedScrap: 缓存在屏幕上的ViewHolder。
  • mCachedViews: 缓存屏幕外的ViewHolder,默认为2个。ListView对于屏幕外的缓存都会调用getView()
  • mViewCacheExtensions: 需要用户定制,默认不实现。
  • mRecyclerPool: 缓存池,多个RecyclerView共用。

在上文Layout Manager中已经介绍了RecyclerView的layout过程,但是一笔带过了getViewForPosition(),因此此处介绍该方法的实现。

View getViewForPosition(int position, boolean dryRun){if(holder == null){//从mAttachedScrap,mCachedViews获取ViewHolderholder = getScrapViewForPosition(position,INVALID,dryRun); //此处获得的View不需要bind}final int type = mAdapter.getItemViewType(offsetPosition);if (mAdapter.hasStableIds()) { //默认为falseholder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);}if(holder == null && mViewCacheExtension != null){final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type); //从if(view != null){holder = getChildViewHolder(view);}}if(holder == null){holder = getRecycledViewPool().getRecycledView(type);}if(holder == null){  //没有缓存,则创建holder = mAdapter.createViewHolder(RecyclerView.this, type); //调用onCreateViewHolder()}if(!holder.isBound() || holder.needsUpdate() || holder.isInvalid()){mAdapter.bindViewHolder(holder, offsetPosition);}return holder.itemView;
}

从上述实现可以看出,依次从mAttachedScrap, mCachedViews, mViewCacheExtension, mRecyclerPool寻找可复用的ViewHolder,如果是从mAttachedScrap或mCachedViews中获取的ViewHolder,则不会调用onBindViewHolder(),mAttachedScrap和mCachedViews也就是我们所说的Scrap Heap;而如果从mViewCacheExtension或mRecyclerPool中获取的ViewHolder,则会调用onBindViewHolder()

RecyclerView局部刷新的实现原理也是基于RecyclerView的回收机制,即能直接复用的ViewHolder就不调用onBindViewHolder()

转载:https://www.jianshu.com/p/4f9591291365

ListView和RecycleView的性能比对相关推荐

  1. listview 和RecycleView区别

    RecyclerView和ListView之间的区别 在我们要搞清楚一个问题之前,首先要搞清楚为什么,是什么, 最后才能得出结论 一:为什么会出现recycleView RecyclerView并不会 ...

  2. Android截屏截图方法汇总(Activity、View、ScrollView、ListView、RecycleView、WebView截屏截图)

    Android截屏 Android截屏的原理:获取具体需要截屏的区域的Bitmap,然后绘制在画布上,保存为图片后进行分享或者其它用途 一.Activity截屏 1.截Activity界面(包含空白的 ...

  3. DataBinding使用指南(一)DataBinding基本使用,双向绑定,ListView RecycleView使用

    databing使用指南 简介 简单使用 双向绑定 ListView.RecycleView中的使用 . ListView ListView 中数据的简单展示 数据源改变后数据更新方式 . Recyc ...

  4. 【Android】RecycleView简单仿漫画APP图片相关样式

    真的真的想不到起什么标题好了,这次的内容真的是太简单了,没有什么挑战性,一天以内就完成了.最近在学kotlin,也会有一份kotlin的代码,鉴于很多人都是从java开始进行android开发的,ko ...

  5. 【转载】RecycleView使用详解

    原文链接:https://blog.csdn.net/u012721519/article/details/54692366 一.RecycleView简要介绍 RecycleView是support ...

  6. RecycleView和ViewPager冲突解决与原理

    1.概述 在实际开发中,我们经常遇到需要在ListView或RecycleView头部添加ViewPager实现Banner轮播效果,并需要添加下拉刷新,上拉加载功能. 但,横向滑动ViewPager ...

  7. php swiper 下拉刷新,SwipeRefreshLayout的使用(下拉刷新)

    在布局中导入 SwipeRefreshLayout在V4包下,作为一个布局,可以在内部嵌套如listview,recycleView等,这里作为例子嵌套了一个listview android:id=& ...

  8. 2016年Android实习岗位 腾讯二面+阿里一面

    转自:http://www.nowcoder.com/discuss/3906?pos=20&type=0&order=0 腾讯 一面 1.看着简历中的技能Java/c/Android ...

  9. Flutter学习之认知基础组件

    一.前言 前一天,学习了Dart语法,对Dart的语法和特性有了更深一步的了解.今天,来学习Flutter的基础控件,身为Android开发者都知道,一开始入坑Android就要熟悉学习其控件,如:T ...

最新文章

  1. Blender全流程制作真实感3D产品学习教程
  2. 手写html5游戏,HTML5 手写输入法
  3. mysql中groupby会用到索引吗_mysql order by 与索引的使用
  4. Caffe训练ImageNet
  5. linux下confstr与uname函数_获取C库与内核信息
  6. python3 isinstance用法_对python中assert、isinstance的用法详解
  7. js向jsp传中文出现乱码的解决方法
  8. C语言试题四十七之程序定义了N×M的二维数组,并在主函数中自动赋值。请编写函数function(int a[N][M], int m),该函数的功能是:将数组右上半三角元素中的值乘以m。
  9. Python学习 - 之super函数
  10. Virtual Treeview 5 0 0的安装以及入门
  11. Spring框架XML配置文件使用外部Bean属性注入
  12. opencv cv.waitKey(60) 0xff 含义和作用
  13. 计算机组策略没有权限,运行组策略或程序时提示没有权限
  14. oracle 获取日期的毫秒_Oracle date timestamp 毫秒 - 时间函数总结(转)
  15. 正则html在线测试,正则表达式在线测试工具
  16. 【产品人卫朋】2022年产品人必备的13个设计类网站(1.0版)
  17. Deep Learning 最优化方法
  18. Android录制声音文件(音频),并播放
  19. Instance Tunnel 使用
  20. 【程序】STM32使用SPI接口读取93C46存储器上的数据(非软件模拟SPI时序)

热门文章

  1. 【iCore3双核心板】发布 iCore3 应用开发平台硬件原理图
  2. 各数据库要使用保留字的处理办法
  3. nodejs 复制、移动文件
  4. java中各种时间格式的转化
  5. 探求Floyd算法的动态规划本质
  6. hdu 3093 动态规划
  7. 关于 JQuery 的克隆
  8. go 依赖注入 哪个好_go与java的依赖注入实现的一些差异
  9. python封装sql脚本_flask-sqlalchemy如何使用原生的sql语句然后封装?
  10. oracle number对应java什么类型_JVM系列之数据类型