Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView.Adapter.notifyItemMoved(0,1) scrolls screen

I have a RecyclerView managed by a LinearlayoutManager, if I swap item 1 with 0 and then call mAdapter.notifyItemMoved(0,1), the moving animation causes the screen to scroll. How can I prevent it?

like image 338
Ari Avatar asked Jan 16 '15 20:01

Ari


4 Answers

Sadly the workaround presented by yigit scrolls the RecyclerView to the top. This is the best workaround I found till now:

// 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);
}
like image 187
Andreas Wenger Avatar answered Nov 06 '22 09:11

Andreas Wenger


Call scrollToPosition(0) after moving items. Unfortunately, i assume, LinearLayoutManager tries to keep first item stable, which moves so it moves the list with it.

like image 22
yigit Avatar answered Nov 06 '22 11:11

yigit


Translate @Andreas Wenger's answer to 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)
}

In my case, the view can have a top margin, which also needs to be counted in the offset, otherwise the recyclerview will not scroll to the intended position. To do so, just write:

val topMargin = (firstView.layoutParams as? MarginLayoutParams)?.topMargin ?: 0
offsetTop = manager.getDecoratedTop(firstView) - manager.getTopDecorationHeight(firstView) - topMargin

Even easier if you have ktx dependency in your project:

offsetTop = manager.getDecoratedTop(firstView) - manager.getTopDecorationHeight(firstView) - firstView.marginTop
like image 3
xiaoyu Avatar answered Nov 06 '22 10:11

xiaoyu


I've faced the same problem. Nothing of the suggested helped. Each solution fix and breakes different cases. But this workaround worked for me:

    adapter.registerAdapterDataObserver(object: RecyclerView.AdapterDataObserver() {
        override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
            if (fromPosition == 0 || toPosition == 0)
                binding.recycler.scrollToPosition(0)
        }
    })

It helps to prevent scrolling while moving the first item for cases: direct notifyItemMoved and via ItemTouchHelper (drag and drop)

like image 3
resoluti0n Avatar answered Nov 06 '22 11:11

resoluti0n