RecyclerView scrollToPosition 不触发 scrollListener

RecyclerView scrollToPosition not trigger scrollListener

我正在使用带有 ScrollListener 的 RecyclerView:

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
        }
 });

当我用手指滚动时,滚动侦听器触发正常。

但是当我按顺序滚动时,就像那样:

mRecyclerView.scrollToPosition(LAST_POSITION);

没有触发滚动监听。

这是一个已知问题。这是因为 RecyclerView 不知道 LayoutManager 将如何处理滚动或者它是否会处理它。

在下一个版本中,如果第一个和/或最后一个子位置在布局后发生变化(这通常是调用滚动到位置的结果),您将收到对 onScrolled 的调用。

不幸的是,dx 和 dy 将为 0,因为 RecyclerView 并不真正知道布局管理器滚动了多少来处理 scrollTo 请求。或者,您也可以使用 ViewTreeObserver 的滚动回调。

它有点简陋,但您可以尝试 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);
    }
}

我遇到了同样的问题并找到了解决方法,即在布局管理器中使用 smoothScrollToPosition。此方法触发滚动侦听器。所以基本上这就是我曾经拥有的:

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

现在我改成了这样:

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

对我来说效果很好。

你的建议很好。

 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);
            }
        });
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());
    }
});

要在回收器视图上显式触发 onScrollListener,请使用:

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

我无法使用 smoothScrollToPosition(我们过去遇到过一些问题)并且根据第一项的位置似乎不可靠,所以我最终使用了 yigit 的答案(它有已经发布),不幸的是它并不总是有效(我不确定为什么)。

所以我最终使用了一个我之前添加到 RecyclerView 的滚动监听器,遗憾的是我找不到这个解决方案的来源。
如果你需要一个固定的监听器,我认为你不应该使用这个监听器,幸运的是我只需要在特定时间监听。

覆盖 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);
    }

}  

用法:

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

myRecyclerView.startScrollerTask();

请尝试

LinearLayoutManager.scrollToPositionWithOffset(int, int).

LayoutManager.scrollToPosition() 可能效果不佳,但 LinearLayoutManager.scrollToPositionWithOffset() 可以。还有类似的 GridLayoutManager。

或者我们必须编写自定义的实现方式,因为 RecyclerView 不知道 LayoutManager 是如何滚动视图的。

    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
        }
    }

有时这会起作用

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