71

I have a recycler view, and I want a smooth scrolldown and then scrollup to it programatically to show the complete content in it to user.

I can do this by:

    final int height=recyclerView.getChildAt(0).getHeight();
    recyclerView.smoothScrollToPosition(height);
    recyclerView.postDelayed(new Runnable() {
        public void run() {
            recyclerView.smoothScrollToPosition(0);
                        }
    },200);

But what I want is to slow down the scrolling speed, so that the complete content gets visible clearly.

peterh
  • 11,875
  • 18
  • 85
  • 108
Dipika
  • 1,097
  • 1
  • 9
  • 19

8 Answers8

133

Just to improve on the answer a little:

public class SpeedyLinearLayoutManager extends LinearLayoutManager {

    private static final float MILLISECONDS_PER_INCH = 5f; //default is 25f (bigger = slower)

    public SpeedyLinearLayoutManager(Context context) {
        super(context);
    }

    public SpeedyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public SpeedyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {

        final LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {

            @Override
            public PointF computeScrollVectorForPosition(int targetPosition) {
                return super.computeScrollVectorForPosition(targetPosition);
            }

            @Override
            protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
            }
        };

        linearSmoothScroller.setTargetPosition(position);
        startSmoothScroll(linearSmoothScroller);
    }
}

And then set SpeedyLayoutManager to your RecyclerView:

recyclerView.setLayoutManager(new SpeedyLinearLayoutManager(context, SpeedyLinearLayoutManager.VERTICAL, false);
Johnny Five
  • 987
  • 1
  • 14
  • 29
Joe Maher
  • 5,354
  • 5
  • 28
  • 44
  • @gturedi sorry i never tested this with horizontal. Are you setting the layout manager to horizontal? – Joe Maher Apr 04 '18 at 21:58
  • 3
    This is working for horizontal. Just make sure to add SpeedyLinearLayoutManager.HORIZONTAL as the second argument of the SpeedyLinearLayoutManager constructor. – Wilhelm Apr 18 '18 at 13:09
  • For smooth scroll do `manager.smoothScrollToPosition(my_rcv,new RecyclerView.State(),0);` – nimi0112 May 30 '18 at 07:39
  • 1
    If you provide an interpolator, you must set a positive duration i get this runtimecrash ! – Jean Raymond Daher Aug 24 '18 at 12:40
  • 3
    I have set private static final float MILLISECONDS_PER_INCH = 3000f, but there is no any speed difference observe. whether is 25 or 3000 it was same speed. I have check in HORIZONTAL "SpeedyLinearLayoutManager(context, SpeedyLinearLayoutManager.HORIZONTAL, false)" – Hitesh Dhamshaniya Dec 13 '18 at 06:00
  • 1
    This didn't work. Didn't feel any difference in the scrolling speed. It's the same as the default no matter the value. – 6rchid Feb 11 '19 at 01:56
  • If you are using this and it is still scrolling too quickly, change the value of MILLISECONDS_PER_INCH in SpeedyLinearLayoutManager to something higher (i.e. 125F). Then when you call smoothScrollToPosition on your recyclerView whose layoutmanager is set to SpeedyLinearLayoutManager, it will be slower. – vikzilla Nov 10 '19 at 02:01
  • 4
    This doesn't work. Using it in horizontal orientation! – Fahad Saleem Aug 06 '20 at 05:34
  • if you're not seeing this work it's probably because you're scrolling with other methods (eg. scrollBy), notice you are only overriding `smoothScrollToPosition` unfortunately I don't think you can influence the speed with other methods – hmac Jan 27 '21 at 13:32
56

For those uninterested in overriding LinearLayoutManager (or whichever subclass you're using), you can just call startSmoothScroll(smoothScroller) directly on the layout manager.

LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {

    @Override
    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
        return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
    }
};

linearSmoothScroller.setTargetPosition(position);
layoutManager.startSmoothScroll(linearSmoothScroller);
David Liu
  • 9,426
  • 5
  • 40
  • 63
  • How can I set here, to smooth scroll to a position in one second, for example? – android developer Oct 25 '17 at 13:32
  • 1
    There's no simple solution for this. The problem is that recycler view doesn't know how far away a position is, so LinearSmoothScroll basically just does scrolls in the general direction of where the view is, and then slows down once the view gets on screen. You'd have to write a bunch of extra structures to do this yourself. – David Liu Oct 25 '17 at 18:51
  • What if all cells have the same size (or I have average size, or estimated one of all), and of course, I know the number of cells ahead? – android developer Oct 25 '17 at 22:02
  • If you have the exact number of pixels to scroll, then you can create your own implementation of RecyclerView.SmoothScroller. Override onTargetFound and onSeekTargetStep, and update the Action given *once*, with the pixels to scroll and the duration. Once the action is updated, ignore any future calls to either of those methods. – David Liu Oct 25 '17 at 22:43
  • Do you have perhaps a sample or a link I can look at, that shows how it's done using what you wrote? – android developer Oct 26 '17 at 07:49
  • No. I have not done it. I'm just looking at the RecyclerView source code, and giving you my approach to how I would do it if I had to do that. – David Liu Oct 26 '17 at 18:40
  • I've noticed that if I set the speed to be too fast (meaning calculateSpeedPerPixel returns a value very near 0) , it either goes crazy (scrolling left and right), or scrolls to the far end, or to a different incorrect location. What are the limitations of the values I can set for calculateSpeedPerPixel ? I think that 0.1f is a safe value yet 0.01f isn't. but is it always this way? – android developer Oct 29 '17 at 16:00
  • Again, for your specific purposes you're much better off overriding **RecyclerView.SmoothScroller**, not LinearSmoothScroller. If you want to override LinearSmoothScroller, look at the code itself to understand what it does. https://github.com/aosp-mirror/platform_frameworks_support/blob/master/v7/recyclerview/src/main/java/android/support/v7/widget/LinearSmoothScroller.java – David Liu Nov 01 '17 at 21:12
  • @androiddeveloper, yeah that speed, sometimes skips the target, how did you solve that? – hemen Aug 29 '18 at 14:24
  • @notTdar I don't remember, but try 0.1f as a limit . Don't let it go beyond it. – android developer Aug 29 '18 at 14:36
  • tried with that, way too fast and misses in some situation, although i do need the speed. – hemen Aug 29 '18 at 14:37
  • @notTdar So decide what's "too fast" for you, and instead of smooth scrolling use instant scrolling on these cases. – android developer Aug 29 '18 at 14:50
  • @androiddeveloper, this would be too broad to explain, in case, https://stackoverflow.com/questions/52074312/infinite-circular-recyclerview-smothscrolltoposition-goes-to-wrong-index, – hemen Aug 29 '18 at 14:54
  • @notTdar Sorry. As I wrote, I've left this a long time ago. I think what I wrote is what we've decided. Good luck – android developer Aug 29 '18 at 17:05
  • this solution is working , but if you want fix sxroll speed you need to add this: override fun calculateTimeForDeceleration(dx: Int): Int { return 0 } – masoud jafari Jun 05 '22 at 09:25
24

I found an answer!

You need to make a custom class extending [LinearLayoutManager][1] and then override the [smoothScrollToPosition][1] method. Inside, you need to create a new [LinearSmoothScroller][1] and override its calculateSpeedPerPixel method Then use that LinearSmoothScroller to complete the scroll.

Here is my example :

public class CustomLinearLayoutManager extends LinearLayoutManager{
        @Override
        public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
            final LinearSmoothScroller linearSmoothScroller =
                    new LinearSmoothScroller(recyclerView.getContext()) {
                        private static final float MILLISECONDS_PER_INCH = 100f;

                        @Override
                        public PointF computeScrollVectorForPosition(int targetPosition) {
                            return CustomLinearLayoutManager.this
                                .computeScrollVectorForPosition(targetPosition);
                    }

                    @Override
                    protected float calculateSpeedPerPixel
                            (DisplayMetrics displayMetrics) {
                        return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
                    }
                };
        linearSmoothScroller.setTargetPosition(position);
        startSmoothScroll(linearSmoothScroller);
    }
}

Assign this CustomLinearLayoutManager to your RecyclerView and you should be good to go. Let me know if I was unclear in anything. I'll be happy to help.

Rise
  • 820
  • 4
  • 18
  • 33
Jay S.
  • 1,318
  • 11
  • 29
  • hey @Jay S, can you plz tell me what min SDK version you r using. As i m using 15 alone with appcompat-v7:22.2.0 support library and the class SnappyLinearLayoutManager is not there to use. – Dipika Apr 29 '16 at 11:25
  • Made my day. Thank you – Hocine B Nov 03 '18 at 15:08
  • @Dipika `SnappyLinearLayoutManager` extends `LinearLayoutManager` in a custom class you found elsewhere. If you are married to the name, you would change every instance of `CustomLinearLayoutManager` in this answer to it. – Abandoned Cart May 10 '19 at 02:51
  • hey, I tried this. But, nothing is happening. Is it because I am extending GridLayoutManager? – Anirudhdhsinh Jadeja Jun 19 '21 at 08:50
15

I've made a version that you can set the scrolling speed variable.

If you set factor to 2, it will be twice as slow as default.

public class VariableScrollSpeedLinearLayoutManager extends LinearLayoutManager {

    private final float factor;

    public VariableScrollSpeedLinearLayoutManager(Context context, float factor) {
        super(context);
        this.factor = factor;
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {

        final LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {

            @Override
            public PointF computeScrollVectorForPosition(int targetPosition) {
                return VariableScrollSpeedLinearLayoutManager.this.computeScrollVectorForPosition(targetPosition);
            }

            @Override
            protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                return super.calculateSpeedPerPixel(displayMetrics) * factor;
            }
        };

        linearSmoothScroller.setTargetPosition(position);
        startSmoothScroll(linearSmoothScroller);
    }
}
Boy
  • 7,010
  • 4
  • 54
  • 68
10

Based on @Bartek Mrozinski's answer. This is my Kotlin extension function for smoothly scroll the RecyclerView in a given time .

fun RecyclerView.smoothSnapToPosition(position: Int, snapMode: Int = LinearSmoothScroller.SNAP_TO_START) {
val scrollDuration = 500f;
val smoothScroller = object : LinearSmoothScroller(this.context) {
    override fun getVerticalSnapPreference(): Int = snapMode
    override fun getHorizontalSnapPreference(): Int = snapMode
    override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics?): Float {
        return scrollDuration / computeVerticalScrollRange();
    }
}
smoothScroller.targetPosition = position
layoutManager?.startSmoothScroll(smoothScroller) 
}
furkanbzkurt
  • 296
  • 4
  • 14
4

To smoothly scroll the RecyclerView in a given time You can override the LinearSmoothScroller#calculateSpeedPerPixel like in example below.

final Context context = getContext();
final RecyclerView recyclerView = new RecyclerView(context);

final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(layoutManager);

final YourCustomAdapter adapter = new YourCustomAdapter();
recyclerView.setAdapter(new YourCustomAdapter());

final float scrollDuration = 30000f; // in milliseconds
final LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
    @Override
    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
        return scrollDuration / recyclerView.computeVerticalScrollRange();
    }
};

linearSmoothScroller.setTargetPosition(adapter.getItemCount() - 1);
layoutManager.startSmoothScroll(linearSmoothScroller);
2

enter image description here

I got it like this horizontally

  private val milliSecondsPerInch = 500f 
  var horizontalLayoutManager =  LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)

onCreate

horizontalLayoutManager =  LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
recycler_banner?.layoutManager = horizontalLayoutManager

and then

val linearSmoothScroller: LinearSmoothScroller =
object : LinearSmoothScroller(this) {
    override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float {
        return milliSecondsPerInch / displayMetrics.densityDpi
    }
}
linearSmoothScroller.targetPosition = 3 //the last position of the item in the list
horizontalLayoutManager.startSmoothScroll(linearSmoothScroller)
AllanRibas
  • 678
  • 5
  • 14
2
//i am using the handler..
 //where cretedactivtyadapter is my adpater classs
// instiate your adpter class like this
 private CreatedActivityAdapter createdActivityAdapter;

//call autoscroll in your oncreate//
public void autoscroll()
{ ////auto scroll is used for the recycler view to scroll it self
    final int speedScroll = 1;
    final Handler handler = new Handler();
    final Runnable runnable = new Runnable() {
        int count = 0;
        @Override
        public void run()
        {
            if (count == createdActivityAdapter.getItemCount())
                count=0;
            if (count < createdActivityAdapter.getItemCount()){
                recyclerView.smoothScrollToPosition(++count);
                handler.postDelayed(this,speedScroll);
            }
        }
    };
    handler.postDelayed(runnable,speedScroll);
}

//and call theis handler in your on create

Mazhar Iqbal
  • 813
  • 7
  • 7