RecyclerView - 滚动到位置不工作每次
RecyclerView - Scroll To Position Not Working Every Time
我实现了水平滚动RecyclerView
。我的 RecyclerView
使用 LinearLayoutManager
,我面临的问题是当我尝试使用 scrollToPosition(position)
或 smoothScrollToPosition(position)
或 LinearLayoutManager
的 scrollToPositionWithOffset(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。但该解决方案对我也有效,但正如我之前所说的,只是部分有效。
- 当对滚动的调用是从第 0 个位置到 totalSize - 7 滚动就像一个魅力。
- 当从 totalSize - 7 滚动到 totalSize - 3 时,我第一次只滚动到列表中的倒数第 7 个项目。但是第二次我可以很好地滚动
- 从 totalSize - 3 滚动到 totalSize 时,我开始出现意外行为。
如果有人找到解决方法,我将不胜感激。这是我的自定义代码 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
这在我的案例中每次都有效:
- 首先创建一个LinearSmoothScroller实例:
LinearSmoothScroller smoothScroller=new LinearSmoothScroller(activity){
@Override
protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}
};
- 然后当您想将回收器视图滚动到任何位置时,请执行以下操作:
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)
}
}
我实现了水平滚动RecyclerView
。我的 RecyclerView
使用 LinearLayoutManager
,我面临的问题是当我尝试使用 scrollToPosition(position)
或 smoothScrollToPosition(position)
或 LinearLayoutManager
的 scrollToPositionWithOffset(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()
因为
- 当对滚动的调用是从第 0 个位置到 totalSize - 7 滚动就像一个魅力。
- 当从 totalSize - 7 滚动到 totalSize - 3 时,我第一次只滚动到列表中的倒数第 7 个项目。但是第二次我可以很好地滚动
- 从 totalSize - 3 滚动到 totalSize 时,我开始出现意外行为。
如果有人找到解决方法,我将不胜感激。这是我的自定义代码 ReyclerView
.
几周前我遇到了同样的问题,但只找到了一个非常糟糕的解决方案。必须使用 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
这在我的案例中每次都有效:
- 首先创建一个LinearSmoothScroller实例:
LinearSmoothScroller smoothScroller=new LinearSmoothScroller(activity){ @Override protected int getVerticalSnapPreference() { return LinearSmoothScroller.SNAP_TO_START; } };
- 然后当您想将回收器视图滚动到任何位置时,请执行以下操作:
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)
}
}