RecyclerView scrolling on insert

ehehhh picture ehehhh · Sep 5, 2016 · Viewed 10k times · Source

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.

Answer

Hala.M picture Hala.M · Sep 5, 2016

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

check https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#notifyItemInserted(int)