RecyclerView - 滚动到位置不工作每次

RecyclerView - Scroll To Position Not Working Every Time

我实现了水平滚动RecyclerView。我的 RecyclerView 使用 LinearLayoutManager,我面临的问题是当我尝试使用 scrollToPosition(position)smoothScrollToPosition(position)LinearLayoutManagerscrollToPositionWithOffset(position).两者都不适合我。滚动调用没有滚动到所需位置,或者没有调用 OnScrollListener.

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

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。但该解决方案对我也有效,但正如我之前所说的,只是部分有效。

如果有人找到解决方法,我将不胜感激。这是我的自定义代码 ReyclerView.

gist

几周前我遇到了同样的问题,但只找到了一个非常糟糕的解决方案。必须使用 200-300 毫秒的 postDelayed

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

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

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

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

只放入objectlist size时总是在最前面。直到我决定将大小设置为一个变量。再次,那没有用。然后我假设它可能在不告诉我的情况下处理越界异常。所以我把它减1。然后它起作用了。

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

我使用以下解决方案在重新加载回收器视图(方向更改等)后使回收器视图中的所选项目可见。它覆盖 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);
    }
}

None 的方法似乎对我有用。只有下面一行代码有效

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

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

查看 here

的更多参考

我在创建 cyclic/circular 适配器时遇到了同样的问题,考虑到位置初始化为 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) }
    }

您可以使用 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);

完成。

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

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

post runnable 方法可用于每个视图元素,一旦视图准备就绪就会执行,从而确保代码在需要时准确执行。

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

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

Java(感谢 Himagi )

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

诀窍是将嵌套的滚动视图滚动到 Y 而不是 RecyclerView。这在 Android 5.0 三星 J5 和华为 P30 pro 和 Android 9.

上运行良好

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

nestedScrollview.smoothScrollTo(0,0)

这对我有用

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

}, 100)

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

        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*/);

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

public void myScrollTo(int pos){
    stopScroll();
    ((LinearLayoutManager)getLayoutManager()).scrollToPositionWithOffset(pos,0);
}

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

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

  lifecycleScope.launch {
                delay(100)
                recyclerView.scrollToPosition(0)

非常奇怪的错误,无论如何我设法在没有 post 或 post 延迟的情况下解决它,如下所示:

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

希望它也对某人有所帮助。

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

pagingAdapter.stateRestorationPolicy=RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY

顺便说一句,pagingAdapter 是我的带有 diffUtil 的适配器。

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

不过我终于在 https://dev.to/aldok/how-to-scroll-recyclerview-to-a-certain-position-5ck4 archive link

找到了可行的解决方案
总结

scrollToPosition 似乎只在 基础数据集准备就绪时才起作用

因此 postDelay 可以(随机)工作,但这取决于 device/app 的速度。如果超时时间太短,它将失败。 smoothScrollToPosition 也仅在适配器不太忙时才有效(参见 )

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

解决我问题的代码:

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