I want my RecyclerView with LinearLayoutManager to show up with scroll position at specific item after adapter got updated. (not first/last position) Means the at first (re-)layout, this given position should be in visible area. It should not layout with position 0 on top and scroll afterwards to target position.
My Adapter starts with itemCount=0, loads its data in thread and notifies its real count later. But the start position must be set already while count is still 0!
As of now I used some kind of post Runnable
containingscrollToPosition
but this has side effects (starts at first pos and jumps immediately to target position (0 -> target) and seems not to work well with DiffUtil (0 -> target -> 0))
Edit: To clearify: I need alternative to layoutManager.setStackFromEnd(true);
, something like setStackFrom(position)
. ScrollToPosition does not work, if I call it when itemCount is still 0, so it gets ignored. If I call it when I notify that itemCount is now >0, it will layout from 0 and jumps short after to target position. And it fails completely if I use DiffUtil.DiffResult.dispatchUpdatesTo(adapter)`. (shows from 0, then scrolls to target position and then again back to position 0)
I found a solution myself:
I extended the LayoutManager:
class MyLayoutManager extends LinearLayoutManager {
private int mPendingTargetPos = -1;
private int mPendingPosOffset = -1;
@Override
public void onLayoutChildren(Recycler recycler, State state) {
if (mPendingTargetPos != -1 && state.getItemCount() > 0) {
/*
Data is present now, we can set the real scroll position
*/
scrollToPositionWithOffset(mPendingTargetPos, mPendingPosOffset);
mPendingTargetPos = -1;
mPendingPosOffset = -1;
}
super.onLayoutChildren(recycler, state);
}
@Override
public void onRestoreInstanceState(Parcelable state) {
/*
May be needed depending on your implementation.
Ignore target start position if InstanceState is available (page existed before already, keep position that user scrolled to)
*/
mPendingTargetPos = -1;
mPendingPosOffset = -1;
super.onRestoreInstanceState(state);
}
/**
* Sets a start position that will be used <b>as soon as data is available</b>.
* May be used if your Adapter starts with itemCount=0 (async data loading) but you need to
* set the start position already at this time. As soon as itemCount > 0,
* it will set the scrollPosition, so that given itemPosition is visible.
* @param position
* @param offset
*/
public void setTargetStartPos(int position, int offset) {
mPendingTargetPos = position;
mPendingPosOffset = offset;
}
}
It stores my target position. If onLayoutChildren
is called by RecyclerView, it checks if adapters itemCount is already > 0. If true, it calls scrollToPositionWithOffset()
.
So I can tell immediately what position should be visible, but it will not be told to LayoutManager before position exists in Adapter.