delphi listview 添加数据 慢_ListView 的缓存机制

3ece1564f650d765e17a365d0951fac0.png

一.前言

ListView 作为一个 Android 5.x 之前的一个用于显示数据列表的控件,或许在今天都已经被 RecyclerView 完全替代,但是其中的缓存机制仍然值得我们去了解,对后面学习 RecyclerView 的缓存机制有很大的帮助。

下面将根据 ListView 的三个过程彻底理解其缓存机制 - OnLayout 过程,这个过程实践上有两次,而且两次是有区别的。 - 滑动一个 Item ,即最上面的一个 item 移除屏幕,屏幕下面出现后一个 item 。 - 滑动一个以上的item .

二.RecycleBin机制

ListView 的缓存实际上都是由 RecyclerBin 类完成的,这是 ListView 的父类 AbsListView 的一个内部类,它也是 GridView 的一个父类,说明 ListView 的缓存和 GridView 的缓存实际上有很多相似的地方。

class RecycleBin {// 第一个可见的 item 的下标private int mFirstActivePosition;// 表示屏幕上可见的 itemView  private View[] mActiveViews = new View[0];//表示废弃的 itemView ,即屏幕上被移除的 itemView //就会添加到这里, 注意这里是个 数组,//数组的每个元素都是 List ,因为 ListView 可能存在多个//类型的 item ,因此用不同的 List 进行存储。private ArrayList<View>[] mScrapViews;// 表示不同类型的 itemView 的数量private int mViewTypeCount;// 表示mScrapViews 数组中一个元素,默认是 第一个private ArrayList<View> mCurrentScrap;...//下面就是初始化的过程。ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];for (int i = 0; i < viewTypeCount; i++) {scrapViews[i] = new ArrayList<View>();}mViewTypeCount = viewTypeCount;mCurrentScrap = scrapViews[0];mScrapViews = scrapViews;

与上面对应的有四个方法,分为两类,

对于可见的 itemView 有两个操作:

  • fillActiveViews ,将屏幕上可见的 itemView 添加到 ActiveViews 数组。
void fillActiveViews(int childCount, int firstActivePosition) {...final View[] activeViews = mActiveViews;for (int i = 0; i < childCount; i++) {View child = getChildAt(i);AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {activeViews[i] = child;lp.scrappedFromPosition = firstActivePosition + i;}}}
  • getActiveView(), 根据位置取出 ActiveViews 数组 中的 itemView ,并将最对应的数组元素置为 null。
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;return match;}return null;}

对于移除屏幕的 itemView 也有两个操作:

  • addScrapView() ,将移除 的 itemView 添加到 mScrapViews/mCurrentScrap 中。
void addScrapView(View scrap, int position) {...if (mViewTypeCount == 1) {mCurrentScrap.add(scrap);} else {mScrapViews[viewType].add(scrap);}...}
  • getScrapView(),用于从废弃缓存中取出一个 ItemView,如果只有一个类型就直接从 mCurrentScrap 当中获取尾部的一个 view 进行返回,同样取出后就直接移除元素。
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;}

三.OnLayout 过程

一个 View 的绘制的时候至少会进行 2 次 onMeasure、onLayout,原因可参考这篇文章 View为什么会至少进行2次onMeasure、onLayout,那么对于 ListView 这两次过程由于缓存机制的存在,就显得不一样。

(1)第一次 OnLayout

对于 RecyclerBin 中的几个变量,因为还未添加任何 View 所以都为 0.

变量 | 数量 ---|--- mActiveViews (表示屏幕可见的itemView )| 0 个 mCurrentScrap/mScrapViews[0] ((表示废弃移除的itemView )) | 0个 getChildCount()/childCount | 0 个

@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);mInLayout = true;...//因为是第一次 OnLayout 所以  getChildCount //还是 0 final int childCount = getChildCount();if (changed) {for (int i = 0; i < childCount; i++) {getChildAt(i).forceLayout();}mRecycler.markChildrenDirty();}//直接进入  layoutChildrenlayoutChildren();....}
@Overrideprotected void layoutChildren() {...// 因为 childcount 为 0 ,所以这里并没有什么作用//但是 在第二次的时候 这里就需要注意//现在可以先跳过。// Pull all children into the RecycleBin.// These views will be reused if possiblefinal int firstPosition = mFirstPosition;final RecycleBin recycleBin = mRecycler;if (dataChanged) {for (int i = 0; i < childCount; i++) {recycleBin.addScrapView(getChildAt(i), firstPosition+i);}} else {recycleBin.fillActiveViews(childCount, firstPosition);}//和上面的一样// Clear out old viewsdetachAllViewsFromParent();//}
switch 里面default:if (childCount == 0) {if (!mStackFromBottom) {final int position = lookForSelectablePosition(0, true);setSelectedPositionInt(position);// 到这里方法sel = fillFromTop(childrenTop);} else {final int position = lookForSelectablePosition(mItemCount - 1, false);setSelectedPositionInt(position);sel = fillUp(mItemCount - 1, childrenBottom);}}

因为是第一次 OnLayout ,因此有效的操作实际上就到 fillFromTop 这个方法

private View fillFromTop(int nextTop) {mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);if (mFirstPosition < 0) {mFirstPosition = 0;}return fillDown(mFirstPosition, nextTop);}

fillFromTop->fillDown 这两个方法就是进行第一次往 ListView 添加 View 。 其中的 fillDown 有个具体的循环。

private View fillDown(int pos, int nextTop) {View selectedView = null;...int end = (mBottom - mTop);...//进入一个循环while (nextTop < end && pos < mItemCount) {View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);}}

上面的循环就是 根据屏幕的大下,对 ListView 添加满屏幕的 ItemView 。重点关注一下 makeAndView

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {if (!mDataChanged) {// Try to use an existing view for this position.//尝试从 getActiveView 获取,但是这个时候为 0//所以 activeView 为 nullfinal View activeView = mRecycler.getActiveView(position);if (activeView != null) {// Found it. We're reusing an existing child, so it just needs// to be positioned like a scrap view.setupChild(activeView, position, y, flow, childrenLeft, selected, true);return activeView;}}//通过 obtainView 获取// Make a new view for this position, or convert an unused view if// possible.final View child = obtainView(position, mIsScrap);// This needs to be positioned and measured.setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);return child;}
View obtainView(int position, boolean[] outMetadata) {...//首先会获取一个 ScrapView 缓存废弃的 itemView ,因为这个时候为 0 //所以 会将 null 传到 mAdapter.getView 这个方法中。final View scrapView = mRecycler.getScrapView(position);final View child = mAdapter.getView(position, scrapView, this);...return child;}

我们可以知道 mAdapter.getView 方法就是 BaseAdapter 中的 getView 方法。

@Overridepublic View getView(int position, View convertView, ViewGroup parent) {if (convertView==null){convertView = LayoutInflater.from(Main2Activity.this).inflate(R.layout.item,null);}TextView textView = convertView.findViewById(R.id.tv_text);textView.setText((position + ":对应为" + convertView).replace("android.widget.",""));return convertView;}});

此时 convertView 就是 scrapView ,因为这时为 null ,所以就通过 LayoutInflater 进行加载。这样 obainView 放回一个 加载的 View , 最后回到 setupChild ,在 setupChild 就将 ItemView 添加到 ListViewGoup 并 mChildrenCount ++ .

private void addInArray(View child, int index) {View[] children = mChildren;final int count = mChildrenCount;...children[index] = child;mChildrenCount++;...}

583a45497dea3d7583a862abdf2143cd.png

(2)第二次 OnLayout

经过一次 OnLayout 后之前的三个变量变化如下: 变量 | 数量 ---|--- mActiveViews (表示屏幕可见的itemView )| 0 个 mCurrentScrap/mScrapViews[0] ((表示废弃移除的itemView )) | 0个 getChildCount()/childCount | 占满屏幕的数量 首先还是还是 从 layoutChildren 开始

@Overrideprotected void layoutChildren() {...// 因为 childcount 这个时候就有值了 n ,//假设为  n// 首先判断有没有数据改变  dataChanged // 没有就进入 else // Pull all children into the RecycleBin.// These views will be reused if possiblefinal int firstPosition = mFirstPosition;final RecycleBin recycleBin = mRecycler;if (dataChanged) {for (int i = 0; i < childCount; i++) {recycleBin.addScrapView(getChildAt(i), firstPosition+i);}} else {// 这里就将 屏幕上的 itemView 添加到 //ActiveViews  中 ,ActiveViews 就是表示屏幕上的 itemView //集合recycleBin.fillActiveViews(childCount, firstPosition);}//然后就将 所有的 View 从 ListView 中先移除//这是为了后面操作导致重复添加。// Clear out old viewsdetachAllViewsFromParent();//}

上面的逻辑就是将 ListView 中的itemView 添加到 ActiveViews 数组中,然后就先移除,因为保存到了 ActiveViews 中,所以不用担心会重新 LayoutInflate 的问题。 变量 | 数量 ---|--- mActiveViews (表示屏幕可见的itemView )| n 个 mCurrentScrap/mScrapViews[0] ((表示废弃移除的itemView )) | 0个 getChildCount()/childCount | n 个 因为是第二次 onLayout ,所以不会进入 fillTop ->fillDown ,而是进入 fillSpecific,但是最后还是回到 makeAndaddView

private View fillSpecific(int position, int top) {boolean tempIsSelected = position == mSelectedPosition;View temp = makeAndAddView(position, top, true, mListPadding.left, ........}
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {if (!mDataChanged) {// Try to use an existing view for this position.final View activeView = mRecycler.getActiveView(position);if (activeView != null) {// Found it. We're reusing an existing child, so it just needs// to be positioned like a scrap view.setupChild(activeView, position, y, flow, childrenLeft, selected, true);return activeView;}}...}

因为 ActiveView 不为 null 了,所以这里就将之前保存的 每个 itemView 重新添加到 ListView ViewGroup . 而且 ActiveView 每次get 都会进行删除。 这样三个变量的结果就为 变量 | 数量 ---|--- mActiveViews (表示屏幕可见的itemView )| 0 个 mCurrentScrap/mScrapViews[0] ((表示废弃移除的itemView )) | 0个 getChildCount()/childCount | n 个

c488863334a4d8f69601d80001358d50.png

四.滑动一个 item

因为是 滑动所以肯定在 onTouchEvent 的 MOVE 里面

@Overridepublic boolean onTouchEvent(MotionEvent ev) {....switch (actionMasked) {....case MotionEvent.ACTION_MOVE: {onTouchMove(ev, vtev);break;}
private void onTouchMove(MotionEvent ev, MotionEvent vtev) {// 这里又回到 layoutChildrenif (mDataChanged) {// Re-sync everything if data has been changed// since the scroll operation can query the adapter.layoutChildren();}

在 layoutChildren 最后又会回到 makeAndAddView

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {if (!mDataChanged) {// Try to use an existing view for this position.final View activeView = mRecycler.getActiveView(position);if (activeView != null) {// Found it. We're reusing an existing child, so it just needs// to be positioned like a scrap view.setupChild(activeView, position, y, flow, childrenLeft, selected, true);return activeView;}}// 执行下面的// Make a new view for this position, or convert an unused view if// possible.final View child = obtainView(position, mIsScrap);// This needs to be positioned and measured.setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);return child;}

这里因为 之前的 getActiveView 已经将所有的 item取出,所以还是会通过 obtainView 去加载一个 item.而且在 onTouchMove 最后还会调用

for (int i = childCount - 1; i >= 0; i--) {final View child = getChildAt(i);if (child.getTop() <= bottom) {break;} else {....// 将移除屏幕的 itemView 添加到 ScrapView mRecycler.addScrapView(child, position);}}}}

这个时候那个几个变量的变化为 mActiveViews (表示屏幕可见的itemView )| 0 个 mCurrentScrap/mScrapViews[0] ((表示废弃移除的itemView )) | 1个 getChildCount()/childCount | n+1 个

五.继续滑动

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {final View child = obtainView(position, mIsScrap);// This needs to be positioned and measured.setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);return child;}
final View scrapView = mRecycler.getScrapView(position);//这个时候和第一次就不同了 因为 这个时候 的 scrapView//就不为  null, 因此 scrapView 和 convertView 就不为null.final View child = mAdapter.getView(position, scrapView, this);if (scrapView != null) {if (child != scrapView) {// Failed to re-bind the data, return scrap to the heap.mRecycler.addScrapView(scrapView, position);} else if (child.isTemporarilyDetached()) {outMetadata[0] = true;// Finish the temporary detach started in addScrapView().child.dispatchFinishTemporaryDetach();}}

上面的过程实际上就是将之前移除屏幕的 itemView 重新获取并设置到 mAdapter.getView 中,这也是 我们在写 getView 方法的时候需要对 convertView 进行判断,因为这样就可以利用 ListView 的缓存机制,不用重新进行 LayoutInflate 。

最后:

  • 一个 ListView 共创建的 itemView 数就是屏幕显示的 数量+1 ,这个原因在滑动一个 item 的时候就说明。

为了证明这个说法,最后做一下验证。

7bf6c4051ca60fba2d89b64199e50cf2.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/551973.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++ 显示三维散点图_【无机纳米材料科研制图——OriginLab 0210】Origin多组柱状图3D柱状图及3D散点图...

此篇&#xff0c;我们来分享Origin绘制多组柱状图、立体柱状图和三维散点图。一、多组柱状图1&#xff09;准备数据。准备数据&#xff0c;并点击Plot——》Column/Bar/Pie——》Column进行绘制。结果如下。2&#xff09;带有误差棒的绘图。如果想绘制带有误差棒的直方图&#…

保存文件_wps文件保存在哪里

我们经常使用WPS进行文本、表格的编辑&#xff0c;有时候顺手点击保存之后&#xff0c;文件就会根据默认文件路径保存&#xff0c;如果您不知道默认的文件保存路径&#xff0c;怎么才能够找到刚刚保存的文件&#xff0c;下面就为大家介绍一下wps文件保存在哪里&#xff0c;希望…

mobi格式电子书_进阶能力 | 了解常见的电子书格式

静读君是初中开始接触电子书的&#xff0c;那个时候以为电子书就是TXT&#xff0c;到后来渐渐地接触到了PDF、DOC、CAJ 才知道原来电子书还分这么多的格式&#xff0c;那个时候还在想&#xff0c;为什么要弄这么多不同的格式呢&#xff1f;那不是自找麻烦吗&#xff1f;接触的…

c++ udp通信_Web 通信协议,你还需要知道:SPDY 和 QUIC

一、开拓者&#xff1a;SPDY1. 简介&#xff1a;spdy 是由google推行的&#xff0c;改进版本的HTTP1.1 (那时候还没有HTTP2)。它基于TCP协议&#xff0c;在HTTP的基础上&#xff0c;结合HTTP1.X的多个痛点进行改进和升级的产物。它的出现使web的加载速度有极大的提高。HTTP2也借…

查看 rabbitmq 启动websocket 提示404_RabbitMQ 部署记录

erlang与rabbitmq版本对应关系&#xff1a;https://www.rabbitmq.com/which-erlang.html安装erlang下载地址&#xff1a;http://www.erlang.org/downloads11.安装依赖 2yum install -y gcc gcc-c ncurses ncurses-base ncurses-devel ncurses-libs ncurses-static ncurses-term…

linux远程windows执行cmd,Linux服务器远程连接window服务器并执行cmd命令

前段时间&#xff0c;要给一个分布式调度系统写一个运维脚本&#xff0c;这个分布式调度系统部分子系统部署在window服务器上&#xff0c;这个时候就要想办法用Linux远程来连接window服务器&#xff0c;并执行cmd命令。下面是我的解决方法&#xff1a;1、在Linux服务器上的处理…

Linux安装Flash脚本,Linux(CentOS)下的Shockwave Flash shell一键更新脚本

原创内容,转载请注明出处: https://www.myzhenai.com.cn/post/2318.html https://www.myzhenai.com/thread-17933-1-1.html关键字: Shockwave Flash一键更新脚本 Flash一键更新脚本这个脚本其实是我自己用的, 我的系统里安装了FlashPlayer软件和火狐(firefox)浏览器里安装了Sho…

收文处理和发文处理的环节_集气罩的设计是气体净化、废气处理系统设计的重要环节...

在工业生产中&#xff0c;常用于控制各种颗粒物和气态污染物的方法是将有害物质在发生源收集起来&#xff0c;经过净化设备净化后排到大气中&#xff0c;这就是局部排气净化系统&#xff0c;这种系统所需要的风量最小&#xff0c;效果好&#xff0c;能耗也少&#xff0c;是生产…

linux磁盘管理不用LVM,[linux] LVM磁盘管理(针对xfs和ext4不同文件系统)

简单来说就是&#xff1a;PV&#xff1a;是物理的磁盘分区VG&#xff1a;LVM中的物理的磁盘分区&#xff0c;也就是PV&#xff0c;必须加入VG&#xff0c;可以将VG理解为一个仓库或者是几个大的硬盘LV&#xff1a;也就是从VG中划分的逻辑分区如下图所示PV、VG、LV三者关系&…

cad监控图标_干货!多种不同环境的无线视频监控系统拓扑图

有人问&#xff0c;既然无线视频监控系统如此普及&#xff0c;是不是所有地方都能用到无线视频监控设备呢&#xff1f;例如在大街上、学校里&#xff0c;工厂中、写字楼内&#xff0c;建筑工地上、公园中、住宅小区里、江河岸边、港口码头、甚至是森林、戈壁滩等等。只要有需要…

axios 获取上传进度_PHP获取HTTP body内容的方法总结

有时候我们获取数据时需要根据Header中的格式来解析&#xff0c;比如上传一个json而不是一个文本。这里用到了 php输入|输出流 的概念。PHP 提供了一些杂项输入/输出(IO)流&#xff0c;允许访问 PHP 的输入输出流、标准输入输出和错误描述符&#xff0c; 内存中、磁盘备份的临时…

python搭建selenium_自动化测试之路3-selenium3+python3环境搭建

1、首先安装火狐浏览器 有单独文章分享怎么安装 2、搭建python环境 安装python&#xff0c;安装的时候把path选好&#xff0c;就不用自己在配置&#xff0c;安装方法有单独文档分享 安装好以后cmd打开输入python查看是否配置好 3、安装pip 一般python会默认带一个&#xff0c;放…

keras实现简单lstm_深度学习(LSTM)在交通建模中的应用

上方点击蓝字关注?在简单了解了LSTM原理之后&#xff0c;本期我将以航班延误预测为例为大家介绍一下如何利用Python编程来构建LSTM模型。这里我们要用到一个高级的深度学习链接库——Keras,它以TensorFlow或者Theano作为后端引擎&#xff0c;只处理模型的建立、训练和预测等功…

commons-pool2-2.3 jar包_[漏洞复现]FastJson 1.2.61远程代码执行漏洞(From第三方jar包)

前言最近FastJson更新了黑名单,升级到了1.2.61版本,我尝试bypass其黑名单,在AutType打开的情况下成功绕过了黑名单防护.(目前暂未修复,官方即将更新)复现环境准备1.JDK 8U202.所需jar清单如下fastjson-1.2.61.jarcommons-configuration2-2.0.jarcommons-lang3-3.3.2.jarcommons…

王思聪吃热狗的c语言小程序,王思聪吃热狗火了,开发各种恶搞小程序!王校长:我不要面子的啊...

原标题&#xff1a;王思聪吃热狗火了&#xff0c;开发各种恶搞小程序&#xff01;王校长&#xff1a;我不要面子的啊近日王校长上了不少次热搜&#xff0c;其中有一条就是王校长吃热狗火了&#xff0c;接下来就出现了各种各样的表情包&#xff0c;手机壳&#xff0c;素描等等。…

酷狗音乐linux版_让父母也爱上音乐 酷狗音乐大字版成中老年人新宠

当智能手机成为人们日常生活中必不可少的一部分&#xff0c;手机早已从单纯的通话设备变成了功能强大的“百宝箱”&#xff0c;听音乐、看视频、拍照上网早已不是什么新鲜事。然而在我们玩手机玩的不亦乐乎时&#xff0c;你有没有关注过爸妈对使用智能手机的接受度&#xff1f;…

二分法求近似根c语言程序,求一C++风格程序,用二分法求f(x)=0的根

满意答案vwamuoor8000推荐于 2018.04.24#include #include typedef double (*F)(double);/* brief 二分法求解函数* param[in] f 求解的函数* param[in] a, b 求解的区间端点* param[in] 吵盯e 精度* pre f(a)*f(b)<0且f在该闭区间上连续* return 方程液枝的解 */double sol…

react的导出是怎么实现的_从零开始开发一个 React

这个是从零开始开发一个 React 系列的第七篇。想要访问之前的内容可以点击下方的链接进行访问&#xff1a;最简单的实现&#xff0c;包括 vdom 结构&#xff0c;createElement&#xff0c;ReactDOM.render增加 Class 的支持增加 JSX 的支持增加 state 支持增加声明周期增加 dom…

python列表生成式内必须定义匿名函数_Python基础-----基础概念总结

Python基础-----基础概念总结 Python程序的构成 构成图行连接符&#xff1a;\对象基本构成和内存示意图对象及其引用&#xff08;变量&#xff09;标识符 基本用法命名规则变量和简单的赋值语句 变量声明必须初始化删除变量和垃圾回收机制链式复制系列解包赋值常量&#xff08;…

英语四级c语言,2017年大学英语四级精选试题练习

2017年大学英语四级精选试题练习勤奋和智慧是双胞胎&#xff0c;懒惰和愚蠢是亲兄弟。以下是小编为大家搜索整理的2017年大学英语四级精选试题练习&#xff0c;希望能给大家带来帮助!更多精彩内容请及时关注我们应届毕业生考试网!1、Courses with the numbers 800 or above are…