How to tell RecyclerView to start at specific item position

雨燕双飞 提交于 2020-01-23 06:13:58

问题


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)


回答1:


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 may target position. If onLayoutChildren is called by RecyclerView, it checks if 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.




回答2:


You can try this, it will scroll to a position you want:

rv.getLayoutManager().scrollToPosition(positionInTheAdapter).



回答3:


If you want to scroll to a specific position and that position is the adapter's position, then you can use StaggeredGridLayoutManager scrollToPosition

StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
       staggeredGridLayoutManager.scrollToPosition(10);
       recyclerView.setLayoutManager(staggeredGridLayoutManager);

If I understand the question, you want to scroll to a specific position but that position is the adapter's position and not the RecyclerView's item position.

You can only achieve this through the LayoutManager.

rv.getLayoutManager().scrollToPosition(youPositionInTheAdapter);



回答4:


None of the methods above worked for me. I did the following using ViewTreeObserver that is triggered once its children have been added/changed visibility.

recyclerView.apply {
   adapter = ...
   layoutManager = ...

   val itemCount = adapter?.itemCount ?: 0
   if(itemCount > 1) {
      viewTreeObserver.addOnGlobalLayoutListener(object: ViewTreeObserver.OnGlobalLayoutListener {
      override fun onGlobalLayout() {
         viewTreeObserver.removeOnGlobalLayoutListener(this)
         (layoutManager as? LinearLayoutManager)?.scrollToPosition(#PositionToStartAt)
      }
   }
}

Go ahead and set #PositionToStartAt to a specific value. You can also ensure that the RecyclerView initial position setting gets triggered once a specific number of children have been laid out to ensure it is set correctly.

if(recyclerView.childCount() > #PositionCheck) {
   viewTreeObserver.removeOnGlobalLayoutListener(this)
   (layoutManager as? LinearLayoutManager)?.scrollToPosition(#PositionToStartAt)
}


来源:https://stackoverflow.com/questions/51499834/how-to-tell-recyclerview-to-start-at-specific-item-position

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!