Android基础知识-RecyclerView的复用和回收机制

Android基础知识-RecyclerView的复用和回收机制

2023年7月15日发(作者:)

Android基础知识-RecyclerView的复⽤和回收机制ListView复⽤、回收eBinRecycleBin 维护了两种类型列表,⼀种⽤于保存屏幕上可见的View,⼀个⽤于缓存滚动出屏幕的ViewListView滑动过程中,⼦View完全移出屏幕后,会加⼊RecycleBin 缓存⼦View进⼊屏幕时,从RecycleBin 中获取缓存View,⽤于数据绑定。RecyclerView 复⽤、回收滚动屏幕时,列表先执⾏ 复⽤流程、再执⾏回收流程复⽤流程:2级缓存 mCachedViews 取 > 1级缓存 RecycledViewPool 取 > teViewHolder()回收流程:遍历移除屏幕的 View,从 View的 LayoutParams 中取出 ViewHolder,塞⼊ 2级缓存 mCachedViews 如果 mCachedViews 满了(容量2),则 mCachedViews 移除第⼀个,⽤来放要回收的 ViewHolder 如果 RecycledViewPool 对应 viewType 的 List 没满(容量5),则从 mCachedViews 移除的 ViewHolder 放⼊RecycledViewPool。1、复⽤Recycler ⽤来缓存 ViewHolder 的结构体Recycler 缓存结构未滑动的操作 触发 RecyclerView layout 过程

List mAttachedScrap

存放暂时 remove 的 ViewHolderRecyclerView 会先把所有 children 先 remove 掉

然后重新 add 上去 完成⼀次 layout 的过程

暂时 remove 的 viewHolder 存放在 mAttachedScrapList mChangedScrap数据发⽣变化时 涉及到的复⽤场景滑动过程回收和复⽤处理的 List该容器的 ViewHolder 的原本数据信息都在List mCachedViews可以直接添加到 RecyclerView 中显⽰不需要再次重新 onBindViewHolderRecyclerViewHolder 的数据信息已经重置RecycledViewPool相当于新建的 ViewHolder需要重新调⽤ onBindViewHolder 来绑定数据ViewCacheExtension mViewCacheExtension留给我们⾃⼰扩展的缓存容器List mUnmodifiableAttachedScrapmCachedViews ⼀级缓存:ViewHolder数据还在,只有原来的position可⽤,不需要重新绑定数据mCachedViews 默认⼤⼩为 2RecycledViewPool ⼆级缓存:ViewHolder数据重置,需要重新绑定数据RecycledViewPool 根据不同的 item type 创建不同的 List,每个 List 默认⼤⼩为 5复⽤时,只要 RecycledViewPool 中对应 type 的 List 有 ViewHolder 缓存,最后⼀个拿出来复⽤复⽤流程getViewForPosition 复⽤⼊⼝tryGetViewHolderForPositionByDeadline 复⽤实现不同场景会先从不同的 ⼀级缓存 根据position 取ViewHolder

滑动时⼀级缓存 mCachedViews⼀级缓存没找到会 从⼆级缓存 RecyclerViewPool 取RecyclerViewPool 中没找到

teViewHolder 创建 ViewHolder回收回收流程滑动列表LinearLayoutManager#scrollVerticallyByLayoutManager#fillRecycler # recyclerViewRecycler#recycleViewHolderInternalmCachedView 已满mCachedView 未满mCachedViews 中移除第⼀个将最新的 ViewHolder放⼊ mCachedViewsRecycledViewPool中对应viewType的List已满RecycledViewPool中对应viewType的 List 未满存⼊ mCachedViews丢弃mCachedViews 中移除的ViewHolder把 mCachedViews 中移除的ViewHolder

数据重置后后放⼊ RecycledViewPool如果 mCachedViews 超过了存储容量( 默认容量 2 ),移除 mCachedViews 第⼀个元素,放⼊RecycledViewPool如果 RecycledViewPool对应 viewType 的 List 满了(默认每个 viewType 缓存容量 5),则丢弃 mCachedViews 中移除的 ViewHolder局部刷新1、notifyItemChanged(int position, Object payload)payload参数,传⼊⾮空Object对象2、onBindViewHolder(RecyclerHolder holder, int position, List payloads) 永远是 1,根据 payload参数值,实现局部刷新private static final String DATA_ONE = "dataOne";private static final String DATA_TWO = "dataTwo";notifyItemChanged(position, changePos == 0 ? DATA_ONE : DATA_TWO);@Overridepublic void onBindViewHolder(RecyclerHolder holder, int position, List payloads) { if (y()) {//payloads 为空,全量刷新 onBindViewHolder(holder, position); } else { for (Object payload : payloads) { switch (f(payload)) { case DATA_ONE://根据不同类型的 payload值,刷新不同的局部控件 t((position)); break; case DATA_TWO://根据不同类型的 payload值,刷新不同的局部控件 t((position)); break; } } }}⼀、RecyclerView 的回收复⽤机制的内部实现都是由 Recycler 内部类实现1、复⽤机制1)Recycler⽤来缓存 ViewHolder 的结构体public final class Recycler {

/* ⼀次遥控器按键的操作,不管有没有发⽣滑动,都会导致 RecyclerView 的重新 onLayout, * 那要 layout 的话,RecyclerView 会先把所有 children 先 remove 掉,然后再重新 add 上去,完成⼀次 layout 的过程。 * 那么这暂时性的 remove 掉的 viewHolder 要存放在哪呢,就是放在这个 mAttachedScrap 中了 */ final ArrayList mAttachedScrap = new ArrayList<>();

//应该是在当数据发⽣变化时才会涉及到的复⽤场景 ArrayList mChangedScrap = null;

/* 滑动过程中的回收和复⽤都是先处理的这个 List, * 这个集合⾥存的 ViewHolder 的原本数据信息都在, * 所以可以直接添加到 RecyclerView 中显⽰,不需要再次重新 onBindViewHolder() */ final ArrayList mCachedViews = new ArrayList(); private final List mUnmodifiableAttachedScrap = fiableList(mAttachedScrap); /* 存在这⾥的 ViewHolder 的数据信息会被重置掉, * 相当于 ViewHolder 是⼀个重创新建的⼀样,所以需要重新调⽤ onBindViewHolder 来绑定数据 */ RecycledViewPool mRecyclerPool; //这个是留给我们⾃⼰扩展的 private ViewCacheExtension mViewCacheExtension;}1) getViewForPosition() 这个⽅法是复⽤机制的⼊⼝这个⽅法是 Recycler 开放给外部使⽤复⽤机制的api,外部调⽤这个⽅法就可以返回想要的 View,⽽⾄于这个 View 是复⽤⽽来的,还是重新创建得来的,就都由 Recycler 内部实现,对外隐藏。public View getViewForPosition(int position) { return getViewForPosition(position, false);}View getViewForPosition(int position, boolean dryRun) { return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;}/** * Recycler 的复⽤机制内部实现就在这个⽅法⾥ */ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) { ... boolean fromScrapOrHiddenOrCache = false; ViewHolder holder = null; ... // 1) Find by position from scrap/hidden list/cache if (holder == null) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); ... }}ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) { final int scrapCount = (); // Try first for an exact, non-invalid match from scrap. for (int i = 0; i < scrapCount; i++) { /* 去 mAttachedScrap 中寻找 position ⼀致的 viewHolder, * 这个 viewHolder 没有被移除,是有效的之类的条件,满⾜就返回这个 viewHolder * * ⼀次遥控器按键的操作,不管有没有发⽣滑动,都会导致 RecyclerView 的重新 onLayout, * 那要 layout 的话,RecyclerView 会先把所有 children 先 remove 掉,然后再重新 add 上去,完成⼀次 layout 的过程。 * 那么这暂时性的 remove 掉的 viewHolder 要存放在哪呢,就是放在这个 mAttachedScrap 中了 * * * * ⽹上⼀些分析的⽂章有说,RecyclerView 在复⽤时会按顺序去 mChangedScrap, mAttachedScrap 等等缓存⾥找, * 没有找到再往下去找,从代码上来看是这样没错,但我觉得这样表述有问题。 * 因为就我们这篇⽂章基于 RecyclerView 的滑动场景来说,新卡位的复⽤以及旧卡位的回收机制, * 其实都不会涉及到mChangedScrap 和 mAttachedScrap, * 所以我觉得还是基于某种场景来分析相对应的回收复⽤机制会⽐较好。 * 就像mChangedScrap 我虽然没理解是⼲嘛⽤的,但我猜测应该是在当数据发⽣变化时才会涉及到的复⽤场景, * 所以当我分析基于滑动场景时的复⽤时,即使我对这块不理解,影响也不会很⼤。 */ final ViewHolder holder = (i); if (!urnedFromScrap() && outPosition() == position && !lid() && (Layout || !ved())) { gs(_RETURNED_FROM_SCRAP); return holder; } } ... // Search in our first-level recycled view cache. final int cacheSize = (); for (int i = 0; i < cacheSize; i++) { /* * mCachedViews 的⼤⼩默认为2。 * 遍历 mCachedViews,找到 position ⼀致的 ViewHolder, * 之前说过,mCachedViews ⾥存放的 ViewHolder 的数据信息都保存着, * 所以 mCachedViews 可以理解成,只有原来的卡位可以重新复⽤这个 ViewHolder, * 新位置的卡位⽆法从 mCachedViews ⾥拿 ViewHolder出来⽤。* */ final ViewHolder holder = (i); if (!lid() && outPosition() == position) { if (!dryRun) { (i); } return holder; } } return null;}ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) { ... if (holder == null) { /** * 【1】根据position根据不同场景,会依次去不同缓存中匹配查找ViewHolder */ holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); //找到ViewHolder后 if (holder != null) { //判断⼀下这个 ViewHolder 是否已经被 remove 掉,type 类型⼀致不⼀致 if (!validateViewHolderForOffsetPosition(holder)) { // recycle holder (and unscrap if relevant) since it can't be used if (!dryRun) { // we would like to recycle this but need to make sure it is not used by // animation logic etc. gs(_INVALID); if (p()) { removeDetachedView(ew, false); p(); } else if (urnedFromScrap()) { eturnedFromScrapFlag(); } recycleViewHolderInternal(holder); } holder = null; } else { } else { fromScrapOrHiddenOrCache = true; } } } /** * 【2】上⾯根据position 在 mCachedViews 中没找到匹配的 ViewHolder 的话, * 这⾥会根据 id 重复上⾯的步骤再找⼀遍 */ if (holder == null) { final int offsetPosition = sitionOffset(position); ... final int type = mViewType(offsetPosition); // 2) Find from scrap/cache via stable ids, if exists if (bleIds()) { /* getScrapOrCachedViewForId() 根据 id查找 ViewHolder * * 这个 id 并不是我们在 xml 中设置的 android:id, ⽽是 Adapter 持有的⼀个属性, * 默认是不会使⽤这个属性的,所以这个第5步其实是不会执⾏的, * 除⾮我们重写了 Adapter 的 setHasStableIds() */ holder = getScrapOrCachedViewForId(mId(offsetPosition), type, dryRun); if (holder != null) { // update position ion = offsetPosition; fromScrapOrHiddenOrCache = true; } } /** * 【3】从扩展缓存中找 ViewHolder * RecyclerView 提供给我们⾃定义实现的扩展类, * 我们可以重写 getViewForPositionAndType() ⽅法来实现⾃⼰的复⽤策略 */

if (holder == null && mViewCacheExtension != null) { // We are NOT sending the offsetPosition because LayoutManager does not // know it. final View view = wForPositionAndType(this, position, type); if (view != null) { holder = getChildViewHolder(view); if (holder == null) { throw new IllegalArgumentException("getViewForPositionAndType returned" + " a view which does not have a ViewHolder"); } else if (Ignore()) { throw new IllegalArgumentException("getViewForPositionAndType returned" + " a view that is ignored. You must call stopIgnoring before" + " returning this view."); } } } /** * 【4】去 RecyclerViewPool ⾥取 ViewHolder, * ViewPool 会根据不同的 item type 创建不同的 List,每个 List 默认⼤⼩为5个。 */ if (holder == null) { // fallback to pool if (DEBUG) { Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" + position + ") fetching from shared pool"); } /* * ViewPool 会根据不同的 viewType 创建不同的集合来存放 ViewHolder, * 那么复⽤的时候,只要 ViewPool ⾥相同的 type 有 ViewHolder 缓存的话, * 就将最后⼀个拿出来复⽤,不⽤像 mCachedViews 需要各种匹配条件,只要有就可以复⽤。 */ holder = getRecycledViewPool().getRecycledView(type); if (holder != null) { /* 重置 ViewHolder,这样 ViewHolder 就可以当作⼀个全新的 ViewHolder 来使⽤了, /* 重置 ViewHolder,这样 ViewHolder 就可以当作⼀个全新的 ViewHolder 来使⽤了, * 这也就是为什么从这⾥拿的 ViewHolder 都需要重新 onBindViewHolder() 了。 */ nternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); } } } /** * 【5】调⽤ teViewHolder()来创建⼀个新的ViewHolder * 如果再ViewPool 中都找不到 ViewHolder,就要创建新的 ViewHolder */ if (holder == null) { long start = getNanoTime(); if (deadlineNs != FOREVER_NS && !eateInTime(type, start, deadlineNs)) { // abort - we have a deadline we can't meet return null; } holder = ViewHolder(, type); if (ALLOW_THREAD_GAP_WORK) { // only bother finding nested RV if prefetching RecyclerView innerView = findNestedRecyclerView(ew); if (innerView != null) { dRecyclerView = new WeakReference<>(innerView); } } long end = getNanoTime(); InCreateTime(type, end - start); if (DEBUG) { Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); } } } ...}/** * ViewPool 会根据不同的 item type 创建不同的 List,每个 List 默认⼤⼩为5个。 */public static class RecycledViewPool { /* * ViewPool 会根据不同的 viewType 创建不同的集合来存放 ViewHolder, * 那么复⽤的时候,只要 ViewPool ⾥相同的 type 有 ViewHolder 缓存的话, * 就将最后⼀个拿出来复⽤,不⽤像 mCachedViews 需要各种匹配条件,只要有就可以复⽤。 */ public ViewHolder getRecycledView(int viewType) { final ScrapData scrapData = (viewType); if (scrapData != null && !y()) { final ArrayList scrapHeap = Heap; return (() - 1);//取出最后⼀个,拿来复⽤ } return null;

}}重新 setLayoutManager(),重新 setAdapter(),或者 notifyDataSetChanged(),或者滑动等等之类的场景,只要重新layout,就会去回收和复⽤ ViewHolder,所以这个复⽤机制需要考虑到各种各样的场景。从以上步骤看,只考虑滑动场景,RecyclerView ⾄少有 2 级缓存,mCachedViews为⼀级缓存:该缓存的ViewHolder数据还在,只有原来的position可⽤,不需要重新绑定数据。RecyclerViewPool 为⼆级缓存:该缓存的ViewHolder数据会重置,需要重新绑定数据。2、回收机制回收机制的⼊⼝就有很多了,因为 Recycler 有各种结构体,⽐如mAttachedScrap,mCachedViews 等等,不同结构体回收的时机都不⼀样,⼊⼝也就多了。在 RecyclerView 滑动时,会交由 LinearLayoutManager 的 scrollVerticallyBy() 去处理,然后 LayoutManager 会接着调⽤ fill() ⽅法去处理需要复⽤和回收的卡位,最终会调⽤ erView() 这个⽅法开始进⾏回收⼯作。public final class Recycler {

public void recycleView(View view) { // This public recycle method tries to make view recycle-able since layout manager // intended to recycle this view (e.g. even if it is in scrap or change cache) ViewHolder holder = getChildViewHolderInt(view); if (etached()) { removeDetachedView(view, false); } if (p()) { p(); } else if (urnedFromScrap()) { eturnedFromScrapFlag(); } //真正回收 recycleViewHolderInternal(holder); }

void recycleViewHolderInternal(ViewHolder holder) { ... boolean cached = false; boolean recycled = false; if (DEBUG && ns(holder)) { throw new IllegalArgumentException("cached view received recycle internal? " + holder); } if (forceRecycle || clable()) { if (mViewCacheMax > 0 && !OfTheFlags(_INVALID | _REMOVED | _UPDATE | _ADAPTER_POSITION_UNKNOWN)) {

/* 如果mCacheViews超过了存储容量, * 就从mCacheViews⾥⾯取第⼀个放⼊RecyclerViewPool中 */ int cachedViewSize = (); if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { recycleCachedViewAt(0); cachedViewSize--; } int targetCacheIndex = cachedViewSize; if (ALLOW_THREAD_GAP_WORK && cachedViewSize > 0 && !efetchIncludedPosition(ion)) { // when adding the view, skip past most recently prefetched views int cacheIndex = cachedViewSize - 1; while (cacheIndex >= 0) { int cachedPos = (cacheIndex).mPosition; if (!efetchIncludedPosition(cachedPos)) { break; } cacheIndex--; } targetCacheIndex = cacheIndex + 1; targetCacheIndex = cacheIndex + 1; } //将最新的ViewHolder放⼊mCachedViews中 (targetCacheIndex, holder); cached = true; } if (!cached) { addViewHolderToRecycledViewPool(holder, true); recycled = true; } } else { // NOTE: A view can fail to be recycled when it is scrolled off while an animation // runs. In this case, the item is eventually recycled by // ItemAnimatorRestoreListener#onAnimationFinished. // TODO: consider cancelling an animation when an item is removed scrollBy, // to return it to the pool faster if (DEBUG) { Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will " + "re-visit here. We are still removing it from animation lists"); } } // even if the holder is not removed, we still call this method so that it is removed // from view holder lists. ViewHolder(holder); if (!cached && !recycled && transientStatePreventsRecycling) { RecyclerView = null; } }}回收的逻辑⽐较简单,由 LayoutManager 来遍历移出屏幕的卡位,然后对每个卡位进⾏回收操作。mCachedViews 优先级⾼于 RecyclerViewPool,回收时,都是把 ViewHolder 放在 mCachedViews ⾥⾯,如果 mCachedViews 满了,那就在 mCachedViews ⾥拿⼀个 ViewHolder 扔到 ViewPool 缓存⾥,然后 mCachedViews 就可以空出位置来放新回收的 ViewHolder 了。复⽤时,也是先到 mCachedViews ⾥找 ViewHolder,但需要各种匹配条件,概括⼀下就是只有原来位置的卡位可以复⽤存在 mCachedViews ⾥的 ViewHolder,如果 mCachedViews ⾥没有,那么才去 ViewPool ⾥找。在 ViewPool ⾥的 ViewHolder 都是跟全新的 ViewHolder ⼀样,只要 type ⼀样,有找到,就可以拿出来复⽤,重新绑定下数据即可。3、问题Q1:如果向下滑动,新⼀⾏的5个卡位的显⽰会去复⽤缓存的 ViewHolder,第⼀⾏的5个卡位会移出屏幕被回收,那么在这个过程中,是先进⾏复⽤再回收?还是先回收再复⽤?还是边回收边复⽤?也就是说,新⼀⾏的5个卡位复⽤的 ViewHolder 有可能是第⼀⾏被回收的5个卡位吗?答:先复⽤再回收,新⼀⾏的5个卡位先去⽬前的 mCachedViews 和 ViewPool 的缓存中寻找复⽤,没有就重新创建,然后移出屏幕的那⾏的5个卡位再回收缓存到 mCachedViews 和 ViewPool ⾥⾯,所以新⼀⾏5个卡位和复⽤不可能会⽤到刚移出屏幕的5个卡位。Q2: 向下滑动⼀⾏后再上滑⼀⾏,在这个过程中,为什么当 RecyclerView 再次向上滑动重新显⽰第⼀⾏的5个卡位时,只有后⾯3个卡位触发了 onBindViewHolder() ⽅法,重新绑定数据呢?明明5个卡位都是复⽤的。答:滑动场景下涉及到的回收和复⽤的结构体是 mCachedViews 和 ViewPool,前者默认⼤⼩为2,后者为5。所以,当第三⾏显⽰出来后,第⼀⾏的5个卡位被回收,回收时先缓存在 mCachedViews,满了再移出旧的到 ViewPool ⾥,所以5个卡位有2个缓存在 mCachedViews ⾥,3个缓存在 ViewPool,⾄于是哪2个缓存在 mCachedViews,这是由 LayoutManager 控制。上⾯讲解的例⼦使⽤的是 GridLayoutManager,滑动时的回收逻辑则是在⽗类 LinearLayoutManager ⾥实现,回收第⼀⾏卡位时是从后往前回收(最后回收的两个会放在 mCachedViews),所以最新的两个卡位是0、1,会放在 mCachedViews ⾥,⽽2、3、4的卡位则放在 ViewPool ⾥。所以,当再次向上滑动时,第⼀⾏5个卡位会去两个结构体⾥找复⽤,之前说过,mCachedViews ⾥存放的 ViewHolder 只有原本位置的卡位才能复⽤,所以0、1两个卡位都可以直接去 mCachedViews ⾥拿 ViewHolder 复⽤,⽽且这⾥的 ViewHolder 是不⽤重新绑定数据的,⾄于2、3、4卡位则去 ViewPool ⾥找,刚好 ViewPool ⾥缓存着3个 ViewHolder,所以第⼀⾏的5个卡位都是⽤的复⽤的,⽽从 ViewPool ⾥拿的复⽤需要重新绑定数据,才会这样只有三个卡位需要重新绑定数据。Q3:接下去不管是向上滑动还是向下滑动,滑动⼏次,都不会再有 onCreateViewHolder() 的⽇志了,也就是说 RecyclerView 总共创建了17个 ViewHolder,但有时⼀⾏的5个卡位只有3个卡位需要重新绑定数据,有时却⼜5个卡位都需要重新绑定数据,这是为什么呢?答:有时⼀⾏只有3个卡位需要重新绑定的原因跟Q2⼀样,因为 mCachedView ⾥正好缓存着当前位置的 ViewHolder,本来就是它的 ViewHolder 当然可以直接拿来⽤。⽽⾄于为什么会创建了17个 ViewHolder,那是因为再第四⾏的卡位要显⽰出来时,ViewPool ⾥只有3个缓存,⽽第四⾏的卡位⼜⽤不了 mCachedViews ⾥的2个缓存,因为这两个缓存的是0、1卡位的 ViewHolder,所以就需要再重新创建2个 ViewHodler 来给第四⾏最后的两个卡位使⽤。⼆、1、Adapteradapter 在 ListView 和数据源之间起到了⼀个桥梁的作⽤,ListView 不会直接和数据源打交道,⽽是会借助 Adapter 这个桥梁来去访问真正的数据源2、RecycleBin 机制(eBin)RecycleBin 维护了两种类型列表,⼀个⽤于保存展⽰在屏幕上的View,⼀个⽤于缓存滚动出屏幕的ViewListView滑动过程中,⼦View完全移出屏幕后,会加⼊RecycleBin 缓存⼦View进⼊屏幕时,从RecycleBin 中获取缓存View,⽤于数据绑定。 public abstract class AbsListView extends AdapterView implements ... {

class RecycleBin { private RecyclerListener mRecyclerListener;

//表⽰ListView中第⼀个可见元素的position值 private int mFirstActivePosition;

/* 屏幕上可见的View,存放在 mActiveViews 数组中 * Head Foot,不会存在 mActiveViews ⾥ */ private View[] mActiveViews = new View[0];

/** *离开屏幕后,View会被移动到 mScrapViews 数组中 * mViewTypeCount > 1时,存储该数组中。 * 数组下标为viewType,数组元素为该viewType对应的废弃view列表 */ private ArrayList[] mScrapViews;

/** * ⽤于存储废弃(滚动出屏幕)的View, * * * 只有⼀种数据类型,ViewTypeCount == 1时,存该列表中 */ private ArrayList mCurrentScrap; //布局类型数量 private int mViewTypeCount;

/** * ListView中所有屏幕上可见的View *

* @param childCount 表⽰要存储的view的数量 * @param firstActivePosition 表⽰ListView中第⼀个可见元素的position值 */ void fillActiveViews(int childCount, int firstActivePosition) { if ( < childCount) { mActiveViews = new View[childCount]; } mFirstActivePosition = firstActivePosition; final View[] activeViews = mActiveViews; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); Params lp = (Params) outParams(); if (lp != null && pe != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { //View塞⼊数组中 activeViews[i] = child; ... } } }

/** * 获取 ListView中 position 位置的 View * * mActiveViews当中所存储的View,⼀旦被获取了之后就会从mActiveViews当中移除, * 下次获取同样位置的View将会返回null,也就是说mActiveViews不能被重复利⽤ *

* @param position 表⽰元素在ListView当中的位置 */ View getActiveView(int position) { //将position值转换成mActiveViews数组对应的下标值 int index = position - mFirstActivePosition; final View[] activeViews = mActiveViews; if (index >= 0 && index < ) { //从数组中取出匹配的View final View match = activeViews[index]; //View取出后,删除数组中对应位置的数据 activeViews[index] = null; return match; } return null;//没有找到,返回null }

/** * 将⼀个废弃(滚动出屏幕)的View进⾏缓存 * head foot不会缓存 */ void addScrapView(View scrap) { Params lp = (Params) outParams(); if (lp == null) { return; } // Don't put header or footer views or views that should be ignored // into the scrap heap int viewType = pe; //viewType < 0 时,会返回false if (!shouldRecycleViewType(viewType)) { if (!shouldRecycleViewType(viewType)) { if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { removeDetachedView(scrap, false); } return; } ... if (mViewTypeCount == 1) {//只有⼀种布局类型 dispatchFinishTemporaryDetach(scrap); (scrap);//存到 mCurrentScrap 列表中 } else { dispatchFinishTemporaryDetach(scrap); //多布局,根据viewType存到 mScrapViews 数组中 mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { dToScrapHeap(scrap); } }

/** * 根据position 从废弃缓存中取出⼀个View * 这些废弃View,没有顺序,直接从列表尾部获取⼀个 scrapView 返回 */ View getScrapView(int position) { ArrayList scrapViews; if (mViewTypeCount == 1) {//单布局,从mCurrentScrap从取出末尾的 scrapView返回 scrapViews = mCurrentScrap; int size = (); if (size > 0) { return (size - 1); } else { return null; } } else {//多布局 //根据position获取viewType int whichScrap = mViewType(position); if (whichScrap >= 0 && whichScrap < ) { //根据viewType获取缓存列表 scrapViews = mScrapViews[whichScrap]; int size = (); if (size > 0) { return (size - 1); } } } return null; }

public void setViewTypeCount(int viewTypeCount) { if (viewTypeCount < 1) { throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); } // noinspection unchecked ArrayList[] scrapViews = new ArrayList[viewTypeCount]; for (int i = 0; i < viewTypeCount; i++) { scrapViews[i] = new ArrayList(); } mViewTypeCount = viewTypeCount; mCurrentScrap = scrapViews[0]; mScrapViews = scrapViews; } } }推荐阅读:Android ListView⼯作原理完全解析,带你从源码的⾓度彻底理解 /guolin_blog/article/details/44996879RecyclerView的缓存机制 /wzy_1988/article/details/81569156

发布者:admin,转转请注明出处:http://www.yc00.com/news/1689430243a246955.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信