RecyclerView.Adapter.notifyItemMoved(0,1) 滚动屏幕
Posted
技术标签:
【中文标题】RecyclerView.Adapter.notifyItemMoved(0,1) 滚动屏幕【英文标题】:RecyclerView.Adapter.notifyItemMoved(0,1) scrolls screen 【发布时间】:2015-03-15 13:23:06 【问题描述】:我有一个由LinearlayoutManager
管理的RecyclerView
,如果我用0 交换项目1 然后调用mAdapter.notifyItemMoved(0,1)
,移动动画会导致屏幕滚动。如何预防?
【问题讨论】:
我在 GridLayoutManager 上遇到了同样的问题,scrollToPosition 的接受答案(在移动之后)修复了它! 我遇到了StaggeredGridLayoutManager
的问题,使用GridLayoutManager
解决了这个问题
【参考方案1】:
遗憾的是,yigit 提出的解决方法将RecyclerView
滚动到顶部。这是迄今为止我发现的最好的解决方法:
// figure out the position of the first visible item
int firstPos = manager.findFirstCompletelyVisibleItemPosition();
int offsetTop = 0;
if(firstPos >= 0)
View firstView = manager.findViewByPosition(firstPos);
offsetTop = manager.getDecoratedTop(firstView) - manager.getTopDecorationHeight(firstView);
// apply changes
adapter.notify...
// reapply the saved position
if(firstPos >= 0)
manager.scrollToPositionWithOffset(firstPos, offsetTop);
【讨论】:
干得好!它绝对比@Yigit 建议的要好。竖起大拇指! 完美答案。谢谢。 (y) 难以置信,3.5 年后,漏洞仍然存在。但是,此解决方案效果很好。 我们什么时候需要这样做?搬家之后还是搬家之前?? @Andreas Wenger,这段代码是否应该在 onMove() 方法中实现?我无法让它工作......【参考方案2】:移动物品后致电scrollToPosition(0)
。不幸的是,我假设,LinearLayoutManager 试图保持第一个项目稳定,它移动所以它移动列表。
【讨论】:
虽然这解决了我的问题,但看看这个code.google.com/p/android/issues/detail?id=99047 感谢您的报告。我们会修复它。带来不便敬请谅解。幸运的是,有一个相对简单的解决方法。顺便说一句,scrollToPosition 只是将视图带到可见视口,因此即使您没有移动第一项,也可以一直调用它。 谢谢你!我使用 ItemTouchHelper 来拖动项目,并且在将第一个项目拖动到第二个项目时遇到了问题。因为滚动。花了 3 天时间弄清楚出了什么问题!!! 我遇到了同样的问题。我尝试在 onMove() 方法和 onChildDraw() 方法中调用 layoutManager 上的 scrollToPosition(0),但无法使其正常工作。我应该在哪里实现此代码?【参考方案3】:翻译@Andreas Wenger 对 kotlin 的回答:
val firstPos = manager.findFirstCompletelyVisibleItemPosition()
var offsetTop = 0
if (firstPos >= 0)
val firstView = manager.findViewByPosition(firstPos)!!
offsetTop = manager.getDecoratedTop(firstView) - manager.getTopDecorationHeight(firstView)
// apply changes
adapter.notify...
if (firstPos >= 0)
manager.scrollToPositionWithOffset(firstPos, offsetTop)
在我的情况下,视图可以有一个上边距,这也需要计入偏移量,否则recyclerview不会滚动到预期的位置。为此,只需编写:
val topMargin = (firstView.layoutParams as? MarginLayoutParams)?.topMargin ?: 0
offsetTop = manager.getDecoratedTop(firstView) - manager.getTopDecorationHeight(firstView) - topMargin
如果你的项目中有 ktx 依赖,那就更容易了:
offsetTop = manager.getDecoratedTop(firstView) - manager.getTopDecorationHeight(firstView) - firstView.marginTop
【讨论】:
【参考方案4】:我也遇到过同样的问题。建议没有任何帮助。每个解决方案都修复和打破了不同的情况。 但是这种解决方法对我有用:
adapter.registerAdapterDataObserver(object: RecyclerView.AdapterDataObserver()
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int)
if (fromPosition == 0 || toPosition == 0)
binding.recycler.scrollToPosition(0)
)
它有助于防止在移动第一个项目时滚动案例:直接 notifyItemMoved 和通过 ItemTouchHelper(拖放)
【讨论】:
【参考方案5】:我也遇到过同样的问题。就我而言,滚动发生在第一个可见项目上(不仅在数据集中的第一个项目上)。我要感谢大家,因为他们的回答帮助我解决了这个问题。 我基于Andreas Wenger' answer 和resoluti0n' answer 启发了我的解决方案
而且,这是我的解决方案(在 Kotlin 中):
RecyclerViewOnDragFistItemScrollSuppressor.kt
class RecyclerViewOnDragFistItemScrollSuppressor private constructor(
lifecycleOwner: LifecycleOwner,
private val recyclerView: RecyclerView
) : LifecycleObserver
private val adapterDataObserver = object : RecyclerView.AdapterDataObserver()
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int)
suppressScrollIfNeeded(fromPosition, toPosition)
init
lifecycleOwner.lifecycle.addObserver(this)
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun registerAdapterDataObserver()
recyclerView.adapter?.registerAdapterDataObserver(adapterDataObserver) ?: return
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun unregisterAdapterDataObserver()
recyclerView.adapter?.unregisterAdapterDataObserver(adapterDataObserver) ?: return
private fun suppressScrollIfNeeded(fromPosition: Int, toPosition: Int)
(recyclerView.layoutManager as LinearLayoutManager).apply
var scrollPosition = -1
if (isFirstVisibleItem(fromPosition))
scrollPosition = fromPosition
else if (isFirstVisibleItem(toPosition))
scrollPosition = toPosition
if (scrollPosition == -1) return
scrollToPositionWithCalculatedOffset(scrollPosition)
companion object
fun observe(
lifecycleOwner: LifecycleOwner,
recyclerView: RecyclerView
): RecyclerViewOnDragFistItemScrollSuppressor
return RecyclerViewOnDragFistItemScrollSuppressor(lifecycleOwner, recyclerView)
private fun LinearLayoutManager.isFirstVisibleItem(position: Int): Boolean
apply
return position == findFirstVisibleItemPosition()
private fun LinearLayoutManager.scrollToPositionWithCalculatedOffset(position: Int)
apply
val offset = findViewByPosition(position)?.let
getDecoratedTop(it) - getTopDecorationHeight(it)
?: 0
scrollToPositionWithOffset(position, offset)
然后,您可以将其用作(例如片段):
RecyclerViewOnDragFistItemScrollSuppressor.observe(
viewLifecycleOwner,
binding.recyclerView
)
【讨论】:
【参考方案6】:LinearLayoutManager 在LinearLayoutManager.prepareForDrop
中为您完成了这项工作。
您只需要提供移动(旧)视图和目标(新)视图。
layoutManager.prepareForDrop(oldView, targetView, -1, -1)
// the numbers, x and y don't matter to LinearLayoutManager's implementation of prepareForDrop
这是一个“非官方”API,因为它在源代码中声明
// This method is only intended to be called (and should only ever be called) by
// ItemTouchHelper.
public void prepareForDrop(@NonNull View view, @NonNull View target, int x, int y)
...
但它仍然有效并且完全按照其他答案所说的那样进行,为您完成所有考虑布局方向的偏移计算。
这实际上与 LinearLayoutManager 在被 ItemTouchHelper
用来解决这个可怕的错误时调用的方法相同。
【讨论】:
嗨@Bassam Helal,你在哪里调用这个方法?我无法让它工作。我尝试了 onMove 方法,其中“oldView”和“targetView”作为参数传递......以上是关于RecyclerView.Adapter.notifyItemMoved(0,1) 滚动屏幕的主要内容,如果未能解决你的问题,请参考以下文章