ListView缓存机制小结
文章目录
- ListView缓存机制小结
- 前言
- 概述
- layout过程
- 第一次Layout
- 第二次Layout
- 滑动事件
- 参考资料
ListView缓存机制小结
前言
由于本人水平有限,如果文章中出现一些谬误,还请各位大佬指正。
概述
ListView的缓存通过父类AbsListView中的内部类RecycleBin
实现,这个类中有两级缓存:ActiveViews
和ScrapViews
- ActiveViews用来缓存滑动后还留在屏幕内的itemview,供layout过程使用
- ScrapViews用来缓存滑出ListView的itemview,供onTouchEvent方法使用
RecycleBin
可以看到,mScrapViews缓存是一个ArrayList类型的数组,数组的长度代表itemview的类型个数,每个索引的ArrayList的长度表示缓存的滑出屏幕的同类型itemview的个数。
class RecycleBin {private View[] mActiveViews = new View[0]; //一级缓存,用来缓存ListView第一次layout过程的itemview,在第二次layout过程会被降级到mScrapViews中private ArrayList<View>[] mScrapViews; //二级缓存,缓存已经滑出ListView的itemview,可以被adapter作为convertview进行重用
}
mScrapViews的初始化
//AbsListView
public void setViewTypeCount(int viewTypeCount) {if (viewTypeCount < 1) {throw new IllegalArgumentException("Can't have a viewTypeCount < 1");}ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount]; //这里的viewTypeCount就是我们通过继承BaseAdapter重写其中getViewTypeCount方法指定的for (int i = 0; i < viewTypeCount; i++) {scrapViews[i] = new ArrayList<View>();}mViewTypeCount = viewTypeCount;mCurrentScrap = scrapViews[0]; //mScrapViews = scrapViews; //将上面创建的scrapViews赋给mScrapViews
}
mScrapViews的缓存大小就是getViewTypeCount方法返回值指定的大小。
在加载多子布局的情况就需要重写ListView的getItemViewType和getViewTypeCount方法;
另外,无论setViewTypeCount()/getViewTypeCount()值为几,RecycleBin都只有一个,而不会单独另起一个。唯一区别的是,当值为1时,即只有一种viewtype时,RecycleBin使用的是ArrayList<View> mCurrentScrap
来回收和存储滚出屏幕的views,当大于1时,RecycleBin用的是ArrayList<View>[] mScrapViews
,
layout过程
由于父ViewGroup的原因可能导致ListView layout多次。
ListView主要看onLayout方法,因为ListView的大小就是我们指定的大小,所以onMeasure方法就没什么看的了;又因为ListView的绘制都是交给子项来完成的,所以onDraw方法也没有了解的意义了。
第一次Layout
第一次Layout,这时还没有item子项的缓存,即RecycleBin中的mActiveViews数组大小为0,mScrapViews还未初始化,所以会通过Adapter的getView方法来获取子项的布局。
调用链
onLayout()->
layoutChildren()->
fillFromTop()->
fillDown()-> //在一个while循环中调用makeAndAddView方法获取子布局的view,直到占满整个屏幕
makeAndAddView()->
obtainView()-> mAdapter.getView()->
setupChild() //将getView方法返回的view添加到ListView中
makeAndAddView()
这个方法很重要,它体现了ListView缓存的特点:
- 先从RecycleBin的ActiveView获取缓存
- 再从RecycleBin的ScrapView获取缓存
- 最后考虑从Adapter的getView方法加载子布局
因为getView方法中是通过inflater的inflate方法来加载子布局的,这样获取效率较低,所以ListView会优先从两个缓存中获取。
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {if (!mDataChanged) {//这里通过getActiveView方法从RecycleBin的ActiveView中获取final View activeView = mRecycler.getActiveView中获取(position);if (activeView != null) { //第一次还没有缓存,所以这里为nullsetupChild(activeView, position, y, flow, childrenLeft, selected, true);return activeView;}}//从scrapview缓存获取或加载一个新的itemview到此位置final View child = obtainView(position, mIsScrap);setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);return child;
}
obtainView()
View obtainView(int position, boolean[] isScrap) {isScrap[0] = false;View scrapView;//再从RecycleBin的scrapview中获取屏幕外item的缓存,因为第一次layout,所以没有缓存scrapView = mRecycler.getScrapView(position);View child;if (scrapView != null) { //这里为nullchild = mAdapter.getView(position, scrapView, this);if (child != scrapView) {mRecycler.addScrapView(scrapView);if (mCacheColorHint != 0) {child.setDrawingCacheBackgroundColor(mCacheColorHint);}} else {isScrap[0] = true;dispatchFinishTemporaryDetach(child);}} else {child = mAdapter.getView(position, null, this); //通过关联的Adapter的getView方法获取item的viewif (mCacheColorHint != 0) {child.setDrawingCacheBackgroundColor(mCacheColorHint);}}return child;
}
第二次Layout
第二次Layout的流程和第一次差不多。
onLayout()->
layoutChildren()-> //会将第一次layout添加到ListView中的view全部添加到RecycleBin.mActiveViews中,然后通过detachAllViewsFromParent()将所有的itemview从ViewGroup中先移除,避免后面重复添加。
fillSpecific()->
makeAndAddView()->
setupChild() //通过attachViewToParent()将一个之前detach的itemView重新attach到ViewGroup上
这次makeAndAddView方法中进入了不同的分支:
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {if (!mDataChanged) {//这里通过getActiveView方法从RecycleBin的ActiveView中获取final View activeView = mRecycler.getActiveView(position);if (activeView != null) { //前面将所有itemview加入到了RecycleBin的mActiveView中,所以这里不为nullsetupChild(activeView, position, y, flow, childrenLeft, selected, true); //其中会通过attachViewToParent()将一个之前detach的itemView重新attach到ViewGroup上return activeView;}}final View child = obtainView(position, mIsScrap);setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);return child;
}
滑动事件
滑动时mActiveViews缓存了滑动后还在屏幕内的itemview,如上面layout过程所分析。下面onTouchEvent方法只会使用mScrapViews屏幕外itemview的缓存。
调用链
onTouchEvent()->
trackMotionScroll()-> //如果该子View已经移出屏幕了,就会调用RecycleBin.addScrapView()将这个View加入到mScrapViews缓存当中,并将count计数器加1,计数器用于记录有多少个子View被移出了屏幕。还会调用detachViewsFromParent()将移出屏幕的itemView全部detach掉
fillGap()->
fillUp()/fillDown()->
makeAndAddView()->
obtainView()
因为在第二次layout过程中通过getActiveView获取过ActiveView中的itemview,所以在makeAndAddView()中就获取不到它们了,这个原因在 getActiveView() 中:
View getActiveView(int position) {int index = position - mFirstActivePosition;final View[] activeViews = mActiveViews;if (index >=0 && index < activeViews.length) {final View match = activeViews[index];activeViews[index] = null; //可以看到,这里会将找到的itemview缓存置空return match;}return null;
}
所以下面会到obtainView获取屏幕外item的缓存或从Adapter的getView方法中加载。
View obtainView(int position, boolean[] isScrap) {isScrap[0] = false;View scrapView;scrapView = mRecycler.getScrapView(position);View child;if (scrapView != null) { //addScrapView()方法将移出屏幕的View加入到了mScrapViews缓存当中,所以这里不为nullchild = mAdapter.getView(position, scrapView, this); //可以看到,第二个参数为获取到的scrapView,这就是我们熟悉的View类型的convertview!if (child != scrapView) {mRecycler.addScrapView(scrapView);if (mCacheColorHint != 0) {child.setDrawingCacheBackgroundColor(mCacheColorHint);}} else {isScrap[0] = true;dispatchFinishTemporaryDetach(child);}} else {child = mAdapter.getView(position, null, this);if (mCacheColorHint != 0) {child.setDrawingCacheBackgroundColor(mCacheColorHint);}}return child;
}
上面obtainView方法中 child = mAdapter.getView(position, scrapView, this); 这行代码就解释了我们为什么要在Adapter的getView方法对第二个convertview做判断了,这样可以复用相同类型的itemview,避免通过inflate方法加载以提高效率。
顺便看看上面 mRecycler.getScrapView(position) 怎样获取ScrapView缓存:
View getScrapView(int position) {final int whichScrap = mAdapter.getItemViewType(position); //通过Adapter.getItemViewType方法获取itemview的类型,它同时是该类型ArrayList在mScrapViews这个ArrayList数组中的索引if (whichScrap < 0) {return null;}if (mViewTypeCount == 1) { //只有一种itemview的类型就在mCurrentScrap中获取缓冲return retrieveFromScrap(mCurrentScrap, position);} else if (whichScrap < mScrapViews.length) { //否则就在该类型的ArrayList中获取缓存return retrieveFromScrap(mScrapViews[whichScrap], position);}return null;
}private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {final int size = scrapViews.size();if (size > 0) {//从后往前遍历是为了找到最近使用的itemview缓存for (int i = size - 1; i >= 0; i--) {final View view = scrapViews.get(i);final AbsListView.LayoutParams params =(AbsListView.LayoutParams) view.getLayoutParams();if (mAdapterHasStableIds) {final long id = mAdapter.getItemId(position);if (id == params.itemId) {return scrapViews.remove(i);}} else if (params.scrappedFromPosition == position) {final View scrap = scrapViews.remove(i);clearScrapForRebind(scrap);return scrap;}}final View scrap = scrapViews.remove(size - 1);clearScrapForRebind(scrap);return scrap;} else {return null;}
}
参考资料
Android ListView工作原理完全解析,带你从源码的角度彻底理解
ListView缓存机制小结相关推荐
- 解决ListView 缓存机制带来的显示不正常问题
ListView加载数据原理:系统绘制ListView时,首先会用getCount()函数得到要绘制的这个列表的长度,然后开始逐行绘制.然后调用getView()函数,在这个函数里面首先获得一个Vie ...
- ListView缓存机制踩过的坑
ListView,GrildView使用时候经常会用到缓存机制,随意一搜,例子成千上万,但是讲解都是很一致,跟自己踩的坑很少有人讲解到. 测试 需求: GrildView 分三列显示,默认背景为白色, ...
- Android笔记(二十五) ListView的缓存机制与BaseAdapter
之前接触了ListView和Adapter,Adapter将数据源和View连接起来,实际应用中,我们要显示的数据往往有很多,而屏幕只有那么大,系统只能屏幕所能显示的内容,当我们滑动屏幕,会将旧的内容 ...
- 【腾讯Bugly干货分享】Android ListView与RecyclerView对比浅析--缓存机制
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/5811d... 作者:黄宁源 一,背景 RecyclerView是谷歌官方出的一 ...
- ListView和GridView的缓存机制及measure过程
目录 前言 1.View的Transient状态 2.RecycleBin 3.obtainView 4.getView的调用 5.GridView的onMeasure 6.ListView的onMe ...
- Android学习——ListView的缓存机制
在使用ListView的时候,需要加载适配器和数据源,这篇文章主要介绍一下ListView的使用以及利用ListView的缓存机制来减少系统的初始化时间. ListView的使用 ListView和V ...
- delphi listview 添加数据 慢_ListView 的缓存机制
一.前言 ListView 作为一个 Android 5.x 之前的一个用于显示数据列表的控件,或许在今天都已经被 RecyclerView 完全替代,但是其中的缓存机制仍然值得我们去了解,对后面学习 ...
- android listview 缓存,探究Android ListView 的缓存机制
概述 ListView 是继承AbListView,AbListView是所有列表类控件的基类. ListView的数据加载 在ListView数据加载中最关键的一个函数就是makeAndAddVie ...
- Picasso之图片缓存机制二ListView篇
前面已经个大家介绍Picasso图片缓存机制,大家不熟悉请看上一篇文章http://blog.csdn.net/qq_15950325/article/details/52809380,其实Picas ...
最新文章
- ubuntu报错解决:The following packages have unmet dependencies:
- 修改服务器名后,sql server的配置处理
- tensorflow lstm 预测_图卷积神经网络GCN与递归结构RNN相结合的时间序列预测
- 三分钟玩转jQuery.noConflict()
- YOLOv5自定义数据集训练
- 自动摘要php,修改DEDECMS文章自动摘要长度或者取掉文章摘要
- ECshop安装及报错解决方案总结
- python webdriver save_Python + Selenium +Chrome 批量下载网页代码修改【新手必学】
- Linux Vim基本操作(文件的打开和编辑)完全攻略(有图有真相)
- Debian/Ubuntu系统下,apt-get update 、apt-get upgrade 、apt-get dist-upgrade 命令之间的区别
- 基于 XGBoost 对 Santander 银行用户购买行为进行预测
- arcgis已试图对空几何执行该操作_ArcGIS中坐标转换和投影变换
- 颜色恒常性评价指标——角度误差【弧度角度】避坑
- 多层高速PCB设计学习笔记(五)四层板实战(下)之阻抗控制计算(SI9000)
- oracle入门学习
- 遇到一个macOS下malware中毒很深的网友,安装的恶意软件MyCouponsmart、SearchMine.AnySearch、Advanced Mac Cleaner等真多!
- bandizip修改压缩文件内容_如何修改压缩文件的编码?
- python小程序之猜水果游戏
- 教你如何降低诺基亚E71的辐射
- rasa填槽slot