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?
-
I had the same issue with the GridLayoutManager, and the accepted answer of scrollToPosition (after the move) fixed it! – Matt Jun 06 '17 at 17:57
-
I had issues with `StaggeredGridLayoutManager`, using `GridLayoutManager` solved the issue – Abu Sufian Feb 09 '22 at 00:32
6 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);
}

- 4,310
- 1
- 22
- 31
-
1Nice work! It is definitely better than the one suggested by @Yigit. Thumbs up! – Shahood ul Hassan Nov 10 '18 at 15:20
-
-
5Unbelievable, 3.5 years later and the bug is still there. However, this solution works perfect. – Miloš Černilovský Dec 05 '19 at 13:33
-
When we need to do this? After move or before move?? – Vijayadhas Chandrasekaran Jan 29 '21 at 18:40
-
@Andreas Wenger, is this code supposed to be implemented in the onMove() method? I am not able to get it to work... – gig6 Jan 30 '21 at 21:43
-
@gig6 I don't know of any `onMove` method in the `RecyclerView` or in the `RecyclerView.Adapter`. The solution consists of 3 steps (marked with the comments). The first section saves the current scroll position. The second section applies changes to the `RecyclerView` through the adapter. The `adapter` in the code is documented here: https://developer.android.com/reference/kotlin/androidx/recyclerview/widget/RecyclerView.Adapter . The last section restores the scroll state that was saved in section 1. – Andreas Wenger Feb 11 '21 at 10:54
-
@AndreasWenger I'm using the [ItemTouchHelper](https://developer.android.com/reference/androidx/recyclerview/widget/ItemTouchHelper) class that contains the `onMove` method. This adds swipe to dismiss and drag and drop support to the `RecyclerView`. Is there another method that allows these functionalities without `ItemTouchHelper`? – gig6 Feb 12 '21 at 21:09
-
@gig6 this answer's code should go wherever you are doing the swapping of two items. In your case, you are using `ItemTouchHelper` so that swap is happening in `onMove`, so it should go in `onMove`. Sadly I'm having the same issue as you, it doesn't work for me. – Chrispher Jul 01 '21 at 11:29
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.

- 37,683
- 13
- 72
- 58
-
Although this solved my problem, take a look at this https://code.google.com/p/android/issues/detail?id=99047 – Ari Jan 18 '15 at 03:13
-
Thanks for the report. We'll fix it. Sorry for the inconvenience. Luckily, there is a relatively easy workaround. Btw, scrollToPosition just brings the view to visible viewport, so it is safe to call it all the time, even if you are not moving the first item. – yigit Jan 18 '15 at 04:06
-
Thanks man ! I used ItemTouchHelper for dragging items and had issues when dragging first item to the second. Because of scroll. Lost 3 days figuring out what was wrong !!! – Misha Akopov Mar 17 '16 at 13:10
-
I am having the same issue. I've tried calling scrollToPosition(0) on my layoutManager in the onMove() method and in the onChildDraw() method but can't get it to work. Where should I be implementing this code? – gig6 Jan 29 '21 at 17:57
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

- 2,369
- 2
- 13
- 15
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)

- 31
- 2
I have faced the same problem. In my case, the scroll happens on the first visible item (not only on the first item in the dataset). And I would like to thanks everybody because their answers help me to solve this problem. I inspire my solution based on Andreas Wenger' answer and from resoluti0n' answer
And, here is my solution (in 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)
}
}
and then, you may use it as (e.g. fragment):
RecyclerViewOnDragFistItemScrollSuppressor.observe(
viewLifecycleOwner,
binding.recyclerView
)

- 11
- 1
- 5
LinearLayoutManager has done this for you in LinearLayoutManager.prepareForDrop
.
All you need to provide is the moving (old) View and the target (new) View.
layoutManager.prepareForDrop(oldView, targetView, -1, -1)
// the numbers, x and y don't matter to LinearLayoutManager's implementation of prepareForDrop
It's an "unofficial" API because it states in the source
// 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) {
...
}
But it still works and does exactly what the other answers say, doing all the offset calculations accounting for layout direction for you.
This is actually the same method that is called by LinearLayoutManager when used by an ItemTouchHelper
to account for this dreadful bug.

- 380
- 3
- 12
-
Hi @Bassam Helal, where do you call this method? I can't get it to work. I tried inside the onMove method where the "oldView" and "targetView" are passed as arguments... – gig6 Jan 29 '21 at 21:49