I am trying to use RecyclerView
to create a chat application. I am using a LinearLayoutManager
with setReverseLayout(true)
.
When I am scrolled all the way to the bottom (which is the dataset start = newest message) and a new message is inserted into the dataset, the item appears at the bottom of the list as expected (the view is scrolled up to make room for the new item).
The problem I have is when I have scrolled up to see the older messages. When a new message is inserted to the beginning of the dataset, the view is scrolled up by approximately one message height, even though that message isn't even rendered since it's out of the viewport's range.
How can I keep the scrolling behavior, when the view is scrolled to the bottom, but disable it when I have scrolled to the older messages?
UPDATE: I also made a small app, where this problem is reproduced: https://github.com/ehehhh/RecyclerViewProblem
UPDATE 2: I committed the fix that worked to the repo I made as well.
Relevant code (hopefully):
compile 'com.android.support:recyclerview-v7:24.2.0'
XML:
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="8dp"
android:paddingTop="8dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
RecyclerView init code:
layoutManager = new LinearLayoutManager(context);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
layoutManager.setReverseLayout(true);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setScrollContainer(true);
recyclerView.setLayoutAnimation(null);
recyclerView.setItemAnimator(null);
adapter = new ChatAdapter(...);
recyclerView.setAdapter(adapter);
Adapter:
public class ChatAdapter extends RecyclerView.Adapter<ChatViewHolder> {
private List<MessageWrapper> dataset;
public ChatAdapter(List<MessageWrapper> dataset, ...) {
this.dataset = dataset;
setHasStableIds(true);
}
...
@Override
public long getItemId(int position) {
return dataset.get(position).getId();
}
@Override
public int getItemCount() {
return dataset.size();
}
public void datasetChanged(List<MessageWrapper> dataset) {
this.dataset = dataset;
notifyDataSetChanged();
}
}
When a new item is added to the dataset, I just call the datasetChanged
method in the adapter.
in Recycler view using notifyDataSetChanged is redundant if you know the items changed you can use
notifyItemInserted(position)
in this particular case what worked was
notifyItemInserted(0);
or
notifyItemRangeInserted(positionStart, newItems.size() - 1)
this will only rebind the views in this range