gpt4 book ai didi

android - RecyclerView 为其项目设置了错误的 MotionLayout 状态

转载 作者:行者123 更新时间:2023-12-04 04:00:09 24 4
gpt4 key购买 nike

首先:我创建了一个示例项目来展示这个问题。到现在为止,我开始认为这是 RecyclerView 或 MotionLayout 中的错误。

https://github.com/muetzenflo/SampleRecyclerView

这个项目的设置与下面描述的略有不同:它使用数据绑定(bind)在 MotionLayout 状态之间切换。但结果是一样的。只需尝试切换状态并在项目之间滑动即可。您迟早会遇到 MotionLayout 状态错误的 ViewHolder。


所以主要问题是:

当从一种 MotionLayout 状态转换到另一种状态时,屏幕外的 ViewHolders 没有正确更新。


所以这就是问题/到目前为止我发现的内容:

我正在使用 RecyclerView。

它只有一种项目类型,即 MotionLayout(因此 RV 的每个项目都是 MotionLayout)。

这个 MotionLayout 有 2 个状态,我们称它们为 State big 和 State small

所有项目都应始终具有相同的状态。因此,每当状态从 big => small 切换时,从那时起所有项目都应该在 small 中。

但是发生的是状态更改为 small 并且大多数(!)项目也正确更新。但是一两个项目总是留在旧状态。我很确定它与回收的 ViewHolders 有关。使用下面的适配器代码(不在示例项目中)时,这些步骤会可靠地产生问题:

  1. 从项目 1 向右滑动到项目 2
  2. big 更改为 small
  3. small 变回 big
  4. 从项目 2 向左滑动到项目 1=> 项目 1 现在处于 small 状态,但应该处于 big 状态

其他发现:

  • 在第 4 步之后,如果我继续向左滑动,还会出现 1 个处于 small 状态的项目(可能是第 4 步中回收的 ViewHolder)。之后没有其他项目是错误的。

  • 从第 4 步开始,我继续滑动几个项目(比如说 10 个),然后一直滑动回来,没有项目再处于错误的 small 状态。错误的回收 ViewHolder 似乎随后得到纠正。

我尝试了什么?

  • 我尝试在转换完成时调用 notifyDataSetChanged()
  • 我尝试保留一组本地创建的 ViewHolders 以直接调用它们的转换
  • 我尝试使用数据绑定(bind)将 motionProgress 设置为 MotionLayout
  • 我尝试设置 viewHolder.isRecycable(true|false) 以在转换期间阻止回收
  • 我搜索了 this great in-depth article about RVs提示接下来要尝试什么

有人遇到过这个问题并找到了好的解决方案吗?

只是为了避免混淆:bigsmall 并不表示我要折叠或展开每个项目!它只是 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)
}
}

最佳答案

我取得了一些进展,但这不是最终解决方案,它还有一些问题需要改进。就像从头到尾的动画不正常,它只是跳到最后的位置。

https://github.com/fmatosqg/SampleRecyclerView/commit/907ec696a96bb4a817df20c78ebd5cb2156c8424

一些我修改过但与解决方案无关但有助于发现问题的东西:

  • 持续时间为 1 秒
  • 回收商 View 中的更多项目
  • recyclerView.setItemViewCacheSize(0) 尝试保留尽可能少的看不见的项目,尽管如果您仔细跟踪它,您就会知道它们往往会留下来
  • 消除了处理转换的数据绑定(bind)。因为我一般不相信 View 持有者,所以我永远无法让它们在没有不良副作用的情况下工作
  • 升级约束库,实现“androidx.constraintlayout:constraintlayout:2.0.0-rc1”

详细了解是什么让它工作得更好:

所有对运动布局的调用都以post方式完成

    //    https://stackoverflow.com/questions/51929153/when-manually-set-progress-to-motionlayout-it-clear-all-constraints
fun safeRunBlock(block: () -> Unit) {

if (ViewCompat.isLaidOut(motionLayout)) {
block()
} else {
motionLayout.post(block)
}

}

实际属性与期望属性的比较

    val goalProgress =
if (currentState) 1f
else 0f
val desiredState =
if (currentState) motionLayout.startState
else motionLayout.endState

safeRunBlock {
startTransition(currentState)
}

if (motionLayout.progress != goalProgress) {

if (motionLayout.currentState != desiredState) {

safeRunBlock {
startTransition(currentState)
}

}
}

这将是部分解决方案的完整类


class DataBindingViewHolder<T>(private val binding: ViewDataBinding) :
RecyclerView.ViewHolder(binding.root) {

val motionLayout: MotionLayout =
binding.root.findViewById<MotionLayout>(R.id.root_item_recycler_view)
.also {
it.setTransitionDuration(1_000)
it.setDebugMode(DEBUG_SHOW_PROGRESS or DEBUG_SHOW_PATH)
}

var lastPosition: Int = -1


fun bind(item: T, position: Int, layoutState: Boolean) {

if (position != lastPosition)
Log.i(
"OnBind",
"Position=$position lastPosition=$lastPosition - $layoutState "
)



lastPosition = position

setMotionLayoutState(layoutState)

binding.setVariable(BR.item, item)
binding.executePendingBindings()
}

// https://stackoverflow.com/questions/51929153/when-manually-set-progress-to-motionlayout-it-clear-all-constraints
fun safeRunBlock(block: () -> Unit) {

if (ViewCompat.isLaidOut(motionLayout)) {
block()
} else {
motionLayout.post(block)
}

}

fun setMotionLayoutState(currentState: Boolean) {

val goalProgress =
if (currentState) 1f
else 0f

safeRunBlock {
startTransition(currentState)
}

if (motionLayout.progress != goalProgress) {

val desiredState =
if (currentState) motionLayout.startState
else motionLayout.endState

if (motionLayout.currentState != desiredState) {
Log.i("Pprogress", "Desired doesn't match at position $lastPosition")
safeRunBlock {
startTransition(currentState)
}
}
}
}

fun startTransition(currentState: Boolean) {
if (currentState) {
motionLayout.transitionToStart()
} else {
motionLayout.transitionToEnd()
}
}

}

编辑:添加约束布局版本

关于android - RecyclerView 为其项目设置了错误的 MotionLayout 状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63173285/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com