RecyclerView.Adapter.notifyItemMoved(0,1) 滚动屏幕
RecyclerView.Adapter.notifyItemMoved(0,1) scrolls screen
我有一个由 LinearlayoutManager
管理的 RecyclerView
,如果我将项目 1 与 0 交换然后调用 mAdapter.notifyItemMoved(0,1)
,移动动画会导致屏幕滚动。我该如何预防?
移动项目后调用 scrollToPosition(0)
。不幸的是,我假设 LinearLayoutManager 试图保持第一个项目稳定,它移动所以它移动列表。
遗憾的是,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);
}
翻译@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
LinearLayoutManager 已在 LinearLayoutManager.prepareForDrop
.
中为您完成此操作
您只需提供移动(旧)视图和目标(新)视图。
layoutManager.prepareForDrop(oldView, targetView, -1, -1)
// the numbers, x and y don't matter to LinearLayoutManager's implementation of prepareForDrop
它是一个 "unofficial" 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
用来解决这个可怕的错误时调用的方法相同。
我遇到过同样的问题。建议没有任何帮助。每个解决方案修复和打破不同的情况。
但这个解决方法对我有用:
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(拖放)
我遇到了同样的问题。在我的例子中,滚动发生在第一个可见项目上(不仅在数据集中的第一个项目上)。我要感谢大家,因为他们的回答帮助我解决了这个问题。
我基于 and from
启发了我的解决方案
而且,这是我的解决方案(在 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
)
我有一个由 LinearlayoutManager
管理的 RecyclerView
,如果我将项目 1 与 0 交换然后调用 mAdapter.notifyItemMoved(0,1)
,移动动画会导致屏幕滚动。我该如何预防?
移动项目后调用 scrollToPosition(0)
。不幸的是,我假设 LinearLayoutManager 试图保持第一个项目稳定,它移动所以它移动列表。
遗憾的是,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);
}
翻译@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
LinearLayoutManager 已在 LinearLayoutManager.prepareForDrop
.
您只需提供移动(旧)视图和目标(新)视图。
layoutManager.prepareForDrop(oldView, targetView, -1, -1)
// the numbers, x and y don't matter to LinearLayoutManager's implementation of prepareForDrop
它是一个 "unofficial" 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
用来解决这个可怕的错误时调用的方法相同。
我遇到过同样的问题。建议没有任何帮助。每个解决方案修复和打破不同的情况。 但这个解决方法对我有用:
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(拖放)
我遇到了同样的问题。在我的例子中,滚动发生在第一个可见项目上(不仅在数据集中的第一个项目上)。我要感谢大家,因为他们的回答帮助我解决了这个问题。
我基于
而且,这是我的解决方案(在 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
)