193

I'd like to scroll to the bottom of the RecyclerView list after loading the activity.

GENERIC_MESSAGE_LIST = (ArrayList) intent.getExtras().getParcelableArrayList(ConversationsAdapter.EXTRA_MESSAGE);
conversationView = (RecyclerView) findViewById(R.id.list_messages);
conversationView.setHasFixedSize(true);
conversationViewLayoutManager = new LinearLayoutManager(this);
conversationView.setLayoutManager(conversationViewLayoutManager);
conversationViewAdapter = new ConversationAdapter(GENERIC_MESSAGE_LIST, this);
conversationView.setAdapter(conversationViewAdapter);

conversationView.scrollTo(...) throws an exception about being not supported in RecyclerView, and conversationView.scrollToPosition(...) doesn't seem to do anything.

After the above block of code, I added

conversationView.scrollToPosition(GENERIC_MESSAGE_LIST.size() + 1)

which doesn't work. There are 30 elements in GENERIC_MESSAGE_LIST.

waylaidwanderer
  • 2,051
  • 2
  • 15
  • 9
  • Are you calling `scrollToPosition` immediately after setting the adapter? – ianhanniballake Oct 27 '14 at 03:42
  • @ianhanniballake yes, it's right after `conversationView.setAdapter(conversationViewAdapter);`. – waylaidwanderer Oct 27 '14 at 05:30
  • 4
    It is probably because you are calling +1 instead of -1 so the 5th element would be position [4] because it starts at 0. Doing +1 would give you an ArrayOutOfBounds ... did it not crash there? – RCB Feb 12 '16 at 16:43
  • 1
    Add after setadapter mRecyclerView.scrollToPosition(carsList.size()-1); – Codelaby Sep 23 '16 at 08:56

29 Answers29

442

I was looking at this post to find the answer but... I think everyone on this post was facing the same scenario as me: scrollToPosition() was fully ignored, for an evident reason.

What I was using?

recyclerView.scrollToPosition(items.size());

... what WORKED?

recyclerView.scrollToPosition(items.size() - 1);
Roc Boronat
  • 11,395
  • 5
  • 47
  • 59
  • 7
    `recyclerView.scrollToPosition(messages.size()-1);` really works for me, I just wonder the reason. – Engine Bai Mar 06 '15 at 10:01
  • 26
    This worked for me. It actually makes sense because Java lists are zero based. E.g. list [0,1,2] has a size of 3 but the last position is 2 because it starts with 0. – Calvin Mar 20 '15 at 07:46
  • 22
    FYI, for a smooth scroll, there is `smoothScrollToPosition(...)`. – Ivan Morgillo Jul 17 '15 at 08:17
  • 6
    @Ivan Morgilo smoothScrollToPosition doesn't work at all :S – cesards Jul 30 '15 at 07:59
  • 2
    @IvanMorgillo - your answer seems to work better both with the result wanted and more smooth =D. seems like scroll to position is a bit buggy for me (my guess is that there's a small delay between the function called and the creation of the view? anyways it's not working properly) – Roee Jul 04 '16 at 15:42
  • I tested manually by recyclerView.scrollToPosition(4), and then realized that when using adapter.getItemCount() I am getting one more than required. – Ishan Aug 29 '16 at 21:35
  • haha, you are correct. I totally forgot that position starts at 0, while list.size() starts from 1. That is why it didn't work. Thanks man – M.Baraka Jun 12 '17 at 11:35
  • 1
    for those with `reverseLayout = true` and `stackFromEnd = true` set, scroll to `0` because it's the bottom most item. – CHAN Jun 20 '19 at 10:37
226

Just set setStackFromEnd=true or setReverseLayout=true so that LLM will layout items from end.

The difference between these two is that setStackFromEnd will set the view to show the last element, the layout direction will remain the same. (So, in an left-to-right horizontal Recycler View, the last element will be shown and scrolling to the left will show the earlier elements)

Whereas setReverseLayout will change the order of the elements added by the Adapter. The layout will start from the last element, which will be the left-most in an LTR Recycler View and then, scrolling to the right will show the earlier elements.

Sample:

final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
linearLayoutManager.setReverseLayout(true);
_listView.setLayoutManager(linearLayoutManager);

See documentation for details.

IcyFlame
  • 5,059
  • 21
  • 50
  • 74
yigit
  • 37,683
  • 13
  • 72
  • 58
61

I know its late to answer here, still if anybody want to know solution is below

conversationView.smoothScrollToPosition(conversationView.getAdapter().getItemCount() - 1);
Stan Kurilin
  • 15,614
  • 21
  • 81
  • 132
42

To scrolldown from any position in the recyclerview to bottom

Kotlin

editText.setOnClickListener {
    rv.postDelayed({                 
        rv.scrollToPosition(rv.adapter.itemCount - 1)
    }, 1000)
}

Java

edittext.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                rv.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                      rv.scrollToPosition(rv.getAdapter().getItemCount() - 1);
                    }
                }, 1000);
            }
        });
Muhammad Umair Shafique
  • 2,475
  • 1
  • 30
  • 39
  • Thanks. Adding the delay solved the issue I was having. I have a recyclerview (which can expand and collapse) inside another recyclerview. The expand/collapse animation is 300ms so I just needed to add that delay. But for smoother scroll I used smoothScrollToPosition instead. – Gemeaux Jan 10 '23 at 17:02
17

Add this code after sending message and before getting message from server

recyclerView.scrollToPosition(mChatList.size() - 1);
Shubham Agrawal
  • 1,252
  • 12
  • 29
12

When you call setAdapter, that does not immediately lay out and position items on the screen (that takes a single layout pass) hence your scrollToPosition() call has no actual elements to scroll to when you call it.

Instead, you should register a ViewTreeObserver.OnGlobalLayoutListener (via addOnGlobalLayoutListner() from a ViewTreeObserver created by conversationView.getViewTreeObserver()) which delays your scrollToPosition() until after the first layout pass:

conversationView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  public void onGlobalLayout() {
    conversationView.scrollToPosition(GENERIC_MESSAGE_LIST.size();
    // Unregister the listener to only call scrollToPosition once
    conversationView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

    // Use vto.removeOnGlobalLayoutListener(this) on API16+ devices as 
    // removeGlobalOnLayoutListener is deprecated.
    // They do the same thing, just a rename so your choice.
  }
});
Saket
  • 2,945
  • 1
  • 29
  • 31
ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
  • 1
    Thanks, but this doesn't work. It breaks scrolling by stopping flinging from working, and if you drag, it scrolls by oddly fast. – waylaidwanderer Oct 27 '14 at 08:22
  • Sounds like you aren't removing the listener. It should only be called exactly once (right after the elements are first laid out) and not at all during scrolling. – ianhanniballake Oct 27 '14 at 13:47
  • `OnGlobalLayoutListener` probably not getting removed because you're checking for `vto.isAlive()`. I don't know the reason, but `ViewTreeObserver` sometimes is changed for a `View`, so instead of checking for `isAlive` you better just get current `ViewTreeObserver` with `conversationView.getViewTreeObserver().removeGlobalOnLayoutListener` – Dmitry Zaytsev Jan 12 '15 at 11:29
  • @ianhanniballake I've proposed an edit to fix this issue. – Saket Dec 03 '17 at 14:01
8

The answer is

recyclerView.scrollToPosition(arrayList.size() - 1);

Everyone has mentioned it.

But the problem I was facing was that it was not placed correctly.

I tried and placed just after the adapter.notifyDataSetChanged(); and it worked. Whenever, data in your recycler view changes, it automatically scrolls to the bottom like after sending messages or you open the chat list for the first time.

Note : This code was tasted in Java.

actual code for me was :

//scroll to bottom after sending message.
binding.chatRecyclerView.scrollToPosition(messageArrayList.size() - 1);
Badri Paudel
  • 1,365
  • 18
  • 19
7

IF you have adapter attached with recyclerView then just do this.

mRecyclerView.smoothScrollToPosition(mRecyclerView.getAdapter().getItemCount());

Robert
  • 5,278
  • 43
  • 65
  • 115
Wasim Amin
  • 71
  • 1
  • 5
7

Solution for Kotlin:

apply below code after setting "recyclerView.adapter" or after "recyclerView.adapter.notifyDataSetChanged()"

recyclerView.scrollToPosition(recyclerView.adapter.itemCount - 1)
AbhinayMe
  • 2,399
  • 1
  • 20
  • 21
6

If you are having issues making recyclerview scroll to the latest, check that the recyclerview is not inside a nestedScrollView. That was my issue. I had to remove the nestedScrollview and then the below code worked.

binding.recyclerView.scrollToPosition(adapter.getItemCount() - 1);
Ismail Osunlana
  • 434
  • 5
  • 9
5
class MyLayoutManager extends LinearLayoutManager {

  public MyLayoutManager(Context context) {
    super(context, LinearLayoutManager.VERTICAL, false);
  }

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

    int fcvip = findFirstCompletelyVisibleItemPosition();
    int lcvip = findLastCompletelyVisibleItemPosition();

    if (position < fcvip || lcvip < position) {
      // scrolling to invisible position

      float fcviY = findViewByPosition(fcvip).getY();
      float lcviY = findViewByPosition(lcvip).getY();

      recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {

        int currentState = RecyclerView.SCROLL_STATE_IDLE;

        @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) {

          if (currentState == RecyclerView.SCROLL_STATE_SETTLING
              && newState == RecyclerView.SCROLL_STATE_IDLE) {

            // recursive scrolling
            smoothScrollToPosition(recyclerView, state, position);
          }

          currentState = newState;
        }

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

          int fcvip = findFirstCompletelyVisibleItemPosition();
          int lcvip = findLastCompletelyVisibleItemPosition();

          if ((dy < 0 && fcvip == position) || (dy > 0 && lcvip == position)) {
            // stop scrolling
            recyclerView.setOnScrollListener(null);
          }
        }
      });

      if (position < fcvip) {
        // scroll up

        recyclerView.smoothScrollBy(0, (int) (fcviY - lcviY));
      } else {
        // scroll down

        recyclerView.smoothScrollBy(0, (int) (lcviY - fcviY));
      }
    } else {
      // scrolling to visible position

      float fromY = findViewByPosition(fcvip).getY();
      float targetY = findViewByPosition(position).getY();

      recyclerView.smoothScrollBy(0, (int) (targetY - fromY));
    }
  }
}

and

MyLayoutManager layoutManager = new MyLayoutManager(context);
recyclerView.setLayoutManager(layoutManager);

RecyclerView.Adapter adapter = new YourAdapter();
recyclerView.setAdapter(adapter);

recyclerView.smoothScrollToPosition(adapter.getItemCount() - 1);

above code works, but it's not smooth and not cool.

Yuki Yoshida
  • 1,233
  • 1
  • 15
  • 28
5

Roc answer is a great help. I would like to add a small block to it:

mRecyclerView.scrollToPosition(mAdapter.getItemCount() - 1);
abedfar
  • 1,989
  • 24
  • 21
5

In Kotlin:

recyclerView.viewTreeObserver.addOnGlobalLayoutListener { scrollToEnd() }

private fun scrollToEnd() =
        (adapter.itemCount - 1).takeIf { it > 0 }?.let(recyclerView::smoothScrollToPosition)
yanchenko
  • 56,576
  • 33
  • 147
  • 165
  • Hah, thanks for `GlobalLayoutListener`! After refactoring I had to use your method to scroll. Don't forget to remove the listener like in https://stackoverflow.com/questions/28264139/view-getviewtreeobserver-addongloballayoutlistener-leaks-fragment, or you won't scroll RecyclerView back to beginning. – CoolMind May 22 '19 at 16:45
4

In my case where views do not have the same height, calling scrollToPosition on the LayoutManager worked to really scroll to the bottom and see fully the last item:

recycler.getLayoutManager().scrollToPosition(adapter.getItemCount() - 1);
galex
  • 3,279
  • 2
  • 34
  • 46
3

First time scroll when entering in recycler view first time then use

linearLayoutManager.scrollToPositionWithOffset(messageHashMap.size()-1

put in minus for scroll down for scroll up put in positive value);

if the view is very big in height then scrolltoposition particular offset is used for the top of view then you use

int overallXScroldl =chatMessageBinding.rvChat.computeVerticalScrollOffset();
chatMessageBinding.rvChat.smoothScrollBy(0, Math.abs(overallXScroldl));
Amir Hossein Ghasemi
  • 20,623
  • 10
  • 57
  • 53
Dishant Kawatra
  • 638
  • 7
  • 5
2

This works perfectly fine for me:

AdapterChart adapterChart = new AdapterChart(getContext(),messageList);
recyclerView.setAdapter(adapterChart);
recyclerView.scrollToPosition(recyclerView.getAdapter().getItemCount()-1);
fantaghirocco
  • 4,761
  • 6
  • 38
  • 48
amaresh hiremani
  • 533
  • 1
  • 8
  • 19
2

You can scroll to the bottom of the last item, if the height of the last item is too large, you need to offset

 private void scrollToBottom(final RecyclerView recyclerView) {
        // scroll to last item to get the view of last item
        final LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        final RecyclerView.Adapter adapter = recyclerView.getAdapter();
        final int lastItemPosition = adapter.getItemCount() - 1;
    
        layoutManager.scrollToPositionWithOffset(lastItemPosition, 0);
        recyclerView.post(new Runnable() {
            @Override
            public void run() {
                // then scroll to specific offset
                View target = layoutManager.findViewByPosition(lastItemPosition);
                if (target != null) {
                    int offset = recyclerView.getMeasuredHeight() - target.getMeasuredHeight();
                    layoutManager.scrollToPositionWithOffset(lastItemPosition, offset);
                }
            }
        });



    }
jiong103
  • 111
  • 7
1

this code will give you latest post first, i think this answer is helpful.

    mInstaList=(RecyclerView)findViewById(R.id.insta_list);
    mInstaList.setHasFixedSize(true);

    LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    layoutManager.setOrientation(LinearLayoutManager.VERTICAL);

    mInstaList.setLayoutManager(layoutManager);
    layoutManager.setStackFromEnd(true);
    layoutManager.setReverseLayout(true);
1

Tried a method of @galex, it worked until refactoring. So I used an answer of @yanchenko and changed a bit. Probably this is because I called scrolling from onCreateView(), where a fragment view was built (and probably didn't have right size).

private fun scrollPhotosToEnd(view: View) {
    view.recycler_view.viewTreeObserver.addOnGlobalLayoutListener(object :
        ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                view.recycler_view.viewTreeObserver.removeOnGlobalLayoutListener(this)
            } else {
                @Suppress("DEPRECATION")
                view.recycler_view.viewTreeObserver.removeGlobalOnLayoutListener(this)
            }
            adapter?.itemCount?.takeIf { it > 0 }?.let {
                view.recycler_view.scrollToPosition(it - 1)
            }
        }
    })
}

You can also add a check of viewTreeObserver.isAlive like in https://stackoverflow.com/a/39001731/2914140.

CoolMind
  • 26,736
  • 15
  • 188
  • 224
1

You must hide AppBarLayout before scrollToPosition if you are using him

Evgenii
  • 105
  • 1
  • 9
1

I know this is too late but this worked for me so that i am answering this question

first,

LinearLayoutManager layoutManager=new LinearLayoutManager(this);
layoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(layoutManager);

after that,

database.addValueEventListener(....){
@override
public void onDataCange(...){
....
....
//put this line here
recyclerView.smoothScrollToPosition(recyclerView.getAdapter().getItemCount()-1);
}
}
1

if you use kotlin, try this

conversationView.post {
    scrollToPosition(conversationView?.adapter.itemCount - 1)
}
Kiwan Park
  • 81
  • 4
0

Only Ian's answer was able to make my RecyclerView scroll to a specified position. However, The RecyclerView was not able to scroll afterwards when I used scrollToPosition(). smoothScrollToPosition() worked but the initial animation made it too slow when the list was long. The reason was the listener was not removed. I used the code below to remove the current ViewTreeObserver and it worked as a charm.

    mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            mRecyclerView.scrollToPosition(mPosition);
            mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
    });
Anky An
  • 620
  • 7
  • 19
0

If none of these worked,

you should try to use :

ConstraintLayout targetView = (ConstraintLayout) recyclerView.findViewHolderForAdapterPosition(adapter.getItemCount()-1).itemView;
targetView.getParent().requestChildFocus(targetView, targetView);

By doing this, you are requesting a certain ConstraintLayout (Or whatever you have) to be displayed. The scroll is instant.

I works even with keyboard shown.

Arnaud
  • 408
  • 4
  • 11
0

If you want just starting from the end use:

layoutManager.stackFromEnd = true

(Kotlin solution)

0

If you working with reversedLayout "chat interface" specially if you have images, you can try the following:

first, register DataObserver with the adapter

   chatAdapter.registerAdapterDataObserver(object : 
                         RecyclerView.AdapterDataObserver() {

   override fun onItemRangeInserted(positionStart: Int itemCount: Int) {

      val messageCount: Int = chatAdapter.itemCount

      val lastVisiblePosition: Int =
            layoutManager.findLastCompletelyVisibleItemPosition()

         if (lastVisiblePosition == -1 || positionStart <= (messageCount - 1)) {
            recyclerView.scrollToPosition(positionStart)
        }
    }
}) 

this will be called once you set the data for the adapter

second, you have to add a scroll listener to the recyclerView like this:

   recyclerView.addOnScrollListener(object 
     :RecyclerView.OnScrollListener() {

       override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: 
          Int) {
            super.onScrolled(recyclerView, dx, dy)

          val lastVisiblePosition: Int =
              layoutManager.findLastCompletelyVisibleItemPosition()
        if (lastVisiblePosition != -1 && lastVisiblePosition < 
                chatAdapter.itemCount - 1) {
            recyclerView.smoothScrollToPosition(0)
        }
    }
})
Aaeb
  • 1,276
  • 11
  • 10
  • the AdapterDataObserver will give you successfully the insert event of data, however this does not imply that the ui is ready to scroll to new positions just yet, because the view you want to scroll to is not available yet – Kibotu Sep 05 '22 at 17:14
0

use

video_frames?.smoothScrollToPosition(pos.toInt())

this work for me*

SwissCodeMen
  • 4,222
  • 8
  • 24
  • 34
Akshay
  • 1
0

add this code for scroll to top mRecyclerView.scrollToPosition(0)

Karam J
  • 1
  • 2
-4

use only gravity for easy solution

android:layout_gravity="bottom"