RecyclerView.Adapter.notifyItemMoved(0,1)滚动屏幕

时间:2015-01-16 20:40:17

标签: android android-adapter android-recyclerview

我有一个由LinearlayoutManager管理的RecyclerView,如果我将项目1与0交换然后调用mAdapter.notifyItemMoved(0,1),则移动动画会导致屏幕滚动。我该如何预防?

6 个答案:

答案 0 :(得分:6)

令人遗憾的是,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);
}

答案 1 :(得分:5)

移动项目后调用scrollToPosition(0)。不幸的是,我假设,LinearLayoutManager尝试保持第一个项目稳定,这会移动,因此它会随之移动列表。

答案 2 :(得分: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

答案 3 :(得分:0)

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) {
    ...
}

但是它仍然可以正常工作,并且完全按照其他答案所说的进行,所有的偏移量计算都可以为您说明布局方向。

这实际上是由ItemTouchHelper用来解决此可怕错误时由LinearLayoutManager调用的方法。

答案 4 :(得分:0)

我遇到了同样的问题。任何建议都没有帮助。每个解决方案都会修复和破坏不同的情况。 但这种解决方法对我有用:

    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 :(得分:0)

我遇到了同样的问题。就我而言,滚动发生在第一个可见项目上(不仅在数据集中的第一个项目上)。我要感谢大家,因为他们的回答帮助我解决了这个问题。 我根据 Andreas Wenger' answerresoluti0n' 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
        )