178

On a RecyclerView, I am able to suddenly scroll to the top of a selected item by using:

((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(position, 0); 

However, this abruptly moves the item to the top position. I want to move to the top of an item smoothly.

I've also tried:

recyclerView.smoothScrollToPosition(position); 

but it does not work well as it does not move the item to the position selected to the top. It merely scrolls the list until the item on the position is visible.

0

16 Answers 16

332

RecyclerView is designed to be extensible, so there is no need to subclass the LayoutManager (as droidev suggested) just to perform the scrolling.

Instead, just create a SmoothScroller with the preference SNAP_TO_START:

RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(context) { @Override protected int getVerticalSnapPreference() { return LinearSmoothScroller.SNAP_TO_START; } }; 

Now you set the position where you want to scroll to:

smoothScroller.setTargetPosition(position); 

and pass that SmoothScroller to the LayoutManager:

layoutManager.startSmoothScroll(smoothScroller); 
Sign up to request clarification or add additional context in comments.

16 Comments

Thanks for this shorter solution. Just two more things I had to consider in my implementation: As I have a horizontal scrolling view I had to set protected int getHorizontalSnapPreference() { return LinearSmoothScroller.SNAP_TO_START; }. Furthermore I had to implement the abstract method public PointF computeScrollVectorForPosition(int targetPosition) { return layoutManager.computeScrollVectorForPosition(targetPosition); }.
RecyclerView may be designed to be extensible, but a simple thing like this just feels lazy to be missing. Good answer tho! Thank you.
Is there any way to let it snap to start, but with an offset of X dp?
is it possible to slow down its speed?
Also wondering if the speed at which the scroll occurs can be modified (slower) @AlirezaNoorali
|
121

for this you have to create a custom LayoutManager

public class LinearLayoutManagerWithSmoothScroller extends LinearLayoutManager { public LinearLayoutManagerWithSmoothScroller(Context context) { super(context, VERTICAL, false); } public LinearLayoutManagerWithSmoothScroller(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } @Override public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext()); smoothScroller.setTargetPosition(position); startSmoothScroll(smoothScroller); } private class TopSnappedSmoothScroller extends LinearSmoothScroller { public TopSnappedSmoothScroller(Context context) { super(context); } @Override public PointF computeScrollVectorForPosition(int targetPosition) { return LinearLayoutManagerWithSmoothScroller.this .computeScrollVectorForPosition(targetPosition); } @Override protected int getVerticalSnapPreference() { return SNAP_TO_START; } } } 

use this for your RecyclerView and call smoothScrollToPosition.

example :

 recyclerView.setLayoutManager(new LinearLayoutManagerWithSmoothScroller(context)); recyclerView.smoothScrollToPosition(position); 

this will scroll to top of the RecyclerView item of specified position.

11 Comments

I had trouble implementing the suggested LayoutManager. This answer here worked much easier for me: stackoverflow.com/questions/28025425/…
Only useful answer after hours of pain, this works as designed!
@droidev I've used your example with smooth scrolling and generally SNAP_TO_START is what I need but it works with some issues for me. Sometimes scrolling stops 1,2,3 or 4 positions earlier that I pass to smoothScrollToPosition. Why this problem may occur? Thanks.
can you somehow reach your code to us ? I'm pretty sure it is your code issue.
This is the correct answer despite the accepted one
|
71

This is an extension function I wrote in Kotlin to use with the RecyclerView (based on @Paul Woitaschek answer):

fun RecyclerView.smoothSnapToPosition(position: Int, snapMode: Int = LinearSmoothScroller.SNAP_TO_START) { val smoothScroller = object : LinearSmoothScroller(this.context) { override fun getVerticalSnapPreference(): Int = snapMode override fun getHorizontalSnapPreference(): Int = snapMode } smoothScroller.targetPosition = position layoutManager?.startSmoothScroll(smoothScroller) } 

Use it like this:

myRecyclerView.smoothSnapToPosition(itemPosition) 

6 Comments

This works! The problem with smooth scroll is when you have large lists and then scrolling through to the bottom or top takes a long, long time
@vovahost how can I center the desired position?
@ysfcyln Check this stackoverflow.com/a/39654328/1502079 answer
Thanks @vovahost you have saved me from suicide. Hahahahaha
Excelent solution
|
16

We can try like this

 recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView,new RecyclerView.State(), recyclerView.getAdapter().getItemCount()); 

Comments

9

i Used Like This :

recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView, new RecyclerView.State(), 5); 

2 Comments

Most simplest one
this method works, however if it's a very long recyclerView, the scroll takes time
8

Override the calculateDyToMakeVisible/calculateDxToMakeVisible function in LinearSmoothScroller to implement the offset Y/X position

override fun calculateDyToMakeVisible(view: View, snapPreference: Int): Int { return super.calculateDyToMakeVisible(view, snapPreference) - ConvertUtils.dp2px(10f) } 

1 Comment

This is what I was looking for. Thanks for sharing this for implementation the offset position of x/y :)
7

I want to more fully address the issue of scroll duration, which, should you choose any earlier answer, will in fact will vary dramatically (and unacceptably) according to the amount of scrolling necessary to reach the target position from the current position .

To obtain a uniform scroll duration the velocity (pixels per millisecond) must account for the size of each individual item - and when the items are of non-standard dimension then a whole new level of complexity is added.

This may be why the RecyclerView developers deployed the too-hard basket for this vital aspect of smooth scrolling.

Assuming that you want a semi-uniform scroll duration, and that your list contains semi-uniform items then you will need something like this.

/** Smoothly scroll to specified position allowing for interval specification. <br> * Note crude deceleration towards end of scroll * @param rv Your RecyclerView * @param toPos Position to scroll to * @param duration Approximate desired duration of scroll (ms) * @throws IllegalArgumentException */ private static void smoothScroll(RecyclerView rv, int toPos, int duration) throws IllegalArgumentException { int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000; // See androidx.recyclerview.widget.LinearSmoothScroller int itemHeight = rv.getChildAt(0).getHeight(); // Height of first visible view! NB: ViewGroup method! itemHeight = itemHeight + 33; // Example pixel Adjustment for decoration? int fvPos = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition(); int i = Math.abs((fvPos - toPos) * itemHeight); if (i == 0) { i = (int) Math.abs(rv.getChildAt(0).getY()); } final int totalPix = i; // Best guess: Total number of pixels to scroll RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(rv.getContext()) { @Override protected int getVerticalSnapPreference() { return LinearSmoothScroller.SNAP_TO_START; } @Override protected int calculateTimeForScrolling(int dx) { int ms = (int) ( duration * dx / (float)totalPix ); // Now double the interval for the last fling. if (dx < TARGET_SEEK_SCROLL_DISTANCE_PX ) { ms = ms*2; } // Crude deceleration! //lg(format("For dx=%d we allot %dms", dx, ms)); return ms; } }; //lg(format("Total pixels from = %d to %d = %d [ itemHeight=%dpix ]", fvPos, toPos, totalPix, itemHeight)); smoothScroller.setTargetPosition(toPos); rv.getLayoutManager().startSmoothScroll(smoothScroller); } 

PS: I curse the day I began indiscriminately converting ListView to RecyclerView.

1 Comment

sometimes ms returns 5000 ms which causes the view to scroll super slow, i would recommend using RecyclerView.SmoothScroller as i hinted out in my answer. stackoverflow.com/a/72668624/6039240
2

Thanks, @droidev for the solution. If anyone looking for Kotlin solution, refer this:

 class LinearLayoutManagerWithSmoothScroller: LinearLayoutManager { constructor(context: Context) : this(context, VERTICAL,false) constructor(context: Context, orientation: Int, reverseValue: Boolean) : super(context, orientation, reverseValue) override fun smoothScrollToPosition(recyclerView: RecyclerView?, state: RecyclerView.State?, position: Int) { super.smoothScrollToPosition(recyclerView, state, position) val smoothScroller = TopSnappedSmoothScroller(recyclerView?.context) smoothScroller.targetPosition = position startSmoothScroll(smoothScroller) } private class TopSnappedSmoothScroller(context: Context?) : LinearSmoothScroller(context){ var mContext = context override fun computeScrollVectorForPosition(targetPosition: Int): PointF? { return LinearLayoutManagerWithSmoothScroller(mContext as Context) .computeScrollVectorForPosition(targetPosition) } override fun getVerticalSnapPreference(): Int { return SNAP_TO_START } } } 

1 Comment

Thanks @pankaj, was looking for a solution in Kotlin.
2

Here you can change to where you want to scroll, changing SNAP_TO_* return value in get**SnapPreference.

duration will be always used to scroll to the nearest item as well as the farthest item in your list.

on finish is used to do something when scrolling is almost finished.

fun RecyclerView.smoothScroll(toPos: Int, duration: Int = 500, onFinish: () -> Unit = {}) { try { val smoothScroller: RecyclerView.SmoothScroller = object : LinearSmoothScroller(context) { override fun getVerticalSnapPreference(): Int { return SNAP_TO_END } override fun calculateTimeForScrolling(dx: Int): Int { return duration } override fun onStop() { super.onStop() onFinish.invoke() } } smoothScroller.targetPosition = toPos layoutManager?.startSmoothScroll(smoothScroller) } catch (e: Exception) { Timber.e("FAILED TO SMOOTH SCROLL: ${e.message}") } } 

1 Comment

Your solution with this duration is perfect.
1

The easiest way I've found to scroll a RecyclerView is as follows:

// Define the Index we wish to scroll to. final int lIndex = 0; // Assign the RecyclerView's LayoutManager. this.getRecyclerView().setLayoutManager(this.getLinearLayoutManager()); // Scroll the RecyclerView to the Index. this.getLinearLayoutManager().smoothScrollToPosition(this.getRecyclerView(), new RecyclerView.State(), lIndex); 

1 Comment

This won't scroll to a position if that position is already visible. It scrolls the least amount it needs to make the position visible. Thus why we need the one with specified offset.
1
  1. Extend "LinearLayout" class and override the necessary functions
  2. Create an instance of the above class in your fragment or activity
  3. Call "recyclerView.smoothScrollToPosition(targetPosition)

CustomLinearLayout.kt :

class CustomLayoutManager(private val context: Context, layoutDirection: Int): LinearLayoutManager(context, layoutDirection, false) { companion object { // This determines how smooth the scrolling will be private const val MILLISECONDS_PER_INCH = 300f } override fun smoothScrollToPosition(recyclerView: RecyclerView, state: RecyclerView.State, position: Int) { val smoothScroller: LinearSmoothScroller = object: LinearSmoothScroller(context) { fun dp2px(dpValue: Float): Int { val scale = context.resources.displayMetrics.density return (dpValue * scale + 0.5f).toInt() } // change this and the return super type to "calculateDyToMakeVisible" if the layout direction is set to VERTICAL override fun calculateDxToMakeVisible(view: View ? , snapPreference : Int): Int { return super.calculateDxToMakeVisible(view, SNAP_TO_END) - dp2px(50f) } //This controls the direction in which smoothScroll looks for your view override fun computeScrollVectorForPosition(targetPosition: Int): PointF ? { return this @CustomLayoutManager.computeScrollVectorForPosition(targetPosition) } //This returns the milliseconds it takes to scroll one pixel. override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float { return MILLISECONDS_PER_INCH / displayMetrics.densityDpi } } smoothScroller.targetPosition = position startSmoothScroll(smoothScroller) } } 

Note: The above example is set to HORIZONTAL direction, you can pass VERTICAL/HORIZONTAL during initialization.

If you set the direction to VERTICAL you should change the "calculateDxToMakeVisible" to "calculateDyToMakeVisible" (also mind the supertype call return value)

Activity/Fragment.kt :

... smoothScrollerLayoutManager = CustomLayoutManager(context, LinearLayoutManager.HORIZONTAL) recyclerView.layoutManager = smoothScrollerLayoutManager . . . fun onClick() { // targetPosition passed from the adapter to activity/fragment recyclerView.smoothScrollToPosition(targetPosition) } 

Comments

1

I have create an extension method based on position of items in a list which is bind with recycler view

Smooth scroll in large list takes longer time to scroll , use this to improve speed of scrolling and also have the smooth scroll animation. Cheers!!

fun RecyclerView?.perfectScroll(size: Int,up:Boolean = true ,smooth: Boolean = true) { this?.apply { if (size > 0) { if (smooth) { val minDirectScroll = 10 // left item to scroll //smooth scroll if (size > minDirectScroll) { //scroll directly to certain position val newSize = if (up) minDirectScroll else size - minDirectScroll //scroll to new position val newPos = newSize - 1 //direct scroll scrollToPosition(newPos) //smooth scroll to rest perfectScroll(minDirectScroll, true) } else { //direct smooth scroll smoothScrollToPosition(if (up) 0 else size-1) } } else { //direct scroll scrollToPosition(if (up) 0 else size-1) } } } } 

Just call the method anywhere using

rvList.perfectScroll(list.size,up=true,smooth=true) 

1 Comment

does not working...
0

Probably @droidev approach is the correct one, but I just want to publish something a little bit different, which does basically the same job and doesn't require extension of the LayoutManager.

A NOTE here - this is gonna work well if your item (the one that you want to scroll on the top of the list) is visible on the screen and you just want to scroll it to the top automatically. It is useful when the last item in your list has some action, which adds new items in the same list and you want to focus the user on the new added items:

int recyclerViewTop = recyclerView.getTop(); int positionTop = recyclerView.findViewHolderForAdapterPosition(positionToScroll) != null ? recyclerView.findViewHolderForAdapterPosition(positionToScroll).itemView.getTop() : 200; final int calcOffset = positionTop - recyclerViewTop; //then the actual scroll is gonna happen with (x offset = 0) and (y offset = calcOffset) recyclerView.scrollBy(0, offset); 

The idea is simple: 1. We need to get the top coordinate of the recyclerview element; 2. We need to get the top coordinate of the view item that we want to scroll to the top; 3. At the end with the calculated offset we need to do

recyclerView.scrollBy(0, offset); 

200 is just example hard coded integer value that you can use if the viewholder item doesn't exist, because that is possible as well.

Comments

0

for @vovahost's Kotlin solution https://stackoverflow.com/a/53986874/22228957

fun RecyclerView.smoothSnapToPosition(position: Int, snapMode: Int = LinearSmoothScroller.SNAP_TO_START, speedRatio : Float = 0.1f) { val smoothScroller = object : LinearSmoothScroller(this.context) { override fun getVerticalSnapPreference(): Int = snapMode override fun getHorizontalSnapPreference(): Int = snapMode } } smoothScroller.targetPosition = position layoutManager?.startSmoothScroll(smoothScroller) } 

if this not work for you, try to slow down animator speed by overwriting calculateSpeedPerPixel like following

fun RecyclerView.smoothSnapToPosition(position: Int, snapMode: Int = LinearSmoothScroller.SNAP_TO_START, speedRatio : Float = 0.1f) { val smoothScroller = object : LinearSmoothScroller(this.context) { override fun getVerticalSnapPreference(): Int = snapMode override fun getHorizontalSnapPreference(): Int = snapMode override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics?): Float { return super.calculateSpeedPerPixel(displayMetrics)/speedRatio // Slow down the speed } } smoothScroller.targetPosition = position layoutManager?.startSmoothScroll(smoothScroller) } 

And use it like following to avoid performance impacts while dealing with large lists.

idCurrentRecyclerView.scrollToPosition(bubblesList.size - 2) // Directly scroll to the second last item idCurrentRecyclerView.smoothSnapToPosition(bubblesList.size - 1) // Smooth scroll to the last item 

Comments

0

best practice and good method

recyclerView.getLayoutManager().scrollToPosition(position);

1 Comment

Multiple other answers reference scrollToPosition(). What differentiates yours? Are you certain your answer hasn't already been given? If so, please edit your answer to help clarify why your approach is different, and why you prefer it over the (highly validated) other answers.
-2

You can reverse your list by list.reverse() and finaly call RecylerView.scrollToPosition(0)

 list.reverse() layout = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,true) RecylerView.scrollToPosition(0) 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.