Reputation: 2023
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
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
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
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
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