【发布时间】:2020-11-20 04:56:01
【问题描述】:
首先:我创建了一个显示此问题的示例项目。现在我开始认为这是 RecyclerView 或 MotionLayout 中的错误。
https://github.com/muetzenflo/SampleRecyclerView
这个项目的设置与下面描述的有点不同:它使用数据绑定在 MotionLayout 状态之间切换。但结果是一样的。只需切换状态并在项目之间滑动即可。迟早您会遇到具有错误 MotionLayout 状态的 ViewHolder。
所以主要问题是:
当从一种 MotionLayout 状态转换到另一种状态时,屏幕外的 ViewHolders 不会正确更新。
这就是问题所在/到目前为止我发现了什么:
我正在使用 RecyclerView。
它只有一种项目类型,即 MotionLayout(因此 RV 的每个项目都是 MotionLayout)。
这个 MotionLayout 有 2 个状态,我们称它们为 State big 和 State small
所有项目都应始终具有相同的状态。因此,每当状态从 big => small 切换时,从那时起所有项目都应该在 small 中。
但是发生的情况是状态更改为small,并且大多数(!)项目也正确更新。但是旧状态总是留下一两个项目。我很确定这与回收的 ViewHolders 有关。当使用下面的适配器代码(不在示例项目中)时,这些步骤会可靠地产生问题:
- 从第 1 项向右滑动到第 2 项
- 从
big更改为small - 从
small改回big - 从第 2 项向左滑动到第 1 项
=> 项目 1 现在处于
small状态,但应该处于big状态
其他发现:
-
在第 4 步之后,如果我继续向左滑动,还会有 1 个处于
small状态的项目(可能是第 4 步中回收的 ViewHolder)。之后没有其他项目是错误的。 -
从第 4 步开始,我继续滑动一些项目(比如说 10 个),然后一直向后滑动,不再有任何项目处于错误的
small状态。错误的回收 ViewHolder 似乎随后被纠正。
我尝试了什么?
- 我尝试在转换完成时致电
notifyDataSetChanged() - 我尝试保留一组本地已创建的 ViewHolders 以直接在它们上调用转换
- 我尝试使用数据绑定将
motionProgress设置为 MotionLayout - 我尝试将
viewHolder.isRecycable(true|false)设置为在过渡期间阻止回收 - 我搜索了 this great in-depth article about RVs 以获取下一步尝试的提示
有人遇到过这个问题并找到了好的解决方案吗?
只是为了避免混淆:big 和small 并不表示我要折叠或展开每个项目!它只是motionlayouts子项的不同排列的名称。
class MatchCardAdapter() : DataBindingAdapter<Match>(DiffCallback, clickListener) {
private val viewHolders = ArrayList<RecyclerView.ViewHolder>()
private var direction = Direction.UNDEFINED
fun setMotionProgress(direction: MatchCardViewModel.Direction) {
if (this.direction == direction) return
this.direction = direction
viewHolders.forEach {
updateItemView(it)
}
}
private fun updateItemView(viewHolder: RecyclerView.ViewHolder) {
if (viewHolder.adapterPosition >= 0) {
val motionLayout = viewHolder.itemView as MotionLayout
when (direction) {
Direction.TO_END -> motionLayout.transitionToEnd()
Direction.TO_START -> motionLayout.transitionToStart()
Direction.UNDEFINED -> motionLayout.transitionToStart()
}
}
}
override fun onBindViewHolder(holder: DataBindingViewHolder<Match>, position: Int) {
val item = getItem(position)
holder.bind(item, clickListener)
val itemView = holder.itemView
if (itemView is MotionLayout) {
if (!viewHolders.contains(holder)) {
viewHolders.add(holder)
}
updateItemView(holder)
}
}
override fun onViewRecycled(holder: DataBindingViewHolder<Match>) {
if (holder.adapterPosition >= 0 && viewHolders.contains(holder)) {
viewHolders.remove(holder)
}
super.onViewRecycled(holder)
}
}
【问题讨论】:
-
你能分享你的 onBindViewHolderMethod 吗?
-
@Rinat 我添加了适配器的重要部分。这是我目前的实现。
-
我花了几个小时尝试不同的方法,但都失败了。也许,您应该开始寻找 MotionLayout 的替代方案来实现您的状态。
-
谢谢,我走了同样的路 :) 我可能很快会在 google tracker 中发布问题。
-
我在 google tracker 上创建了一个问题:issuetracker.google.com/issues/162811653
标签: android android-recyclerview android-motionlayout