Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to tell RecyclerView to start at specific item position

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)

like image 788
allofmex Avatar asked Jul 24 '18 13:07

allofmex


People also ask

How do I get the first visible position of RecyclerView?

For example: GridLayoutManager layoutManager = ((GridLayoutManager)mRecyclerView. getLayoutManager()); int firstVisiblePosition = layoutManager. findFirstVisibleItemPosition();

How do I get the selected item position in the adapter?

You do not have to use any extra method. Just create a global variable named 'position' and initialize it with getAdapterPosition() in any of the major method of the adapter (class or similar). Here is a brief documentation from this link. getAdapterPosition added in version 22.1.


1 Answers

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.

like image 194
allofmex Avatar answered Nov 25 '22 15:11

allofmex