271

I'm looking for a way to scroll a RecyclerView to show the selected item on top.

In a ListView I was able to do that by using scrollTo(x,y) and getting the top of the element that need to be centered.

Something like:

@Override
public void onItemClick(View v, int pos){
    mylistView.scrollTo(0, v.getTop());
}

The problem is that the RecyclerView returns an error when using it's scrollTo method saying

RecyclerView does not support scrolling to an absolute position

How can I scroll a RecyclerView to put the selected item at the top of the view?

mmBs
  • 8,421
  • 6
  • 38
  • 46
Distwo
  • 11,569
  • 8
  • 42
  • 65

19 Answers19

473

If you are using the LinearLayoutManager or Staggered GridLayoutManager, they each have a scrollToPositionWithOffset method that takes both the position and also the offset of the start of the item from the start of the RecyclerView, which seems like it would accomplish what you need (setting the offset to 0 should align with the top).

For instance:

//Scroll item 2 to 20 pixels from the top
linearLayoutManager.scrollToPositionWithOffset(2, 20);
mohammadReza Abiri
  • 1,759
  • 1
  • 9
  • 20
Niraj
  • 5,270
  • 2
  • 13
  • 19
  • 43
    If anyone needs this for restoring the scroll position of a RecyclerView, this is how to save the scroll positions (the two arguments for the method scrollToPositionWithOffset): int index = linearLayoutManager.findFirstVisibleItemPosition(); View v = linearLayoutManager.getChildAt(0); int top = (v == null) ? 0 : (v.getTop() - linearLayoutManager.getPaddingTop()); – DominicM Feb 17 '15 at 18:43
  • What I don't seem to get about this is *when* to call scrollToPosition? if the selection listener is on the individual views, how will the RecyclerView object (which has access to its layout manager that can call scrolToPosition) be notified that an item is selected? – Nonos May 12 '15 at 17:54
  • @Nonos you can first store the LayoutManager that you use in RecylerView.setLayoutManager() and then access it directly. Like this: LinearLayoutManager llm = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(llm); ...(later in code)... llm.scrollToPositionWithOffset(2, 20); – Niraj May 13 '15 at 18:32
  • 24
    What if I want to scroll to the top "smoothly"? – Bitcoin Cash - ADA enthusiast Jul 01 '15 at 04:12
  • @Tiago, I haven't played around with it, but this 3-part series of articles may help: http://blog.stylingandroid.com/scrolling-recyclerview-part-1 – Niraj Jul 01 '15 at 19:13
  • if you don't want move from screen ? how I can do ? –  Sep 14 '15 at 13:44
  • fyi layout manager has it's on onSaveInstanceState() onRestoreInstanceState(Parcelable) calls to help with state management. – Travis Castillo Feb 16 '16 at 21:41
  • @DominicM the first view in LayoutManager is not always the one that is first visible. To get the correct top offset, I would use layoutManager.findViewByPosition(firstVisiblePosition).getTop(); – Bolein95 Mar 26 '16 at 17:17
  • the method scrollToPositionWithOffset for the linearlayoutmanager does'nt seem to be available for my version of the support libraries 23.3.0, is that true for anyone else? – Akah Sep 22 '16 at 01:51
  • this method is now removed. – Manoj Perumarath Mar 03 '17 at 11:29
  • Manoj Frekzz where did you read that? larrytech be sure you're not trying to call that method on the base `LayoutManager`. i.e. `recyclerView.getLayoutManager().scrollToPositionWithOffset(...)` won't work without casting. – tir38 Mar 15 '17 at 20:46
109

If you looking for vertical LinearLayout Manager you can achieve smooth scrolling using a custom LinearSmoothScroller:

import android.content.Context;
import android.graphics.PointF;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.RecyclerView;

public class SnappingLinearLayoutManager extends LinearLayoutManager {

    public SnappingLinearLayoutManager(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 SnappingLinearLayoutManager.this
                    .computeScrollVectorForPosition(targetPosition);
        }

        @Override
        protected int getVerticalSnapPreference() {
            return SNAP_TO_START;
        }
    }
}

use an instance of the layoutmanager in recycle view and then calling recyclerView.smoothScrollToPosition(pos); will smooth scroll to selected position to top of the recycler view

ItamarG3
  • 4,092
  • 6
  • 31
  • 44
nizammoidu
  • 2,074
  • 1
  • 14
  • 14
  • 2
    Really useful answer! I also override `getHorizontalSnapPreference()` in the TopSnappedSmoothScroller for Horizontal lists. – Juan Saravia Oct 09 '15 at 18:40
  • 6
    Am I right in observing that this only works only for items that still have enough after/below them to fill the remaining `RecyclerView`? Which is to say: This does not seem to work for the last item - regardless of the `SNAP_TO_START` the `RecyclerView` only scrolls until the item becomes visible at the bottom but does not move it all the way up. Any ideas of how to achieve this? (I have worked around this by setting custom padding on the `RecyclerView` but that does not seem really right...) – david.mihola Aug 12 '16 at 08:51
  • 3
    Same in Kotlin => https://gist.github.com/Kevinrob/4f32a6b971930cdc49f06be8dce6403c – Kevin Robatel Dec 13 '16 at 10:10
  • sometime the item dosent get focused, i am having a focus drawable for item view. – YLS Mar 30 '17 at 04:16
  • 1
    @david.mihola Have you found a 'right' way? I've set custom padding to my recyclerview but I'm having some weird problems... – bernardo.g Oct 16 '17 at 13:31
  • @bernardo.g No, not really. We now have a fixed `paddingRight` on our `RecyclerView` (it's a horizontal one) and have set `clipToPadding` to false. That makes sure that the item can move to the left as far as we want to - but we are doing this in a TV app, so all user input is coming from the remote. I have no idea how touch input would be affected by this solution... – david.mihola Oct 17 '17 at 13:09
  • @nizammoidu can decrease the speed of the scroll? – CrazyMind Dec 20 '18 at 07:43
64

//Scroll item pos

linearLayoutManager.scrollToPositionWithOffset(pos, 0);
HBQ
  • 641
  • 4
  • 3
28

You just need to call recyclerview.scrollToPosition(position). That's fine!

If you want to call it in adapter, just let your adapter has the instance of recyclerview or the activity or fragment which contains recyclerview,than implements the method getRecyclerview() in them.

I hope it can help you.

Victor Häggqvist
  • 4,484
  • 3
  • 27
  • 35
Zero
  • 1,142
  • 13
  • 10
  • 2
    This worked for me - recyclerView.scrollToPosition(0); put me at the top. – Ray Hunter Mar 24 '16 at 17:56
  • 3
    that not working you have to use the linearLayoutManager.scrollToPositionWithOffset(pos, 0); – Anil Ugale Jun 28 '17 at 09:30
  • @Anil Ugale That's nonsense. Of course it works. You can use any LayoutManager for a RecyclerView. Why should you later care about it when you want to scroll? I think you were doing something wrong. Recyclerview.scrollToPosition(position) is a convenience method which calls LayoutManager.scrollToPosition(position). – The incredible Jan Aug 06 '18 at 13:29
  • 1
    @TheincredibleJan it doesn't work in all cases, the layoutManager is generally responsible for the scrolling anyhow just for your information. – Mohammed Feb 27 '19 at 08:45
  • This doesn't always put the requested item on the top as the question asked. – James Riordan Jul 01 '20 at 11:11
26

If you want to scroll automatic without show scroll motion then you need to write following code:

mRecyclerView.getLayoutManager().scrollToPosition(position);

If you want to display scroll motion then you need to add following code. =>Step 1: You need to declare SmoothScroller.

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

=>step 2: You need to add this code any event you want to perform scroll to specific position. =>First you need to set target position to SmoothScroller.

smoothScroller.setTargetPosition(position);

=>Then you need to set SmoothScroller to LayoutManager.

mRecyclerView.getLayoutManager().startSmoothScroll(smoothScroller);
gianlucaparadise
  • 1,678
  • 20
  • 32
Paras Santoki
  • 821
  • 11
  • 26
  • Amazing! The simplest solution is if you need a smooth scroll with the target element showing at the top of the RecyclerView! Thanks! – Gregory Jul 06 '20 at 17:11
13

just call this method simply:

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

instead of:

recyclerView.scrollToPosition(yourItemPosition);
Amir Hossein Ghasemi
  • 20,623
  • 10
  • 57
  • 53
  • "instead of"? Distwo didn't mention "scrollToPosition". If he had used it there wouldn't have been any problem. scrollToPosition() as intended. – The incredible Jan Aug 06 '18 at 13:37
  • 1
    strangely enough, using scrollToPositionWithOffset works for me and recyclerview.scrollToPosition didnt. Although , i would request Amir to change the RecyclerView to recyclerview, because it gives a sense that the methods are static, and they are not. – Mohit Oct 21 '18 at 18:44
11

same with speed regulator

public class SmoothScrollLinearLayoutManager extends LinearLayoutManager {
private static final float MILLISECONDS_PER_INCH = 110f;
private Context mContext;

public SmoothScrollLinearLayoutManager(Context context,int orientation, boolean reverseLayout) {
    super(context,orientation,reverseLayout);
    mContext = context;
}

@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
                                   int position) {
    RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext()){
        //This controls the direction in which smoothScroll looks for your view
        @Override
        public PointF computeScrollVectorForPosition(int targetPosition) {
            return new PointF(0, 1);
        }

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


private class TopSnappedSmoothScroller extends LinearSmoothScroller {
    public TopSnappedSmoothScroller(Context context) {
        super(context);

    }

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

    @Override
    protected int getVerticalSnapPreference() {
        return SNAP_TO_START;
    }
}
}
user2212515
  • 1,220
  • 1
  • 12
  • 10
9

Try what worked for me cool!

Create a variable private static int displayedposition = 0;

Now for the position of your RecyclerView in your Activity.

myRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                LinearLayoutManager llm = (LinearLayoutManager) myRecyclerView.getLayoutManager();


                displayedposition = llm.findFirstVisibleItemPosition();


            }
        });

Place this statement where you want it to place the former site displayed in your view .

LinearLayoutManager llm = (LinearLayoutManager) mRecyclerView.getLayoutManager();
        llm.scrollToPositionWithOffset(displayedposition , youList.size());

Well that's it , it worked fine for me \o/

psicopante
  • 353
  • 3
  • 5
8

what i did to restore the scroll position after refreshing the RecyclerView on button clicked:

if (linearLayoutManager != null) {

    index = linearLayoutManager.findFirstVisibleItemPosition();
    View v = linearLayoutManager.getChildAt(0);
    top = (v == null) ? 0 : (v.getTop() - linearLayoutManager.getPaddingTop());
    Log.d("TAG", "visible position " + " " + index);
}

else{
    index = 0;
}

linearLayoutManager = new LinearLayoutManager(getApplicationContext());
linearLayoutManager.scrollToPositionWithOffset(index, top);

getting the offset of the first visible item from the top before creating the linearLayoutManager object and after instantiating it the scrollToPositionWithOffset of the LinearLayoutManager object was called.

Distwo
  • 11,569
  • 8
  • 42
  • 65
tsiro
  • 2,323
  • 3
  • 24
  • 46
8

I don't know why I didn't find the best answer but its really simple.

recyclerView.smoothScrollToPosition(position);

No errors

Creates Animations

Lucem
  • 2,912
  • 3
  • 20
  • 33
8

What i may add here is how to make it work together with DiffUtil and ListAdapter

You may note that calling recyclerView.scrollToPosition(pos) or (recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(pos, offset) wouldn't work if called straight after adapter.submitList. It is because the differ looks for changes in a background thread and then asynchronously notifies adapter about changes. On a SO i have seen several wrong answers with unnecessary delays & etc to solve this.

To handle the situation properly the submitList has a callback which is invoked when changes have been applied.

So the proper kotlin implementations in this case are:

//memorise target item here and a scroll offset if needed
adapter.submitList(items) { 
    val pos = /* here you may find a new position of the item or just use just a static position. It depends on your case */
    recyclerView.scrollToPosition(pos) 
}
//or
adapter.submitList(items) { recyclerView.smoothScrollToPosition(pos) }
//or etc
adapter.submitList(items) { (recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(pos, offset) }
Beloo
  • 9,723
  • 7
  • 40
  • 71
5

Introduction

None of the answers explain how to show last item(s) at the top. So, the answers work only for items that still have enough items above or below them to fill the remaining RecyclerView. For instance, if there are 59 elements and a 56-th element is selected it should be at the top as in the picture below:

example

So, let's see how to implement this in the next paragraph.

Solution

We could handle those cases by using linearLayoutManager.scrollToPositionWithOffset(pos, 0) and additional logic in the Adapter of RecyclerView - by adding a custom margin below the last item (if the last item is not visible then it means there's enough space fill the RecyclerView). The custom margin could be a difference between the root view height and the item height. So, your Adapter for RecyclerView would look as follows:

...
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    ...

    int bottomHeight = 0;
    int itemHeight = holder.itemView.getMeasuredHeight();
    // if it's the last item then add a bottom margin that is enough to bring it to the top
    if (position == mDataSet.length - 1) {
        bottomHeight = Math.max(0, mRootView.getMeasuredHeight() - itemHeight);
    }
    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)holder.itemView.getLayoutParams();
    params.setMargins(0, 0, params.rightMargin, bottomHeight);
    holder.itemView.setLayoutParams(params);

    ...
} 
...
Anatolii
  • 14,139
  • 4
  • 35
  • 65
4

If your LayoutManager is LinearLayoutManager you can use scrollToPositionWithOffset(position,0); on it and it will make your item the first visible item in the list. Otherwise, you can use smoothScrollToPosition on the RecyclerView directly.

I ended up using the below code.

 RecyclerView.LayoutManager layoutManager = mainList.getLayoutManager();
        if (layoutManager instanceof LinearLayoutManager) {
            // Scroll to item and make it the first visible item of the list.
            ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, 0);
        } else {
            mainList.smoothScrollToPosition(position);
        }
Mohamed Nageh
  • 1,963
  • 1
  • 19
  • 27
3

scroll at particular position
and this helped me alot. by click listener you can get the position in your adapter

layoutmanager.scrollToPosition(int position);
Sandeep Singh
  • 259
  • 2
  • 11
2

In my case my RecyclerView have a padding top like this

<android.support.v7.widget.RecyclerView
     ...
     android:paddingTop="100dp"
     android:clipToPadding="false"
/>

Then for scroll a item to top, I need to

recyclerViewLinearLayoutManager.scrollToPositionWithOffset(position, -yourRecyclerView.getPaddingTop());
Linh
  • 57,942
  • 23
  • 262
  • 279
1

please note that if scrollToPosition not work notice that your RecyclerView was inside a NestedScrollView; refer to this post

FxRi4
  • 1,096
  • 10
  • 15
1

This is pretty simple

recyclerView.scrollToPosition(position)
Tarif Chakder
  • 1,708
  • 1
  • 11
  • 10
  • but this will not scroll to the top of the screen it can be in middle in bottom it will just show selected position item – Adam Noor Mar 15 '22 at 08:16
  • If you want top use recyclerView.scrollToPosition(0) – Tarif Chakder Mar 15 '22 at 10:05
  • 1
    I have 10 items and i click on some button and i need to scroll to item 7 if you write recyclerview.scrollToPosition(7) it will scroll that position but if i want that item to display on the top of the screen then? – Adam Noor Mar 16 '22 at 06:45
0

If you've Recycler view inside nestedscrollview :

val y = recyclerview.getChildAt(0).y
recyclerview.smoothScrollTo(0, y.toInt())

If your Recycler view is not inside nestedscrollview :

recyclerview.smoothScrollToPosition(index) 

or

recyclerview.layoutManager?.smoothScrollToPosition(recyclerview, null ,index)
Elikill58
  • 4,050
  • 24
  • 23
  • 45
Soft Code
  • 49
  • 5
-2

I use the code below to smooth-scroll an item (thisView) to the top.
It works also for GridLayoutManager with views of different heights:

View firstView = mRecyclerView.getChildAt(0);
int toY = firstView.getTop();
int firstPosition = mRecyclerView.getChildAdapterPosition(firstView);
View thisView = mRecyclerView.getChildAt(thisPosition - firstPosition);
int fromY = thisView.getTop();

mRecyclerView.smoothScrollBy(0, fromY - toY);

Seems to work good enough for a quick solution.