40

我已经实现了水平滚动RecyclerView。我RecyclerView使用 a LinearLayoutManager,我面临的问题是当我尝试使用scrollToPosition(position)orsmoothScrollToPosition(position)或 from时LinearLayoutManagerscrollToPositionWithOffset(position)都不适合我。滚动调用不会滚动到所需位置,或者它不会调用OnScrollListener.

到目前为止,我已经尝试了很多不同的代码组合,因此我无法将它们全部发布在这里。以下是对我有用的(但只是部分):

public void smoothUserScrollTo(final int position) {

    if (position < 0 || position > getAdapter().getItemCount()) {
        Log.e(TAG, "An attempt to scroll out of adapter size has been stopped.");
        return;
    }

    if (getLayoutManager() == null) {
        Log.e(TAG, "Cannot scroll to position a LayoutManager is not set. " +
                "Call setLayoutManager with a non-null layout.");
        return;
    }

    if (getChildAdapterPosition(getCenterView()) == position) {
        return;
    }

    stopScroll();

    scrollToPosition(position);

    if (lastScrollPosition == position) {

        addOnLayoutChangeListener(new OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {

                if (left == oldLeft && right == oldRight && top == oldTop && bottom == oldBottom) {
                    removeOnLayoutChangeListener(this);

                    updateViews();

                    // removing the following line causes a position - 3 effect.
                    scrollToView(getChildAt(0));
                }
            }
        });
    }

    lastScrollPosition = position;
}

@Override
public void scrollToPosition(int position) {
    if (position < 0 || position > getAdapter().getItemCount()) {
        Log.e(TAG, "An attempt to scroll out of adapter size has been stopped.");
        return;
    }

    if (getLayoutManager() == null) {
        Log.e(TAG, "Cannot scroll to position a LayoutManager is not set. " +
                "Call setLayoutManager with a non-null layout.");
        return;
    }

//      stopScroll();

        ((LinearLayoutManager) getLayoutManager()).scrollToPositionWithOffset(position, 0);
//        getLayoutManager().scrollToPosition(position);
    }

我因此选择了scrollToPositionWithOffset()这个但情况可能有所不同,因为我使用的是 LinearLayoutManager 而不是 GridLayoutManager。但该解决方案也对我有用,但正如我之前所说的,只是部分地。

  • 当滚动调用从第 0 个位置到 totalSize - 7 滚动就像一个魅力。
  • 当滚动从 totalSize - 7 到 totalSize - 3 时,我第一次只滚动到列表中的最后一项。但是第二次我可以很好地滚动
  • 从 totalSize - 3 滚动到 totalSize 时,我开始出现意外行为。

如果有人找到解决方法,我会很感激。这是我的自定义代码的要点ReyclerView

4

18 回答 18

58

几周前我遇到了同样的问题,发现只有一个非常糟糕的解决方案来解决它。必须使用postDelayed200-300 毫秒。

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

如果您找到更好的解决方案,请告诉我!祝你好运!

于 2016-04-05T12:33:54.980 回答
17

原来我遇到了类似的问题,直到我使用

myRecyclerview.scrollToPosition(objectlist.size()-1)

仅放入对象列表大小时,它将始终保持在顶部。直到我决定将大小设置为等于一个变量。再一次,那没有用。然后我假设它可能正在处理 outofboundsexception 而没有告诉我。所以我把它减了1。然后它起作用了。

于 2016-07-20T02:12:29.443 回答
14

接受的答案会起作用,但也可能会中断。这个问题的主要原因是当你要求它滚动时回收器视图可能还没有准备好。最好的解决方案是等待回收器视图准备好然后滚动。幸运的是,android 提供了这样一种选择。以下解决方案适用于 Kotlin,您可以尝试使用 java 替代方案,它会起作用。

newsRecyclerView.post {
    layoutManager?.scrollToPosition(viewModel.selectedItemPosition)
}

post runnable 方法可用于每个 View 元素,并且将在视图准备好后执行,从而确保代码在需要时准确执行。

于 2019-06-05T08:29:34.450 回答
8

LinearSmoothScroller在我的情况下,您每次都可以使用它:

  1. 首先创建一个 LinearSmoothScroller 的实例:
  LinearSmoothScroller smoothScroller=new LinearSmoothScroller(activity){
            @Override
            protected int getVerticalSnapPreference() {
                return LinearSmoothScroller.SNAP_TO_START;
            }
        };
  1. 然后,当您想将回收站视图滚动到任何位置时,请执行以下操作:
smoothScroller.setTargetPosition(pos);  // pos on which item you want to scroll recycler view
recyclerView.getLayoutManager().startSmoothScroll(smoothScroller);

完毕。

于 2019-05-17T07:34:28.267 回答
7

所以对我来说问题是我在NestedScrollView中有一个RecyclerView。我花了一些时间才弄清楚这是问题所在。解决方案是(Kotlin):

val childY = recycler_view.y + recycler_view.getChildAt(position).y
nested_scrollview.smoothScrollTo(0, childY.toInt())

Java(归功于 Himagi https://stackoverflow.com/a/50367883/2917564

float y = recyclerView.getY() + recyclerView.getChildAt(selectedPosition).getY();    
scrollView.smoothScrollTo(0, (int) y);

诀窍是将嵌套的滚动视图滚动到 Y 而不是 RecyclerView。这在 Android 5.0 三星 J5 和带有 Android 9 的华为 P30 pro 上运行良好。

于 2019-06-20T11:50:03.030 回答
5

这些方法似乎都不适合我。只有下面的单行代码有效

((LinearLayoutManager)mRecyclerView.getLayoutManager()).scrollToPositionWithOffset(adapter.currentPosition(),200);

第二个参数指的是偏移量,它实际上是item view的起始边缘和RecyclerView的起始边缘之间的距离(以像素为单位)。我为它提供了一个常量值,以使顶部项目也可见。

在此处查看更多参考

于 2018-04-25T07:23:39.820 回答
3

在 Fragment 或 Activity 中使用 Kotlin 协程,并使用生命周期范围,因为在此范围内启动的任何协程都会在生命周期被销毁时取消。

  lifecycleScope.launch {
                delay(100)
                recyclerView.scrollToPosition(0)
于 2020-08-29T11:07:36.360 回答
2

我也遇到了类似的问题(更新列表时必须滚动到顶部),但上述选项都没有 100% 有效

但是我终于在https://dev.to/aldok/how-to-scroll-recyclerview-to-a-certain-position-5ck4 存档链接找到了一个可行的解决方案

概括

scrollToPosition似乎只有在底层数据集准备好时才有效。

因此postDelay(随机)有效,但这取决于设备/应用程序的速度。如果超时时间太短,则会失败。smoothScrollToPosition也仅在适配器不太忙时才有效(请参阅https://stackoverflow.com/a/61403576/11649486

要观察数据集何时准备就绪,AdapterDataObserver可以添加 a 并覆盖某些方法。

解决我的问题的代码:

adapter.registerAdapterDataObserver( object : RecyclerView.AdapterDataObserver() {
    override fun onItemRangeInserted(
        positionStart: Int,
        itemCount: Int
    ) {
        // This will scroll to the top when new data was inserted
        recyclerView.scrollToPosition(0)
    }
}
于 2021-07-15T20:54:24.527 回答
1

我在创建循环/圆形适配器时遇到了同样的问题,考虑到位置初始化为0. 我首先考虑使用 Robert 的方法,但它太不可靠了,因为 Handler 只触发了一次,如果我不走运,在某些情况下位置不会被初始化。

为了解决这个问题,我创建了一个间隔 Observable,它每隔 XXX 时间检查一次初始化是否成功,然后将其处理掉。这种方法对我的用例非常可靠。

private fun initialisePositionToAllowBidirectionalScrolling(layoutManager: LinearLayoutManager, realItemCount: Int) {
        val compositeDisposable = CompositeDisposable() // Added here for clarity, make this into a private global variable and clear in onDetach()/onPause() in case auto-disposal wouldn't ever occur here
        val initPosition = realItemCount * 1000

        Observable.interval(INIT_DELAY_MS, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe ({
                    if (layoutManager.findFirstVisibleItemPosition() == 0) {
                        layoutManager.scrollToPositionWithOffset(initPosition, 0)

                        if (layoutManager.findFirstCompletelyVisibleItemPosition() == initPosition) {
                            Timber.d("Adapter initialised, setting position to $initPosition and disposing interval subscription!")
                            compositeDisposable.clear()
                        }
                    }
                }, {
                    Timber.e("Failed to initialise position!\n$it")
                    compositeDisposable.clear()
                }).let { compositeDisposable.add(it) }
    }
于 2018-08-08T09:14:34.627 回答
1

这对我有用

Handler().postDelayed({
                    (recyclerView.getLayoutManager() as LinearLayoutManager).scrollToPositionWithOffset( 0, 0)

}, 100)
于 2020-06-17T05:39:37.300 回答
1

当滚动到回收站中的最后一个项目时,这非常有效

        Handler handler = new Handler();

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (((LinearLayoutManager) recyclerView.getLayoutManager())
                        .findLastVisibleItemPosition() != adapter.getItemCount() - 1) {
                    recyclerView.scrollToPosition(adapter.getItemCount() - 1);
                    handler.postDelayed(this, 200);
                }
            }
        }, 200 /* change it if you want*/);
于 2020-06-17T21:31:53.020 回答
1

非常奇怪的错误,无论如何我设法解决它而没有发布或延迟发布如下:

list.scrollToPosition(position - 1)
list.smoothScrollBy(1, 0)

希望它也可以帮助某人。

于 2020-12-01T21:22:32.003 回答
0

有同样的问题。我的问题是,在我尝试滚动之后,我用异步任务中的数据重新填充了视图。从 onPostExecute ofc 解决了这个问题。延迟也解决了这个问题,因为当滚动执行时,列表已经被重新填充。

于 2017-09-07T10:19:16.477 回答
0

我使用以下解决方案在重新加载回收器视图(方向更改等)后使回收器视图中的选定项目可见。它覆盖 LinearLayoutManager 并使用onSaveInstanceState来保存当前的回收站位置。然后在onRestoreInstanceState中恢复保存的位置。最后,在onLayoutCompleted中,scrollToPosition(mRecyclerPosition)用于使先前选择的回收站位置再次可见,但正如 Robert Banyai 所说,为了使其可靠工作,必须插入一定的延迟。我想在调用 scrollToPosition 之前需要为适配器提供足够的时间来加载数据。

private class MyLayoutManager extends LinearLayoutManager{
    private boolean isRestored;

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

    public MyLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public MyLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public void onLayoutCompleted(RecyclerView.State state) {
        super.onLayoutCompleted(state);
        if(isRestored && mRecyclerPosition >-1) {
            Handler handler=new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    MyLayoutManager.this.scrollToPosition(mRecyclerPosition);
                }
            },200);

        }
        isRestored=false;
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable savedInstanceState = super.onSaveInstanceState();
        Bundle bundle=new Bundle();
        bundle.putParcelable("saved_state",savedInstanceState);
        bundle.putInt("position", mRecyclerPosition);
        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        Parcelable savedState = ((Bundle)state).getParcelable("saved_state");
        mRecyclerPosition = ((Bundle)state).getInt("position",-1);
        isRestored=true;
        super.onRestoreInstanceState(savedState);
    }
}
于 2017-11-27T01:16:21.553 回答
0

如果在 nestedScrollView 中使用 recyclerview,则必须滚动 nestScrollview

nestedScrollview.smoothScrollTo(0,0)
于 2020-04-23T19:10:17.773 回答
0

也许这不是那么优雅的方式,但这总是对我有用。向 RecyclerView 添加一个新方法并使用它scrollToPosition

public void myScrollTo(int pos){
    stopScroll();
    ((LinearLayoutManager)getLayoutManager()).scrollToPositionWithOffset(pos,0);
}
于 2020-08-09T18:51:21.943 回答
0

答案是使用 Post 方法,它将保证任何操作的正确执行

于 2020-08-24T17:12:44.110 回答
0

这是在这个日期使用 kotlin 的最终解决方案......如果您导航到另一个片段并返回并且您的 recyclerview 重置到第一个位置,只需将此行添加到 onCreateView 或您需要的任何地方都可以调用适配器...

pagingAdapter.stateRestorationPolicy=RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY

BTW pagingAdapter 是我的 diffUtil 适配器。

于 2020-12-23T02:19:38.477 回答