ChatGPT解决这个技术问题 Extra ChatGPT

Scroll RecyclerView to show selected item on top

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?


m
mohammadReza Abiri

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

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());
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 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);
What if I want to scroll to the top "smoothly"?
@Tiago, I haven't played around with it, but this 3-part series of articles may help: blog.stylingandroid.com/scrolling-recyclerview-part-1
I
ItamarG3

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


Really useful answer! I also override getHorizontalSnapPreference() in the TopSnappedSmoothScroller for Horizontal lists.
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...)
sometime the item dosent get focused, i am having a focus drawable for item view.
@david.mihola Have you found a 'right' way? I've set custom padding to my recyclerview but I'm having some weird problems...
H
HBQ

//Scroll item pos

linearLayoutManager.scrollToPositionWithOffset(pos, 0);

is there any way to do this smoothly?
@MarkBuikema I found it before. thanx for your attention dude
V
Victor Häggqvist

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.


This worked for me - recyclerView.scrollToPosition(0); put me at the top.
that not working you have to use the linearLayoutManager.scrollToPositionWithOffset(pos, 0);
@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).
@TheincredibleJan it doesn't work in all cases, the layoutManager is generally responsible for the scrolling anyhow just for your information.
This doesn't always put the requested item on the top as the question asked.
g
gianlucaparadise

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

Amazing! The simplest solution is if you need a smooth scroll with the target element showing at the top of the RecyclerView! Thanks!
A
Amir Hossein Ghasemi

just call this method simply:

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

instead of:

recyclerView.scrollToPosition(yourItemPosition);

"instead of"? Distwo didn't mention "scrollToPosition". If he had used it there wouldn't have been any problem. scrollToPosition() as intended.
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.
u
user2212515

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

p
psicopante

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/


L
Lucem

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

recyclerView.smoothScrollToPosition(position);

No errors

Creates Animations


where will you put this in activity or adapter?
This won't scroll clicked item to the top - which was the problem in the first place.
D
Distwo

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.


B
Beloo

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

You mentioned great point: "changes happens in a background thread and then asynchronously notifies adapter about changes". TNX.
A
Anatolii

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:

https://i.stack.imgur.com/oGfwI.png

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

    ...
} 
...

M
Mohamed Nageh

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

S
Sandeep Singh

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

layoutmanager.scrollToPosition(int position);

L
Linh

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());

F
FxRi4

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


T
Tarif Chakder

This is pretty simple

recyclerView.scrollToPosition(position)

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
If you want top use recyclerView.scrollToPosition(0)
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?
f
feheren.fekete

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.


What is value of thisPosition ?
it's the position you want to smooth scroll to
Why not simply use smoothScrollToPosition(int position) without any calculation? Doesn't matter from where you start, does it?