Android: Customizing SmoothScroller for the RecyclerView

Standard

First, this is not a full comprehensive in-depth guide for the SmoothScroller, but I hope this gives you a jumpstart on where you can begin customizing because I could not find any clear tutorials on this topic.

What is a SmoothScroller you ask? A SmoothScroller is exactly as it seems. It’s a Class that helps a View scroll to an item’s position smoothly to create a seamless transition as opposed to snapping to it abruptly. In this example I will be using its child Class LinearSmoothScroller with the RecyclerView.

We all know that to initialize a RecylerView we need to set a LayoutManager like so:

mRecyclerView.setLayoutManager(mLinearLayoutManager);

or else an error will occur during run-time. Well, the LayoutManager is in charge of the smooth scrolling! Therefore, in order to customize the SmoothScroller we need to create our own custom LayoutManager first.

1. Setup

I will be customizing the LinearLayoutManager.

public class MyCustomLayoutManager extends LinearLayoutManager {

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

After this step we want to override this method (taken from Google Docs):

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

    /*Smooth scroll to the specified adapter position.

    To support smooth scrolling,
    1. Override this method,
    2. Create your RecyclerView.SmoothScroller instance
    3. Call startSmoothScroll(SmoothScroller).*/

The docs give us all the directions we need to support smooth scrolling!… or does it?
Lets override this method first, implementing what the docs has told us to do. After doing so, our custom LayoutManager now looks like this:

public class MyCustomLayoutManager extends LinearLayoutManager {
    //We need mContext to create our LinearSmoothScroller
    private Context mContext;

    public MyCustomLayoutManager(Context context) {
        super(context);
        mContext = context;
    }

    //Override this method? Check.
    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView,
        RecyclerView.State state, int position) {

        //Create your RecyclerView.SmoothScroller instance? Check.
        LinearSmoothScroller smoothScroller = 
            new LinearSmoothScroller(mContext) {

            //Automatically implements this method on instantiation.
            @Override
            public PointF computeScrollVectorForPosition
                (int targetPosition) {
                return null;
            }
        };

        //Docs do not tell us anything about this,
        //but we need to set the position we want to scroll to.
        smoothScroller.setTargetPosition(position);

        //Call startSmoothScroll(SmoothScroller)? Check.
        startSmoothScroll(smoothScroller);
    }
}

 

2. Customizing Direction to Look for the List Item

You may be wondering what does computeScrollVectorForPosition(int targetPosition) do? Well, in the Google Docs the documentation is blank.. BLANK! C’mon Google, I am disappoint. Thankfully you have me. I did some testing and scoured the source code for information and this is what I came up with.

LinearSmoothScroller smoothScroller = 
    new LinearSmoothScroller(mContext) {

//This controls the direction in which smoothScroll looks for your
//list item
    @Override
    public PointF computeScrollVectorForPosition(int targetPosition) {
        //What is PointF? A class that just holds two float coordinates.
        //Accepts a (x , y)
        //for y: use -1 for up direction, 1 for down direction.
        //for x (did not test): use -1 for left direction, 1 for right
        //direction.
        //We let our custom LinearLayoutManager calculate PointF for us
        return MyCustomLayoutManager.this.computeScrollVectorForPosition
            (targetPosition);
    }
};

To clarify, what computeScrollVectorForPosition(...) does is that if my screen was in the middle of the list (item #50) and I wanted to go to item #100, if I returned PointF(0, 1), it will scroll downwards til it finds item #100. If it was PointF(0, -1), it would scroll up to find item #100, which will make it scroll all the way to the top since it won’t find item #100 obviously.

Recommended: Just let SmoothScroller automatically calculate the direction to look for the list item by returning : MyCustomLayoutManager.this.computeScrollVectorForPosition(targetPosition); (lines 14, 15)

3. Customizing Scrolling Speed

Next, how do we customize scrolling speed?
This is probably the number one reason why anyone would want to create a custom SmoothScroller in the first place. All you need to do is override this method in your LinearSmoothScroller instance:

//Make an instance variable at the top of you custom LayoutManager
private static final float MILLISECONDS_PER_INCH = 50f;
    .
    .
    .
    LinearSmoothScroller smoothScroller = 
        new LinearSmoothScroller(mContext) {
        .
        .
        .
        //The holy grail of smooth scrolling
        //returns the milliseconds it takes to scroll one pixel.
        @Override
        protected float calculateSpeedPerPixel
            (DisplayMetrics displayMetrics) {
            return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
        }
    };

calculateSpeedPerPixel(DisplayMetrics displayMetrics) allows you to adjust how fast you want the SmoothScroller to scroll over a pixel on your screen!

Some devices have 480 pixels in an inch and some have 240 pixels in an inch. More pixels means that it will take longer to scroll one inch as compared to a lower dpi device. Therefore, we need to find an equation that can solve that problem:

MILLISECONDS_PER_INCH is how fast we want it to scroll one inch. We divide it by displayMetrics.densityDpi because we want that speed to look consistent through different devices with different pixel densities. Higher dpi devices will have to scroll through pixels at a faster rate than that of a lower dpi device to keep up. I used 50 ms/inch but you can do whatever you like.

4. Finally

In the end your custom LinearLayoutManager should look like this:

public class MyCustomLayoutManager extends LinearLayoutManager {
    private static final float MILLISECONDS_PER_INCH = 50f;
    private Context mContext;

    public MyCustomLayoutManager(Context context) {
        super(context);
        mContext = context;
    }

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

        LinearSmoothScroller smoothScroller = 
            new LinearSmoothScroller(mContext) {

            //This controls the direction in which smoothScroll looks
            //for your view
            @Override
            public PointF computeScrollVectorForPosition
            (int targetPosition) {
                return MyCustomLayoutManager.this
                    .computeScrollVectorForPosition(targetPosition);
            }

            //This returns the milliseconds it takes to 
            //scroll one pixel.
            @Override
            protected float calculateSpeedPerPixel
                (DisplayMetrics displayMetrics) {
                return MILLISECONDS_PER_INCH/displayMetrics.densityDpi;
            }
        };

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

To wrap it all up, you can now set MyCustomLayoutManager to your RecyclerView and call smoothScrollToPosition(position). It should now scroll smooth like butter.

mLayoutManager = new MyCustomLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.smoothScrollToPosition(position);

Hopefully my directions were helpful and you weren’t too overwhelmed. To find out what other methods you can overwrite to customize the LinearSmoothScroller even further please visit: https://developer.android.com/reference/android/support/v7/widget/LinearSmoothScroller.html

Thanks and remember coding can get very complex. Hang in there!

11 thoughts on “Android: Customizing SmoothScroller for the RecyclerView

  1. Great post. But how do we customize the “computeScrollVectorForPosition” method so that it works for scrolling up and down? Hardcoding 1 oder -1 doesn’t seem sufficient in this case.

    Like

    • Ok I’ve just figured it out by looking at the Source-Code of the LinearLayoutManager. Simply return the following inside your “computeScrollVectoreForPosition” method: “return MyCustomLayoutManager.this.computeScrollVectorForPosition(targetPosition);” This will take care of the proper scrolling direction.

      Liked by 1 person

  2. Anondroid

    Thank you, for this.

    If you override the snapPreference methods in the smoothscroller, you can set how (or if) the view that is being scrolled to should align to with the recyclerview.

    Like

Leave a comment