39

I want my RecyclerView to scroll to the bottom when a new item is added to the list. Below is my code:

RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity()); recyclerView.setLayoutManager(layoutManager); adapter = new RecyclerViewAdapter(data, recyclerView); recyclerView.setAdapter(adapter); 

And when I click a button which is below the RecyclerView, data is added to the list:

public void addNewCard() { data.add("New"); adapter.notifyItemInserted(data.size() - 1); recyclerView.scrollToPosition(data.size() - 1); } 

Whenever I add an item, it always gets scrolled up to the topmost position. I tried using smoothScrollToPosition() and tried using scrollToPosition() on LinearLayoutManager too. I even tried using ViewTreeObserver. Tried changing the version of RecyclerView in dependencies too. I searched thoroughly on Stack Overflow and none of the solutions work for me.

Any ideas on what could be the problem? I'm using RecyclerView inside a fragment. Could that be a cause of the problem?

My .xml file for the RecyclerView

<?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/scroll_view_recycler" android:layout_width="match_parent" android:layout_height="match_parent"> <RelativeLayout android:id="@+id/rel_layout" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.RecyclerView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="6dp" /> <Button android:id="@+id/button_add_new_card" style="@style/Widget.AppCompat.Button.Borderless" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/listView" android:layout_marginBottom="16dp" android:layout_marginTop="8dp" android:padding="20dp" android:text="+ Add new Item" /> </RelativeLayout> </android.support.v4.widget.NestedScrollView> 
14
  • 1
    Are you using RecyclerView inside ScrollView? Commented Nov 12, 2017 at 16:09
  • @Deepa yes I'm using it inside a RelativeLayout which is inside a ScrollView Commented Nov 12, 2017 at 16:14
  • 2
    Then try to use NestedSrollView instead of ScrollView. Or use this method for RecyclerView layoutManager.setStackFromEnd(true); Commented Nov 12, 2017 at 16:20
  • @Deepa tried both. Doesn't work! :( Commented Nov 12, 2017 at 16:28
  • Are you using GridLayoutManager? In that case it will not support setStackFromEnd(). Commented Nov 12, 2017 at 16:31

9 Answers 9

31

If want to add the data to the bottom of the list you need to use setStackFromEnd() in RecyclerView Layout Manager.

But first, you need to fix your Adapter. You must not pass your RecylerView to your Adapter. So the following code is wrong:

... // This is wrong!! adapter = new RecyclerViewAdapter(data, recyclerView); recyclerView.setAdapter(adapter); 

You need to change your Adapter constructor to only receive the data as its parameter. Something like this:

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> { private List<Data> mData; public RecyclerViewAdapter(List<Data> data) { this.mData = data; } ... } 

Then you can set the data to always added at the last bottom with the following code:

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); linearLayoutManager.setStackFromEnd(true); recyclerView.setLayoutManager(linearLayoutManager); adapter = new RecyclerViewAdapter(data); recyclerView.setAdapter(adapter); 

To add the new data, you better to make a new method in the adapter. Something like this:

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> { private List<Data> mData; ... public void addItem(Data datum) { mData.add(datum); notifyItemInserted(mData.size()); } } 

Whenever you have adding a new data, you need to scroll to the bottom with scrollToPosition method. Something like this:

adapter.addItem(newData); recyclerView.scrollToPosition(adapter.getItemCount() - 1); 

Remember that you need to override getItemCount() in your Adapter. It should be something like this:

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> { private List<Data> mData; public RecyclerViewAdapter(List<Data> data) { this.mData = data; } // Return the total count of items @Override public int getItemCount() { return mData.size(); } ... } 

Please be aware that I'm using a Data pojo as a sample here. You need to change it according to your data type.

Sign up to request clarification or add additional context in comments.

6 Comments

Thank you. But I have tried this and it doesn't work!
What doesn't work? can you create a simple project just for only the RecyclerView and adapter. I'm using the above code in my project.
I have added my .xml file including the RecyclerView. Can you see if there's a problem with it?
You are right. I created a simple project and did the same with RecyclerView and RecyclerView.Adapter. It works fine. Apparently the problem is somewhere else. I'll look at it. Thanks for your time!
Glad it help you a little bit! Good luck on your endeavor ;)
|
15

This was the solution I came up with, which was good enough for the basics, and didn't require any custom adapters.

myAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { myRecyclerView.scrollToPosition(positionStart) } }) 

1 Comment

In case you are using paging, check for positionStart != 0 and then scroll
9
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { super.onItemRangeInserted(positionStart, itemCount) val msgCount = adapter.getItemCount() val lastVisiblePosition = linearLayoutManager.findLastCompletelyVisibleItemPosition() if (lastVisiblePosition == -1 || positionStart >= msgCount - 1 && lastVisiblePosition == positionStart - 1) { recyclerView.scrollToPosition(positionStart) } else { recyclerView.scrollToPosition(adapter.getItemCount() - 1); } } }) 

Comments

4

Alternatively, if you are using ListAdapter with DiffUtill and submit new list through submitList(), you could use ListAdapter's onCurrentListChanged. For example:

override fun onCurrentListChanged(previousList: MutableList<Item>, currentList: MutableList<Item>) { super.onCurrentListChanged(previousList, currentList) //E.g. check if new item has been added if (currentList.size == previousList.size + 1) { recyclerView.scrollToPosition(currentList.size - 1) } } 

Comments

1

ישו אוהב אותך's solution worked on other projects. But setting recyclerView.scrollToPosition() didn't work for me. This is what finally did the job:

public void addNewCard() { data.add("New"); adapter.notifyItemInserted(data.size()); scrollView.post(new Runnable() { @Override public void run() { scrollView.scrollTo(0,buttonAddNewItem.getTop()); } }); } 

Comments

1

Maybe this will help someone

@Override public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) { if (pingAdapter != null) { Ping ping = dataSnapshot.getValue(Ping.class); pingAdapter.pings.add(ping); recyclerView.smoothScrollToPosition(pingAdapter.getItemCount()); pingAdapter.notifyDataSetChanged(); } } 
recyclerView.smoothScrollToPosition(pingAdapter.pings.size()); 

is even faster

2 Comments

I can't locate onChildAdded or DataSnapshot in RecyclerView's source
Because it is a method by com.google.firebase.database.DatabaseReference.addChildEventListener, not RecyclerView.
1

It took me quite some time to get Recycler View to scroll to show the new added item. I got it to work by adding scrollToPosition in the Observer of the list of items coming from database. Here is my approach:

viewModel.itemList.observe(this, Observer { list -> list?.let { val numNewItems = it.size - recyclerViewAdapter.itemCount recyclerViewAdapter.updateList(it) /* Scroll to top to show new item if one was added */ if (numNewItems > 0) { // position could be it.size - 1 for bottom or 0 for top recyclerView.scrollToPosition(position) } } }) 

Comments

0

This is happening because your

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

is getting called first and then the list is being initialized. You need to add this line after your recycler View view is initialized. You can add this line in a handler.

Comments

0

This is working for me.

adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onChanged() { super.onChanged() if (canShowLive()) wvl_log_rv.scrollToPosition(adapter.itemCount - 1) } }) 

Tested & Verified

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.