RecycledViewPool
我们想回答有关每种缓存的一些问题:它的支持数据结构是什么,在什么条件下将ViewHolders存储在那里并从那里检索,最重要的是,它的目的是什么。
您可能非常了解池的用途:例如向下滚动时,顶部消失的视图将被回收到池中,以重新使用从底部出现的视图。我们将在稍后讨论ViewHolders进入池的其他场景。但是首先让我们看一下RecycledViewPool的一些代码(RecycledViewPool是RecyclerView.Recycler的内部类):
public static RecycledViewPool {
private SparseArray <ArrayList <ViewHolder >> mScrap = new SparseArray <>();
private SparseIntArray mMaxScrap = new SparseIntArray();
…
public ViewHolder getRecycledView(int viewType){
ArrayList <ViewHolder> scrapHeap = mScrap.get(viewType);
…
首先,不要让名称mScrap混淆您-与上面列表中提到的**changed scrap 和 ****attached scrap**无关。我们看到每种视图类型都有自己的ViewHolders池。如果在搜索ViewHolder的过程中RecyclerView用尽了所有其他可能性,它将要求池提供具有正确视图类型的ViewHolder;那时,视图类型是唯一重要的事情。现在,每种视图类型都有其自身的功能。默认情况下为**5**,但是您可以像这样更改它:
recyclerView.getRecycledViewPool()
.setMaxRecycledViews(SOME_VIEW_TYPE,POOL_CAPACITY);
这是非常重要的灵活性。如果屏幕上有数十个相同类型的项目,这些项目经常同时更改,请为该视图类型增大池。而且,如果您知道某些视图类型的项目非常稀有,以至于它们在屏幕上显示的数量永远不会超过一个,则请为该视图类型设置池大小1。否则,迟早池中将充满其中的5个项目,而其中4个项目只会闲置在那儿,这会浪费内存。该方法`getRecyclerView(),``putRecycledView(),``clear()`是公开的,所以你可以操纵池的内容。但是putRecycledView()
手动
使用(例如为了事先准备一些ViewHolders)不是一个好主意。您只能在onCreateViewHolder()适配器的方法中创建ViewHolder,否则ViewHolders可能会RecyclerView所不希望的状态出现。
[²](https://android.jlelse.eu/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714#fe2a)
另一个很酷的功能是,与getter一起,getRecycledViewPool()还有一个setter setRecycledViewPool(),因此您可以将单个池重用于多个RecycleViews。最后,我会注意到每种视图类型的池都像堆栈一样工作(后进先出)。稍后我们将了解为什么这很好。
现在让我们解决将ViewHolders扔到池中的问题。有5种情况:
-
在滚动过程中,视图超出了RecyclerView的范围。
-
数据已更改,因此视图不再可见。消失动画结束时,会添加到池中。
-
**view cache**中的项目已更新或删除。
-
在搜索ViewHolder时,在**scrap**或**cache**中找到了我们想要的位置,但由于视图类型或ID错误(如果适配器具有稳定的ID),结果并不是我们想要的。 [
](https://android.jlelse.eu/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714#8b86)
-
LayoutManager在布**pre-layout**添加了一个视图,但没有在**post-layout**添加该视图。
前两种情况非常明显。但是,要注意的一件事是,场景2不仅通过删除有问题的项目来触发,而且还通过例如插入其他项目来触发,这将给定项目推出了界限。
需要着重讨论其他情况。我们还没有介绍view cache和废料,但是场景3和4背后的想法很简单。池是我们知道“脏”的视图的地方,需要重新绑定。除池外,所有缓存中的ViewHolders保留其某些状态(最重要的是位置)。所有这些缓存均按位置进行搜索,希望某些ViewHolder可以原样重用。相反,当视图进入池时,其状态(所有标志,位置等)被清除。剩下的唯一内容是关联的视图和视图类型。众所周知,池是根据视图类型进行搜索的,当在其中找到ViewHolder时,它将重获新生。
鉴于上述情况,场景3和4不应是个谜:例如,如果我们看到**view cache**中的一项已被删除,则不再将其保存在该缓存中是没有意义的,因为它不会被重复使用。 -是相同的位置。但是,将其完全丢弃并不是很好,因此我们将其丢弃到池中。
最后一种情况要求我们知道什么是**pre-layout** 和 **post-layout**。好吧,让我们继续进行下去吧!这种情况绝对不是**pre-layout**/**post-layout**机制中最关键的方面,但是该机制通常非常重要,并且在RecyclerView的每个部分中都得到体现,因此无论如何我们都必须知道这一点。
主题:pre-layout,post-layout和predictive animations
考虑一个场景,其中我们有项目a,b和c,其中a和b恰好占满屏幕。我们删除b,使c出现在视图中:

我们希望看到的是c从底部到新位置的平滑滑动。但是那怎么可能呢?我们从新布局中知道了c的最终位置,但是我们如何知道它应该从哪里滑动呢?RecyclerView或ItemAnimator仅仅通过查看新布局认为c应该来自底部是错误的。我们可能有一些自定义LayoutManager,它可以来自侧面或别的地方。因此,我们需要来自LayoutManager的更多帮助。我们可以使用以前的布局吗?不,那里没有с。在没有c的情况下b将会被删除,因此LayoutManager正确地考虑布局c是浪费资源。
Google提供的解决方案如下。适配器发生更改后,RecyclerView不是从LayoutManager请求一个布局,而是请求两个布局。第一个是pre-layout,用于布局处于先前适配器状态的项目,但是使用适配器更改来暗示布局一些额外的视图可能是一个好主意。在我们的示例中,由于我们现在知道b已被删除,因此我们另外对c进行了布局,尽管事实超出了范围。第二个布局-后布局,只是与更改后的适配器状态相对应的常规布局。

现在,通过比较c在**pre-layout**和**post-layout**的位置,我们可以对其外观进行适当的动画处理。这种动画-当动画视图既不在以前的布局中也不在新布局中时-称为预测动画,这是RecyclerView中最重要的概念之一。我们将在本系列的后续部分中对其进行更详细的讨论。但是,现在让我们快速看一下另一个示例:如果b被更改而不是被删除怎么办?

这可能令人惊讶,但是LayoutManager仍在**pre-layout**阶段对c进行布局。为什么?因为也许b的改变会使它的高度变小,谁知道呢?而且,如果b变小,则c可能会从底部弹出,因此我们最好将其布置在**pre-layout**中。但是后来,在**post-layout**,情况似乎并非如此,比如说我们只是在b内更改了一些TextView。因此,不需要c,并将其扔到池中。这就是让自己进入游泳池的场景5。希望现在已经很清楚了,我们可以回到RecycledViewPool。
RecycledViewPool,继续
当我们遇到ViewHolder应该进入池中的一种情况时,那里仍然有两个障碍:它可能不可回收,并且View可能处于**Transient state**。
可回收性只是ViewHolder中的一个标志,您可以使用ViewHolder类的方法`setIsRecyclable()`进行操作。RecycleView也使用它,从而使ViewHolders在动画过程中不可回收。
从不同的独立位置操作单个标志通常是一个坏主意。例如,当动画结束时,RecyclerView会调用`setIsRecyclable(true)`,因为您不希望由于特定于应用程序的某种原因而使其可回收。但是在这种情况下,事情实际上并不会中断,因为对的调用setIsRecyclable()是成对的。也就是说,如果您调用setIsRecyclable(false)两次,则setIsRecyclable(true)仅调用一次不会使ViewHolder可回收,因此您也需要调用两次。
**Transient state**
视图的Transient state非常相似。这是View中的一个标志,由setHasTransientState()方法操作,并且调用也成对配对。View类本身不使用标志,而仅保留它。它为诸如ListView和RecyclerView之类的小部件提供了提示,最好不要在当前将此视图重用于新内容。您可以自己设置此标志,但ViewPropertyAnimator(也就是您在操作时someView.animate()…)会在动画开始时自动将其设置为true,并在动画结束时将其设置为false。⁴
请注意,例如,如果使用ValueAnimator对视图进行动画处理,则必须自己管理过渡状态。
关于**Transient state**的最后一件事要注意的是,它从孩子传播到父母,一直到根视图为止。因此,如果为列表中某个项目的某些内部视图设置动画,则不仅该视图而且ViewHolder保留引用的该项目的根视图都将进入**Transient state**
**OnFailedToRecycleView**
如果要回收的ViewHolder未能通过可回收性或**Transient state**检查,则将调用适配器的`onFailedToRecycleView()`方法。现在,这是非常重要的一点:这种方法不仅是事件的通知,而且还问您如何处理这种情况的问题。`onFailedToRecycledView()`从返回true 表示“无论如何都要回收”。一种合适的情况是,在绑定新项目时 清除所有动画和此问题的其他来源。此时您可以在onFailedToRecycledView()方法中正确处理这些事情。
您不应该做的就是onFailedToRecycledView()完全忽略。以下是可能会伤害您的一种情况。
想象一下,当您看到项目中的图像时,它们正在褪色。如果用户滚动列表的速度足够快,则当它们消失时,图像将不会完全消失,从而使ViewHolders不符合回收条件。因此,您的滚动会很慢,最重要的是,将创建新的和新的ViewHolders,从而使内存混乱。
成功回收ViewHolder会导致调用onViewRecycled()方法,这是释放大量资源(例如图像)的好地方。请记住,某些ViewHolder实例可能会长时间不使用而坐在池中,这可能会浪费大量内存。现在,我们进入下一个缓存-**View Cache**。