RecyclerView 重新创建 ViewHolder 而不是重新绑定
RecyclerView recreating ViewHolder instead of rebinding
基本上我有一个 RecyclerView
聊天消息。场景如下:
- 我添加一条消息 -> 导致
RecyclerView
添加一条新消息 ViewHolder
- 延迟约2秒
- 我更改了第一条消息并添加了一条新消息 -> 应该会导致
RecyclerView
重新绑定第一条消息并为第二条消息创建新的 ViewHolder
。
我的问题出在第 3 步。第一个 ViewHolder
被删除,然后创建一个新的然后绑定。这会产生非常轻微的闪烁,有点烦人,总而言之,这不应该发生。
我正在使用 DiffUtil
来调度更改,整个设置有点复杂,因此很难描述整个画面,但我将其缩小为:
DiffUtil
生成正确的结果并为第一项调用 onItemRangeChanged
,为第二项调用 onItemRangeInserted
。无论出于何种原因,然后删除第一个 ViewHolder 并创建、添加然后绑定一个新的。
是否有人经历过类似的行为或知道为什么会出现这种情况?我试图通过 RecyclerView
代码进行调试,但这是一场噩梦,我仍然不明白整个事情。第一个ViewHolder
无法反弹,需要创建一个新的是什么原因?
如果我能提供更多信息,请提出要求,我会尽力而为。
可以在此处找到显示该问题的小型存储库:https://github.com/MaxGierlachowski/recyclerview_viewholder_bug
如果您查看错误日志并查看第 4 个“MESSAGE”日志,您会看到一个 onChanged 和一个 onInserted 被分派到适配器,但是有一个为 onChanged 创建的新 ViewHolder。我知道 RecyclerView 检查很多事情,比如动画完成等等,但我真的很想知道为什么 ViewHolder 没有反弹而是重新创建。
编辑:
我发现如果 createViewHolder
被 RecyclerView
调用,那么应该重用的 ViewHolder 在 mChangedScrap
列表中并且 mState.isPreLayout()
是 false 所以函数 tryGetViewHolderForPositionByDeadline()
不搜索 mChangedScrap
列表并创建一个新列表。这仍然不应该发生,ViewHolder
甚至没有动画或其他东西,似乎所有动画都在那时完成。
闪烁的小视频:
您的 DiffUtil.Callback
可能有问题。在 documentation 中,有一个方法 areItemsTheSame(T oldItem, T newItem)
基于结果 diff util 可以删除和添加新的 holder 或者只更新现有 holder 的内容。确保您的实施是正确的。
areItemsTheSame -> 例如检查左右支架是否为相同类型的支架。
areContentsTheSame -> 这里你应该检查是否相等。 object1.equals(object2)
嘿,所以我自己想通了,但我仍然不完全理解为什么这是必要的。所以 RecyclerView
里面的 ItemAnimator
决定了一个改变的 ViewHolder 是只是反弹还是重新创建。它将根据以下文章中提到的三个因素来决定。所以基本上因为我使用的是 DiffUtil
我只需要为 onItemRangeChanged
调用提供一些任意性,它会重用 ViewHolder
而不是创建一个新的
正是这篇文章帮助我理解了这个问题:
https://medium.com/android-news/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-continued-d81c631a2b91
基本上我有一个 RecyclerView
聊天消息。场景如下:
- 我添加一条消息 -> 导致
RecyclerView
添加一条新消息ViewHolder
- 延迟约2秒
- 我更改了第一条消息并添加了一条新消息 -> 应该会导致
RecyclerView
重新绑定第一条消息并为第二条消息创建新的ViewHolder
。
我的问题出在第 3 步。第一个 ViewHolder
被删除,然后创建一个新的然后绑定。这会产生非常轻微的闪烁,有点烦人,总而言之,这不应该发生。
我正在使用 DiffUtil
来调度更改,整个设置有点复杂,因此很难描述整个画面,但我将其缩小为:
DiffUtil
生成正确的结果并为第一项调用 onItemRangeChanged
,为第二项调用 onItemRangeInserted
。无论出于何种原因,然后删除第一个 ViewHolder 并创建、添加然后绑定一个新的。
是否有人经历过类似的行为或知道为什么会出现这种情况?我试图通过 RecyclerView
代码进行调试,但这是一场噩梦,我仍然不明白整个事情。第一个ViewHolder
无法反弹,需要创建一个新的是什么原因?
如果我能提供更多信息,请提出要求,我会尽力而为。
可以在此处找到显示该问题的小型存储库:https://github.com/MaxGierlachowski/recyclerview_viewholder_bug
如果您查看错误日志并查看第 4 个“MESSAGE”日志,您会看到一个 onChanged 和一个 onInserted 被分派到适配器,但是有一个为 onChanged 创建的新 ViewHolder。我知道 RecyclerView 检查很多事情,比如动画完成等等,但我真的很想知道为什么 ViewHolder 没有反弹而是重新创建。
编辑:
我发现如果 createViewHolder
被 RecyclerView
调用,那么应该重用的 ViewHolder 在 mChangedScrap
列表中并且 mState.isPreLayout()
是 false 所以函数 tryGetViewHolderForPositionByDeadline()
不搜索 mChangedScrap
列表并创建一个新列表。这仍然不应该发生,ViewHolder
甚至没有动画或其他东西,似乎所有动画都在那时完成。
闪烁的小视频:
您的 DiffUtil.Callback
可能有问题。在 documentation 中,有一个方法 areItemsTheSame(T oldItem, T newItem)
基于结果 diff util 可以删除和添加新的 holder 或者只更新现有 holder 的内容。确保您的实施是正确的。
areItemsTheSame -> 例如检查左右支架是否为相同类型的支架。
areContentsTheSame -> 这里你应该检查是否相等。 object1.equals(object2)
嘿,所以我自己想通了,但我仍然不完全理解为什么这是必要的。所以 RecyclerView
里面的 ItemAnimator
决定了一个改变的 ViewHolder 是只是反弹还是重新创建。它将根据以下文章中提到的三个因素来决定。所以基本上因为我使用的是 DiffUtil
我只需要为 onItemRangeChanged
调用提供一些任意性,它会重用 ViewHolder
而不是创建一个新的
正是这篇文章帮助我理解了这个问题: https://medium.com/android-news/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-continued-d81c631a2b91