miroslavign
miroslavign

Reputation: 2023

Diffutil in recycleview, making it autoscroll if a new item is added

If we use DiffUtil.Callback, and do

adapter.setItems(itemList);
diff.dispatchUpdatesTo(adapter);

how can we make sure that adding of new elements will scroll to that new position.

I have a case where I see item disappear, and a new one is created as a first element at the top, but not visible. It is hidden on top until you scroll down to make it visible. Before using DiffUtil, I was implementing this manually, and after I knew I was inserting at some position (on top) I could scroll to.

Upvotes: 32

Views: 18609

Answers (4)

Prem Thakur
Prem Thakur

Reputation: 200

It worked for me

        recyclerview.setItemAnimator(new DefaultItemAnimator() {
            @Override
            public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull List<Object> payloads) {
                return true;
            }
        });
        adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
            @Override
            public void onItemRangeInserted(int positionStart, int itemCount) {
                //when a new item is inserted
                if (positionStart == 0 && itemCount == 1/*number of item inserted*/) {
                    scrollToFirst();
                    mRecordListAdapter.unregisterAdapterDataObserver(this);
                }
            }

        });



    private void scrollToFirst() {
        //when an item is added to the first position
        //however as per the recyclerview the view is added out of the visible area
        //(as the 2nd item after update was the first item before list update)
        //thus the newly item won't be visible
        //Therefore if the 1st item is visible then scroll to the first pos
        //so that the newly item can be visible when inserted

        var position = ((LinearLayoutManager) recyclerview.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
        if (position < 1 && position != RecyclerView.NO_POSITION) {
            recyclerview.scrollToPosition(0);
        }
    }

Upvotes: 1

gregdev
gregdev

Reputation: 1953

I used RecyclerView.AdapterDataObserver to detect when new items were added, and then scroll to the top of my list;

adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {
        super.onItemRangeInserted(positionStart, itemCount);
        recycleView.smoothScrollToPosition(0);
    }
});

Upvotes: 20

Lorne Laliberte
Lorne Laliberte

Reputation: 6311

There's an easy way to do this that also preserves the user's scroll position if items are inserted outside the viewable area:

import android.os.Parcelable;

Parcelable recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();
// apply diff result here (dispatch updates to the adapter)
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);              

Using this approach, the new items are made visible if they are inserted where the user can see them -- but the user's viewpoint is preserved if the items are inserted outside of view.

Upvotes: 79

tynn
tynn

Reputation: 39873

You have a dispatchUpdatesTo(ListUpdateCallback) method to use as well.

So you could just implement a ListUpdateCallback which gives you the first element inserted

class MyCallback implements ListUpdateCallback {
    int firstInsert = -1;
    Adapter adapter = null;
    void bind(Adapter adapter) {
        this.adapter = adapter;
    }
    public void onChanged(int position, int count, Object payload) {
        adapter.notifyItemRangeChanged(position, count, payload);
    }
    public void onInserted(int position, int count) {
        if (firstInsert == -1 || firstInsert > position) {
            firstInsert = position;
        }
        adapter.notifyItemRangeInserted(position, count);
    }
    public void onMoved(int fromPosition, int toPosition) {
        adapter.notifyItemMoved(fromPosition, toPosition);
    }
    public void onRemoved(int position, int count) {
        adapter.notifyItemRangeRemoved(position, count);
    }
}

and then just scroll the RecyclerView manually

myCallback.bind(adapter)
adapter.setItems(itemList);
diff.dispatchUpdatesTo(myCallback);
recycler.smoothScrollToPosition(myCallback.firstInsert);

Upvotes: 23

Related Questions