Scroll RecyclerView to show selected item on top
Asked Answered
A

19

272

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?

Alexia answered 11/11, 2014 at 21:38 Comment(0)
J
478

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);
Jeffjeffcoat answered 11/11, 2014 at 22:45 Comment(11)
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());Forerunner
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?Galton
@Galton 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);Jeffjeffcoat
What if I want to scroll to the top "smoothly"?Kibitzer
@Tiago, I haven't played around with it, but this 3-part series of articles may help: blog.stylingandroid.com/scrolling-recyclerview-part-1Jeffjeffcoat
if you don't want move from screen ? how I can do ?Erk
fyi layout manager has it's on onSaveInstanceState() onRestoreInstanceState(Parcelable) calls to help with state management.Nival
@Forerunner 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();Lawn
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?Pimentel
this method is now removed.Twoseater
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.Slipway
Q
111

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

Qianaqibla answered 5/7, 2015 at 7:43 Comment(7)
Really useful answer! I also override getHorizontalSnapPreference() in the TopSnappedSmoothScroller for Horizontal lists.Scathing
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...)Worriment
Same in Kotlin => gist.github.com/Kevinrob/4f32a6b971930cdc49f06be8dce6403cCalbert
sometime the item dosent get focused, i am having a focus drawable for item view.Hydrophobic
@Worriment Have you found a 'right' way? I've set custom padding to my recyclerview but I'm having some weird problems...Devotion
@Devotion 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...Worriment
@Qianaqibla can decrease the speed of the scroll?Staw
T
67

//Scroll item pos

linearLayoutManager.scrollToPositionWithOffset(pos, 0);
Typhogenic answered 19/11, 2014 at 7:1 Comment(3)
is there any way to do this smoothly?Carlock
@Carlock #31235683Weakness
@MarkBuikema I found it before. thanx for your attention dudeCarlock
H
29

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.

Homosexual answered 1/9, 2015 at 7:5 Comment(5)
This worked for me - recyclerView.scrollToPosition(0); put me at the top.Fellow
that not working you have to use the linearLayoutManager.scrollToPositionWithOffset(pos, 0);Farra
@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).Deodar
@TheincredibleJan it doesn't work in all cases, the layoutManager is generally responsible for the scrolling anyhow just for your information.Downer
This doesn't always put the requested item on the top as the question asked.Gallop
D
27

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);
Dean answered 25/9, 2018 at 9:16 Comment(1)
Amazing! The simplest solution is if you need a smooth scroll with the target element showing at the top of the RecyclerView! Thanks!Starveling
L
13

just call this method simply:

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

instead of:

recyclerView.scrollToPosition(yourItemPosition);
Lemuelah answered 25/4, 2018 at 15:53 Comment(2)
"instead of"? Distwo didn't mention "scrollToPosition". If he had used it there wouldn't have been any problem. scrollToPosition() as intended.Deodar
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.Hindquarter
W
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;
    }
}
}
Waler answered 28/7, 2015 at 14:53 Comment(0)
M
11

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) }
Marchal answered 12/10, 2020 at 9:25 Comment(2)
You mentioned great point: "changes happens in a background thread and then asynchronously notifies adapter about changes". TNX.Delirious
Do we update the scroll position from Runnable?Laraelaraine
A
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/

Aila answered 27/8, 2015 at 0:35 Comment(0)
M
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.

Midshipman answered 17/9, 2015 at 18:38 Comment(0)
A
8

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

recyclerView.smoothScrollToPosition(position);

No errors

Creates Animations

Aquilegia answered 22/3, 2019 at 6:34 Comment(2)
where will you put this in activity or adapter?Hendrickson
This won't scroll clicked item to the top - which was the problem in the first place.He
A
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);

    ...
} 
...
Asinine answered 7/11, 2019 at 23:38 Comment(0)
A
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);
        }
Avina answered 16/2, 2020 at 13:13 Comment(0)
S
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);
Stacee answered 25/10, 2016 at 11:53 Comment(0)
E
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());
Epitaph answered 9/8, 2017 at 2:32 Comment(0)
E
1

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

Earthy answered 3/3, 2021 at 11:55 Comment(0)
D
1

This is pretty simple

recyclerView.scrollToPosition(position)
Digitoxin answered 25/10, 2021 at 6:52 Comment(3)
but this will not scroll to the top of the screen it can be in middle in bottom it will just show selected position itemUnderdog
If you want top use recyclerView.scrollToPosition(0)Digitoxin
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?Underdog
K
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)
Kozlowski answered 9/2, 2023 at 14:6 Comment(0)
Y
-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.

Yhvh answered 18/2, 2016 at 18:28 Comment(3)
What is value of thisPosition ?Tallyman
it's the position you want to smooth scroll toBreastfeed
Why not simply use smoothScrollToPosition(int position) without any calculation? Doesn't matter from where you start, does it?Deodar

© 2022 - 2024 — McMap. All rights reserved.