在RecyclerView中刷新数据并保持其滚动位置

时间:2015-02-22 13:52:51

标签: android android-recyclerview

如何刷新RecyclerView中显示的数据(在其适配器上调用notifyDataSetChanged)并确保滚动位置重置到它的确切位置?

如果好{ol} ListView,只需检索getChildAt(0),然后检查其getTop()并使用相同的确切数据调用setSelectionFromTop

RecyclerView

似乎无法实现。{/ 1}

我想我应该使用确实提供LayoutManager的{​​{1}},但是检索位置和偏移的正确方法是什么?

scrollToPositionWithOffset(int position, int offset)layoutManager.findFirstVisibleItemPosition()

或者有更优雅的方式来完成工作吗?

16 个答案:

答案 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,这可能会使列表看起来不同 ---

  1. 像这样应用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;
        }
    };
    
  2. 这将在mScrollY中始终存储确切的滚动位置。

    1. 将此变量存储在Bundle中,并在状态恢复中将其恢复为另一个变量,我们将其称为mStateScrollY。

    2. 状态恢复后 后,您的RecyclerView重置了所有数据,重置滚动条:

      mRecyclerView.scrollBy(0, mStateScrollY);
      
    3. 那就是它。

      请注意,将滚动恢复为另一个变量,这很重要,因为将使用.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,尤其是在另一个活动之后,不要闪烁并保持滚动位置,就需要刷新。

  1. 确保您正在使用DiffUtil更新回收者视图。在此处了解有关此内容的更多信息:https://www.journaldev.com/20873/android-recyclerview-diffutil
  2. 恢复活动,或在要更新活动时,将数据加载到recyclerview。使用diffUtil,将仅在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对我有用。

  1. 创建适配器并在构造函数中移交数据
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. 更改此属性,然后调用notifyDataSetChanged()
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();

我的回收者视图位置已保存。