RecyclerView scrollToPosition not trigger scrollLi

2020-05-18 11:18发布

问题:

I'm using RecyclerView, with ScrollListener:

mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener()
{
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState)
        {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy)
        {
            super.onScrolled(recyclerView, dx, dy);
            // Do my logic
        }
 });

When I scroll with the finger, the scroll listener triggered fine.

But when I scroll progrematically, like that:

mRecyclerView.scrollToPosition(LAST_POSITION);

The scroll listener is not triggered.

回答1:

This is a known issue. It is caused by the fact that RecyclerView does not know how LayoutManager will handle the scroll or if it will handle it at all.

In the next release, you'll receive a call to onScrolled if first and or last child position changes after a layout (which is generally the result of calling scroll to position).

Unfortunately, dx and dy will be 0 because RecyclerView does not really know how much layout manager did scroll to handle scrollTo request. Alternatively, you can also use the the ViewTreeObserver's scroll callback.



回答2:

I've faced the same issue and found a workaround, which is to use the smoothScrollToPosition in layout manager. This method triggers the scroll listener. So basically this is what I use to have:

recyclerView.scrollToPosition(recyclerAdapter.getItemCount() - 1);

and now I've changed to this:

recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView, null, recyclerAdapter.getItemCount() - 1);

and it's working fine for me.



回答3:

It's a bit rudimentary, but you could try this Subclass of RecyclerView:

public class PatchedRecyclerView extends RecyclerView {

    private OnScrollListener listener;

    public PatchedRecyclerView(Context context) {
        super(context);
    }

    public PatchedRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public PatchedRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void setOnScrollListener(OnScrollListener listener) {
        super.setOnScrollListener(listener);
        this.listener = listener;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int preFirstChildPosition = getChildCount() != 0 ? getChildViewHolder(getChildAt(0)).getPosition() : -1;
        int preLastChildPosition = getChildCount() != 0 ? getChildViewHolder(getChildAt(getChildCount() - 1)).getPosition() : -1;
        super.onLayout(changed, l, t, r, b);
        int postFirstChildPosition = getChildCount() != 0 ? getChildViewHolder(getChildAt(0)).getPosition() : -1;
        int postLastChildPosition = getChildCount() != 0 ? getChildViewHolder(getChildAt(getChildCount() - 1)).getPosition() : -1;

        // TODO: Insert proper DX and DY values
        if (preFirstChildPosition != postFirstChildPosition || preLastChildPosition != postLastChildPosition)
            listener.onScrolled(this, 0, 0);
    }
}


回答4:

Muito legal seu conselho.

 Button maisOpcoes = (Button) findViewById(R.id.maisOpcoes);
        maisOpcoes.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView, null , recyclerViewTmd.getItemCount() - 1);
                maisOpcoes.setVisibility(View.GONE);
            }
        });


回答5:

int mFirst=0, mLast=0;

recyclerview.setOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
    }
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        LinearLayoutManager llm = (LinearLayoutManager) recyclerview.getLayoutManager();
        mLast = llm.findLastCompletelyVisibleItemPosition();
        mFirst = llm.findFirstCompletelyVisibleItemPosition();
    }
});

imgRight.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        LinearLayoutManager llm = (LinearLayoutManager) recyclerview.getLayoutManager();
        llm.scrollToPositionWithOffset(mLast + 1, List.length());
    }
});

imgLeft.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        LinearLayoutManager llm = (LinearLayoutManager) recyclerview.getLayoutManager();
        llm.scrollToPositionWithOffset(mFirst - 1, List.length());
    }
});


回答6:

To explicitly trigger onScrollListener over recycler view use:

recyclerView.smoothScrollBy(x, y);
//x is the horizontal displacement and y is vertical displacement.


回答7:

I could not use the smoothScrollToPosition (we had some problems with it in the past) and depending on the position of the first item seemed unreliable, so I ended up using yigit's answer (it has been released already), unfortunately it didn't always work (I am not sure why).

So I ended up using a scroll listener I've added previously to the RecyclerView, sadly I cannot find the origin of this solution.
I don't think you should use this listener if you need a constant one, luckily I need to listen only at specific times.

Override the RecyclerView:

public class MyRecyclerView extends RecyclerView {

    public interface OnScrollStoppedListener{
        void onScrollStopped();
    }

    private static final int NEW_CHECK_INTERVAL = 100;
    private OnScrollStoppedListener mOnScrollStoppedListener;
    private Runnable mScrollerTask;
    private int mInitialPosition;

    public MyRecyclerView(Context context) {
        super(context);
        init();
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        mScrollerTask = new Runnable() {
            public void run() {
                int newPosition = getScrollY();
                if(mInitialPosition - newPosition == 0) {//has stopped
                    if(mOnScrollStoppedListener !=null) {
                        mOnScrollStoppedListener.onScrollStopped();
                    }
                } else {
                    startScrollerTask();
                }
            }
        };
    }

    public void setOnScrollStoppedListener(MyRecyclerView.OnScrollStoppedListener listener){
        mOnScrollStoppedListener = listener;
    }

    public void startScrollerTask() {
        mInitialPosition = getScrollY();
        postDelayed(mScrollerTask, NEW_CHECK_INTERVAL);
    }

}  

Usage:

myRecyclerView.setOnScrollStoppedListener(new MyRecyclerView.OnScrollStoppedListener() {
    @Override
    public void onScrollStopped() {
        // Do your thing
    }
});

myRecyclerView.startScrollerTask();


回答8:

Please try with

LinearLayoutManager.scrollToPositionWithOffset(int, int).

LayoutManager.scrollToPosition() may not work well, but LinearLayoutManager.scrollToPositionWithOffset() will work. And similarlly GridLayoutManager as well.

Or we have to write our custom way of implementation because RecyclerView doesn't know how LayoutManager scrolls the view.

    class CustomLayoutManager extends LinearLayoutManager{
         public void smoothScrollToPosition(RecyclerView recyclerView,
                RecyclerView.State state, final int position) {

          LinearSmoothScroller smoothScroller = 
                    new LinearSmoothScroller(mContext) {
        //Override the methods and write your implementation as per your requirement 
       //one which controls the direction and another one calculate the speed per Pixel
        }
    }

Sometimes this does the trick

    new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        yourList.scrollToPosition(position);
    }
}, 200);