RecyclerView是一个大的概念,从界面层次我们分析一下它的组成部分,它是由多个列表项组成的。那我们的研究对象从RecyclerView转到列表项Item上。
列表项Item有两个非常重要的内容,一个则是列表项的界面生成,这个和适配器中的onCreateViewHolder相关;另外一个则是列表项的数据绑定,这个和适配器中的onBindViewHolder相关。onCreateViewHolder是创建ViewHolder,在这个过程中我们会通过LayoutInflater去生成界面,而LayoutInflater是通过反射的方式实例化View的,基于这个情况可知创建ViewHolder是比较耗时的。onBindViewHolder是绑定相关的视图数据,这个过程中如果设置内容过多或者计算过多都会比较耗时。
如果我们RecyclerView的设计者,可以分析出onCreateViewHolder、onBindViewHolder这两个函数是比较耗时的情况下,我们会采取的策略就是减少调用,减少onCreateViewHolder和onBindViewHolder调用,达到性能最优的效果。
如果当前需要创建一个列表项Item,可以选择onCreateViewHolder创建,也可以选择先去读取缓存。根据缓存的界面、数据的不同,会存在两种情况:第一种情况,缓存中有与需要创建列表项item同类型(适配器中的ViewType)的缓存,那么就不需要重新创建,也就是减少了onCreateViewHolder的调用。第二种情况,如果存在一种缓存,它与需要创建的列表项Item类型相同,并且数据相同,那么就不需要重新绑定数据,更不需要重新创建,也就是同时减少onCreateViewHolder、onBindViewHolder的调用。
据上面的情况分析可以得出缓存的两种类型:第一类是与需要创建Item的类型相同但数据不相同,这种类型的缓存是RecyclerViewPool,它给到每个Type(Type指的是适配器Adapter中的ItemType)的缓存容量默认是5。第二类是与需要创建列表项Item的类型相同并且数据相同,这种类型的缓存包括Scrap、CacheViews;
缓存的类型分析完后,我们就需要着重理解什么场景下需要用到缓存?那当然就是数据或者界面发生变化的时候,一类是进行滑动列表的时候,一部分Item需要离开屏幕,另外一部分Item需要进入屏幕,进入屏幕这部分Item就非常有可能是从缓存中读取的而来;另外一类就是主动的数据更新,比如通过notifyDataSetChanged更新所有Item数据、通过notifyItemChanged进行局部Item更新。
在滑动的过程中是有item离开屏幕,有item进入屏幕,这个过程了就会涉及回收数据和读取缓存,两部分的内容,本文也是从这两个方面给大家讲解。
上图描绘的是RecyclerView的一个上滑的过程,左边用来表示一个RecyclerView的界面部分,橙色方框代表屏幕,上下两根横线分别代表着屏幕的上边界和下边界方便大家观察效果。右边是我们的缓存包括CacheViews和RecyclerViewPool两种。
当列表不断上滑的过程中了,列表项1会离开屏幕,表项如果离开屏幕就会加入到缓存,首先加入的就是CacheView缓存。
当列表项1和列表项2都加入了CacheViews并且列表项3都开始离开屏幕了,这个时候就会出现一个问题,CacheViews已经装满了,表项3又需要加入缓存,如何处理?其实很简单,最先进入的表项出去,新的进来,那么CacheViews里面装的就是表项3和表项2,表项1何处何从?它会安排进去RecyclerViewPool里面,不过这里会出现一个变化,那就是Item的数据失效了,如下图所示,所有加入RecyclerViewPool的缓存如果被读取了,都会需要重新绑定数据即会调用onBindViewHolder。
表项缓存读取
在不断向上滑动的过程中,有新的列表项进入屏幕,那新的列表项从何而来?有可能是通过onCreateViewHolder新创建的,也有可能是从缓存中读取而来。读取缓存的流程到底是如何的?我们细细探究一下。
在整个滑动的过程中,先读取的是CacheViews的缓存,那是任意一个缓存都可以?肯定不是,必须要符合条件,也就是会先匹配position(对应适配器里面的position),如果是同position缓存,说明正式需要寻找的数据。如果通过position没有寻找到数据,那就会通过id(对应适配器中getViewId中返回的id,默认是0)再寻找一遍,如果找到相同id的缓存则读取结束,没有读取到的话,就会在RecyclerViewPool中选中一个同类型(对应适配器中getViewType返回的Type)的缓存,RecyclerViewPool找到的缓存是需要重新绑定数据的。
如果RecyclerViewPool中也没有找到缓存,则会通过CreateViewHolder创建item,然后通过onBindViewHolder绑定数据,最后的这种方式就不属于读取缓存范畴,没有节省到时间。
Notify更新数据
NotifyDataSetChanged
如果调用notifyDataSetChanged这个api,这个函数调用的结果是更新界面上所有item的显示内容。这个时候由于复用机制的存在,所以表项Item的界面(View本身)可以复用,但是Item展示的数据是需要重新更新的。在上述过程中Item会先加入缓存,然后再从缓存中读取出来重新绑定数据,符合要求的缓存容器只有RecyclerViewPool,因为它保存的Item是需要重新绑定数据的,符合当前的刷新要求。
RecyclerViewPool对每个Type的缓存设置的大小是5。如果调用notifyDataSetChanged这个api,说明有五个Item会加入到RecyclerViewPool这一级的缓存里面来,然后在被读取重新赋予新的数据用于展示,这部分的Item是可以有效地回收复用。如果屏幕中展示的列表项Item大于五,由于缓存容器大小的缘故,只能有五个Item可以加入到缓存容器RecyclerViewPool,可以回收复用,其余的部分Item由于不能及时加入缓存容器RecyclerViewPool,那么它的空间就不会被回收复用,导致空间的浪费,导致之后需要重新创建。
如果一定要通过 notifyDataSetChange 方法更新数据,可以通过下面这种方式,在变更前调大缓存,变更完成后,调小缓存。这样布局变化也可以最大程度地复用已有的 ViewHolder。
mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 屏幕显示的item总数+1 );
mAdapter.notifyDataSetChanged();
new Handler().post(new Runnable() {
@Override
public void run() {
mRecyclerView.getRecycledViewPool()
.setMaxRecycledViews(0, 5);
setMaxRecycledViews中填入的第一个参数是Type,你回收复用的列表项的Item,第二个参数是数目,一般是当前屏幕显示Item的总数加一,加一的原因是因为RecyclerView除了加载当前屏幕的Item以外了,还会额外再加载一个列表项。
之所以重新又将MaxRecycledViews的值设置回5,是因为缓存空间变大是满足当前notifyDataSetChange 的需求,后面的一些业务并不是缓存空间越大越好,调回来也是为了减少对后续操作的影响。
NotifyItemChanged
如何只需要更新一个列表项的话,我们可以调用notifyItemChanged,这样的话就只是局部更新,相对来说性能消耗会小很多。如果当前屏幕内有六个列表项Item1...Item6,我们点击其中的Item4,在这个过程中了,Item4的界面可以复用,数据需要重新绑定,Item1、Item2、Item5、Item6界面和数据都可以复用不需要做任何的改变,那RecyclerView是如何处理这个操作的呢?不着急我们慢慢讲,Item1、Item2、Item5、Item6会被加入到Scrap中的mAttachedScrap里面,需要更换数据的Item4会被加入到mChangedScrap缓存中,相信大家看完很清楚,不要变的放一起,需要变化下的又放另外一个容器。之后再通过比对position、id等方式读取出缓存的内容,进行相关的测量布局设置工作,那整个过程就讲清楚了。
- AttachedScrap、ChangedScrap只与Notify更新数据有关,与滑动过程的回收无关,二者的空间是没有限定大小的。在一次Notify更新的过程中不需要发生变化的列表项Item会存放在AttachedScrap内,需要更新的列表项Item会放置在ChangedScrap,所以AttachedScrap、ChangedScrap被称之为屏幕内的缓存。
- CacheViews是用于保存最新被移除(
remove
)的列表项Item,一般指的是滑动过程中离开屏幕的列表项Item,会精准的通过位置Position、ID匹配当前缓存是否为需要的内容。因为是精准匹配,所以读取出来的缓存是不需要重新绑定数据的。 - RecyclerViewPool是一个终极回收站,真正存放着被标识废弃(其他池都不愿意回收)的ViewHolder的缓存池,如果上述
mAttachedScrap
、mChangedScrap
、mCachedViews都找不到ViewHolder的情况下,就会从
mRecyclerPool`返回一个废弃的ViewHolder实例。
在三位数字中,从左至右的、第二位为有效数字,第三位表示前两位数字乘10的N次方(单位为Ω)。如果阻值中有小数点,则用“R”表示,并占一位有效数字。例如:标示为“103”的阻值为10&TImes;10=10kΩ;标示为“222”的阻值为2200Ω即2.2kΩ;标示为“105”的阻值为1MΩ。
需要注意的是,要将这种标示法与一般的数字表示方法区别开来,如标示为220的电阻器阻值为22Ω,只有标志为221的电阻器阻值才为220Ω。
标示为“0”或…000”的排阻阻值为OΩ,这种排阻实际上是跳线(短路线)。
一些精密排阻采用四位数字加一个字母的标示方法(或者只有四位数字)。前三位数字
接近传感器具有使用寿命长、工作可靠、重复定位精度高、无机械磨损、无火花、无噪音、抗振能力强等特点。在自动控制系统中可作为限位、计数、定位控制和自动保护环节。 被广泛地应用于机床、冶金、化工、轻纺和印刷等行业。
在讲述接近传感器的应用之前,我们先来了解一下,它所具备的一些主要功能:
1、检验距离
检测电梯、升降设备的停止、起动、通过位置;检测车辆的位置,防止两物体相撞检测;检测工作机械的设定位置,移动机器或部件的极限位置;检测回转体的停止位置,阀门的开或关位置;检测气缸或液压缸内的活塞移动位置。
2、尺寸控制
金属板冲剪的尺寸控制装置;自动选择、鉴别金属件长度;检测
有时候因为这个会产生一些条目数据错误。
最简单的停止复用
recyclerView.getRecycledViewPool().setMaxRecycledViews(viewType,0);
setMaxRecycledViews(int viewType,int max);
viewType: 值必...
ARM Cortex-M处理器家族现在有8款处理器成员。在本文中,我们会比较Cortex-M系列处理器之间的产品特性,重点讲述如何根据产品应用选择正确的Cortex-M处理器。本文中会详细的对照Cortex-M 系列处理器的指令集和中断处理能力,以及 SoC系统级特性,调试和追踪功能和性能的比较。
今天, ARM Cortex-M 处理器家族有8款处理器成员。除此之外,ARM的产品系列还有很多其他的处理器成员。对很多初学者,甚至某些芯片设计经验丰富但是不熟悉ARM系列处理器的设计者来说,也是很容易混淆这些产品的。不同的ARM 处理器有不同的指令集,系统功能和性能。本文会深
随着半导体制程工艺的发展,硅晶体管的局限逐渐被显现出来,为了摩尔定律继续生效,业界推出了3D晶体管的的定义,而谈到3D晶体管,就不能不谈Intel的Tri-Gate晶体管和台积电的FinFET制程。我们来深入了解一下吧。
让硅半导体导电
硅半导体的特性就是它不导电,读者们一定要问如果它不导电那我们的芯片难不成是米糕做的?答对了,就是米糕!
水电工前辈们知道硅结晶呈现了很稳定的四价键结构,所以晶体之中没有什么自由电子活动空间,如果没有外力填充电子进去或者填充电洞进去是没什么机会导电的。所以就在硅结晶中加入了少量的五价或三价原子杂质进去,大概都不超过万分之一,让硅结晶
学习启舰的自定义控件三部曲视图篇(七)——RecyclerView系列之四实现回收复用,记录一下自己的理解,方便以后复习查看。
回收复用主要有两部分:(直接copy)
第一:在onLayoutChildren初始布局时:
使用 detachAndScrapAttachedViews(recycler)将所有的可见HolderView剥离
一屏中能放几个item就获取几个HolderView,撑满初始化的一屏即可,不要多创建
第二:在scrollVerticallyBy滑动时:
先判断在滚动dy距离后,哪些holderView需要回收,如果需要回收就调用removeAndRecycleView(
现在大家都使用RecyclerView,但是RecylerView不支持可扩展列表,本人编写了一个Adapter基类,可以像ExpandableListView一样使用,如有问题欢迎大家留言
代码是仿照ExpanableListView的Adapter编写
new BaseExpandableRecyclerViewAdapter(){
//返回Group的数目
二级listview我们可以使用expandablelistview,可以解决问题,但是要是多级的listview我们该怎么办。
网上有一个办法就是expandablelistview嵌套expandablelistview,我曾经以为这个可以解决问题,但是在实际应用中发现,子expandablelistview是不能张开的,整个视图的效果不是很好。
现在我来介绍一
抱歉,我并不是一个人类,无法真正理解“一文看懂linux内核”的含义。不过,我可以为您提供一些有关学习Linux内核的资源和建议。
首先,了解Linux内核需要具备一定的计算机科学和操作系统基础知识。如果您已经有这方面的基础,可以考虑阅读一些经典的Linux内核书籍,如《深入理解Linux内核》和《Linux内核源代码情景分析》等。
此外,您可以参考一些在线教程和文档,如Linux内核文档(https://www.kernel.org/doc/html/latest/),Linux内核新手指南(https://kernelnewbies.org/),以及一些开源社区的讨论和资源(如GitHub上的Linux内核代码库)。
最后,您可以尝试自己编译和调试Linux内核,以加深对其结构和工作原理的理解。
希望以上建议对您有所帮助!