RecyclerView 重新创建 ViewHolder 而不是重新绑定

Posted

技术标签:

【中文标题】RecyclerView 重新创建 ViewHolder 而不是重新绑定【英文标题】:RecyclerView recreating ViewHolder instead of rebinding 【发布时间】:2021-05-20 17:59:33 【问题描述】:

基本上我有一个带有聊天消息的RecyclerView。场景如下:

    我添加了一条消息 -> 导致 RecyclerView 添加新的 ViewHolder 延迟约 2 秒 我更改了第一条消息并添加了一条新消息 -> 应该导致 RecyclerView 重新绑定第一条消息并为第二条消息创建一个新的 ViewHolder

我的问题在于第 3 步。因为第一个 ViewHolder 被删除并创建一个新的 ViewHolder 然后绑定。这会产生非常轻微的闪烁,这有点烦人,总而言之,这不应该发生。

我正在使用DiffUtil 来调度更改,整个设置涉及更多一些,因此很难描述整个情况,但我将其缩小为:

DiffUtil 产生正确的结果,并为第一项调用onItemRangeChanged,为第二项调用onItemRangeInserted。无论出于何种原因,第一个 ViewHolder 都会被移除,并创建、添加和绑定新的 on。

是否有人经历过类似的行为或知道为什么会出现这种情况?我试图通过RecyclerView 代码进行调试,但这是一场噩梦,我仍然不明白整个事情。第一个ViewHolder不能被反弹而必须创建一个新的可能是什么原因?

如果我可以提供更多信息,请询问,我会尽力而为。

可以在此处找到显示问题的小型存储库:https://github.com/MaxGierlachowski/recyclerview_viewholder_bug

如果您查看错误日志并查看第 4 个“MESSAGE”日志,您会看到一个 onChanged 和一个 onInserted 被分派到适配器,但是为 onChanged 一个创建了一个新的 ViewHolder。我知道 RecyclerView 会检查很多东西,比如动画完成等等,但我真的很想知道为什么 ViewHolder 没有反弹而是重新创建。

编辑: 我发现如果createViewHolderRecyclerView 调用,则应该重用的ViewHolder 位于mChangedScrap 列表中,而mState.isPreLayout() 为false,因此函数tryGetViewHolderForPositionByDeadline() 不会搜索@987654341 @list 并创建一个新的。但这不应该发生,ViewHolder 甚至没有动画或其他东西,似乎所有动画都在那时完成了。

闪烁的小视频:

【问题讨论】:

【参考方案1】:

嘿,所以我自己想通了,但我仍然不完全理解为什么这是必要的。因此,RecyclerView 中的 ItemAnimator 决定了更改后的 ViewHolder 是重新反弹还是重新创建。它将根据以下文章中提到的三个因素来决定。所以基本上因为我使用的是DiffUtil,所以我只需要为onItemRangeChanged 调用提供一些任意值,它会重用ViewHolder 而不是创建一个新的。

正是这篇文章帮助我理解了这个问题: https://medium.com/android-news/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-continued-d81c631a2b91

【讨论】:

【参考方案2】:

您的DiffUtil.Callback 可能有问题。从documentation 中,有一个方法areItemsTheSame(T oldItem, T newItem) 基于结果diff util 可以删除和添加新的持有人或只是更新现有持有人的内容。确保您的实施是正确的。

areItemsTheSame -> 例如检查左右支架是相同类型的支架。 areContentsTheSame -> 在这里您应该检查是否相等。 object1.equals(object2)

【讨论】:

正如我已经说过的,DiffUtil 只是在适配器上调用 onItemRangeChanged、onItemRangeInserted、...,我已经观察到它为第一项调用 onItemRangeChanged,为第二项调用 onItemRangeInserted。应该是这样的。 这并不容易,因为我不允许发布整个项目,并且与 RecyclerView 相关的代码有很多部分。我将尝试创建一个小项目来重现问题,然后发布并返回这里。 嘿,我刚刚添加了一个显示问题的存储库。

以上是关于RecyclerView 重新创建 ViewHolder 而不是重新绑定的主要内容,如果未能解决你的问题,请参考以下文章

scrollToPosition(),使用片段更新RecyclerView时

深入理解Android RecyclerView的缓存机制

从另一个活动返回后防止recyclerview重新加载

Android:重新绑定 ListView 或 RecyclerView 而不刷新 Header

是否有可能当recyclerview加载项只加载一次而不重新加载滚动?

当 Kotlin 中的数据发生变化时,如何在 RecyclerView 中重新绑定项目?