如何刷新RecyclerView
中显示的数据(在其适配器上调用notifyDataSetChanged
)并确保滚动位置重置到它的确切位置?
如果好{ol} ListView
,只需检索getChildAt(0)
,然后检查其getTop()
并使用相同的确切数据调用setSelectionFromTop
。
RecyclerView
。
我想我应该使用确实提供LayoutManager
的{{1}},但是检索位置和偏移的正确方法是什么?
scrollToPositionWithOffset(int position, int offset)
和layoutManager.findFirstVisibleItemPosition()
?
或者有更优雅的方式来完成工作吗?
答案 0 :(得分:102)
我用这个。^ _ ^
// Save state
private Parcelable recyclerViewState;
recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();
// Restore state
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);
它更简单,希望它能帮到你!
答案 1 :(得分:43)
我有类似的问题。我提出了以下解决方案。
使用notifyDataSetChanged
是一个坏主意。您应该更具体,然后RecyclerView
将为您保存滚动状态。
例如,如果您只需要刷新,或者换句话说,您希望每个视图都被重新加入,请执行以下操作:
adapter.notifyItemRangeChanged(0, adapter.getItemCount());
答案 2 :(得分:20)
编辑:为了恢复完全相同的明显的位置,使其看起来与它完全一样,我们需要做一些不同的事情(见下文)恢复确切的scrollY值):
保存位置和偏移,如下所示:
LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
int firstItem = manager.findFirstVisibleItemPosition();
View firstItemView = manager.findViewByPosition(firstItem);
float topOffset = firstItemView.getTop();
outState.putInt(ARGS_SCROLL_POS, firstItem);
outState.putFloat(ARGS_SCROLL_OFFSET, topOffset);
然后像这样恢复滚动:
LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
manager.scrollToPositionWithOffset(mStatePos, (int) mStateOffset);
这会将列表恢复到其确切的明显位置。显而易见,因为它看起来与用户相同,但它不会具有相同的scrollY值(因为横向/纵向布局尺寸可能存在差异)。
请注意,这仅适用于LinearLayoutManager。
--- 下面如何恢复精确的scrollY,这可能会使列表看起来不同 ---
像这样应用OnScrollListener:
private int mScrollY;
private RecyclerView.OnScrollListener mTotalScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mScrollY += dy;
}
};
这将在mScrollY中始终存储确切的滚动位置。
将此变量存储在Bundle中,并在状态恢复中将其恢复为另一个变量,我们将其称为mStateScrollY。
状态恢复后 后,您的RecyclerView重置了所有数据,重置滚动条:
mRecyclerView.scrollBy(0, mStateScrollY);
那就是它。
请注意,将滚动恢复为另一个变量,这很重要,因为将使用.scrollBy()调用OnScrollListener,然后将mScrollY设置为存储在mStateScrollY中的值。如果你不这样做,mScrollY将使滚动值加倍(因为OnScrollListener使用增量,而不是绝对滚动)。
活动中的国家储蓄可以这样实现:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(ARGS_SCROLL_Y, mScrollY);
}
要恢复在onCreate()中调用它:
if(savedState != null){
mStateScrollY = savedState.getInt(ARGS_SCROLL_Y, 0);
}
片段中的状态保存以类似的方式工作,但实际的状态保存需要一些额外的工作,但是有很多文章处理这个,所以你不应该有一个问题找出如何,保存scrollY并恢复它的原则保持不变。
答案 3 :(得分:5)
是的,你可以通过只使用一次适配器构造函数来解决这个问题,我在这里解释编码部分:
if (appointmentListAdapter == null) {
appointmentListAdapter = new AppointmentListAdapter(AppointmentsActivity.this);
appointmentListAdapter.addAppointmentListData(appointmentList);
appointmentListAdapter.setOnStatusChangeListener(onStatusChangeListener);
appointmentRecyclerView.setAdapter(appointmentListAdapter);
} else {
appointmentListAdapter.addAppointmentListData(appointmentList);
appointmentListAdapter.notifyDataSetChanged();
}
现在您可以看到我已经检查了适配器是否为null,并且仅在它为空时进行初始化。
如果适配器不为null,那么我确信我已经初始化了我的适配器至少一次。
所以我只是将列表添加到适配器并调用notifydatasetchanged。
RecyclerView始终保持滚动的最后位置,因此您不必存储最后一个位置,只需调用notifydatasetchanged,回收站视图始终刷新数据而不会返回顶部。
由于 快乐编码
答案 4 :(得分:1)
@DawnYu给出的最高答案有效,但是recyclerview将首先滚动到顶部,然后返回到预期的滚动位置,从而引起“闪烁状”反应,这并不令人愉快。
要刷新recyclerView,尤其是在另一个活动之后,不要闪烁并保持滚动位置,就需要刷新。
希望这会有所帮助。
答案 5 :(得分:1)
1- 您需要像这样保存滚动位置
rvProduct.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
recyclerViewState = rvProduct.getLayoutManager().onSaveInstanceState(); // save recycleView state
}
});
2- 在你调用 notifyDataSetChanged 然后 onRestoreInstanceState 像这个例子一样
productsByBrandAdapter.addData(productCompareList);
productsByBrandAdapter.notifyDataSetChanged();
rvProduct.getLayoutManager().onRestoreInstanceState(recyclerViewState); // restore recycleView state
答案 6 :(得分:0)
我没有使用过Recyclerview,但我在ListView上做过。 Recyclerview中的示例代码:
setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
rowPos = mLayoutManager.findFirstVisibleItemPosition();
用户滚动时是听众。性能开销并不重要。第一个可见位置就是这样准确的。
答案 7 :(得分:0)
mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
}
});
这里的解决方案是在收到新消息时继续滚动recyclerview。
onChanged()方法检测在recyclerview上执行的操作。
答案 8 :(得分:0)
使用@DawnYu答案来包裹notifyDataSetChanged()
,以保持滚动位置,如下所示:
val recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState()
adapter.notifyDataSetChanged()
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)
答案 9 :(得分:0)
那在Kotlin对我有用。
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>() { ... }
adapter.currentPole = pole
adapter.notifyDataSetChanged()
滚动偏移量不变。
答案 10 :(得分:0)
对于将DataBinding
用于RecyclerView
的人,这里是一个选项。
我的适配器中有var recyclerViewState: Parcelable?
。我使用带有{DawnYu答案的变体的BindingAdapter
来设置和更新RecyclerView
中的数据:
@BindingAdapter("items")
fun setRecyclerViewItems(
recyclerView: RecyclerView,
items: List<RecyclerViewItem>?
) {
var adapter = (recyclerView.adapter as? RecyclerViewAdapter)
if (adapter == null) {
adapter = RecyclerViewAdapter()
recyclerView.adapter = adapter
}
adapter.recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState()
// the main idea is in this call with a lambda. It allows to avoid blinking on data update
adapter.submitList(items.orEmpty()) {
adapter.recyclerViewState?.let {
recyclerView.layoutManager?.onRestoreInstanceState(it)
}
}
}
最后,XML部分如下所示:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/possible_trips_rv"
android:layout_width="match_parent"
android:layout_height="0dp"
app:items="@{viewState.yourItems}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
答案 11 :(得分:0)
我犯了这样的错误,也许它可以帮助某人:)
如果每次使用新数据时都使用#include <stdio.h>
union ab {
struct {
int a;
} a_st;
struct {
int b;
} b_st;
};
typedef struct d {
union ab;
struct {
int c;
} c_st;
} d_st;
int main(void) {
d_st x;
printf("%zu\n", sizeof(d_st));
x.a_st.a = 1;
x.b_st.b = 2;
x.c_st.c = 3;
printf("a_st = %d\n", x.a_st.a);
printf("b_st = %d\n", x.b_st.b);
printf("c_st = %d\n", x.c_st.c);
return 0;
}
,则每次使用适配器时都会调用适配器{if {$product.stock_quantity} < {$product.cart_quantity}{l s='Warning: Quantity of product in your cart is out-of-stock and will take longer to be delivered.'}{/if}
方法,这将导致recyclerView.setAdapter
刷新并重新开始。要摆脱这种情况,您需要使用clear()
。
答案 12 :(得分:-1)
我遇到了这个问题,列表中的每个项目都有几分钟的时间,直到它们“到期”并需要更新。我会更新数据然后再调用
orderAdapter.notifyDataSetChanged();
每次都滚动到顶部。我用
替换了它 for(int i = 0; i < orderArrayList.size(); i++){
orderAdapter.notifyItemChanged(i);
}
很好。这个帖子中没有其他方法适合我。但是在使用这种方法时,它会使每个单独的项目在更新时闪烁,所以我还必须将它放在父片段的onCreateView
中RecyclerView.ItemAnimator animator = orderRecycler.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}
答案 13 :(得分:-1)
如果您在recyclerview项中有一个或多个EditText,请禁用这些项的自动聚焦,然后将此配置放在recyclerview的父视图中:
android:focusable="true"
android:focusableInTouchMode="true"
当我启动另一个从recyclerview项启动的活动时,我遇到了这个问题,当我回来并使用notifyItemChanged(position)设置RV滚动时,更新了一个项中一个字段的更新时,我的结论是,自动聚焦EditText项目,上面的代码解决了我的问题。
最好。
答案 14 :(得分:-2)
如果oldPosition和position相同,则返回;
private int oldPosition = -1;
public void notifyItemSetChanged(int position, boolean hasDownloaded) {
if (oldPosition == position) {
return;
}
oldPosition = position;
RLog.d(TAG, " notifyItemSetChanged :: " + position);
DBMessageModel m = mMessages.get(position);
m.setVideoHasDownloaded(hasDownloaded);
notifyItemChanged(position, m);
}
答案 15 :(得分:-2)
也许这太简单了,但我只是通过调用
来做这件事recreate();
我的回收者视图位置已保存。