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

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);
}

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.


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