【问题标题】:Refreshing data in RecyclerView and keeping its scroll position刷新 RecyclerView 中的数据并保持其滚动位置
【发布时间】:2015-04-23 21:06:01
【问题描述】:

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

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

RecyclerView 的情况下似乎不可能。

我想我应该使用它的LayoutManager,它确实提供了scrollToPositionWithOffset(int position, int offset),但是检索位置和偏移量的正确方法是什么?

layoutManager.findFirstVisibleItemPosition()layoutManager.getChildAt(0).getTop()?

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

【问题讨论】:

  • 这与您的 RecyclerView 或 LayoutManager 的宽度和高度值有关。检查此解决方案:stackoverflow.com/questions/28993640/…
  • @Konard,在我的情况下,我有带有 Seekbar 实现的音频文件列表,并且运行时遇到以下问题:1)对于少数最后一个索引项目,一旦它触发 notifyItemChanged(pos),它就会在底部自动推送视图. 2)在继续 notifyItemChanged(pos) 时,它卡住了列表并且不允许轻松滚动。虽然我会问一个问题,但如果您有更好的方法来解决此类问题,请告诉我。

标签: android android-recyclerview


【解决方案1】:

我用这个。^_^

// Save state
private Parcelable recyclerViewState;
recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();

// Restore state
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);

比较简单,希望对你有帮助!

【讨论】:

  • 这实际上是 imo 的正确答案。仅因为异步数据加载而变得棘手,但您可以推迟恢复。
  • 完美 +2 正是我想要的
  • 我应该在哪里使用此代码?在 onResume 方法中?
  • 它可以用来包围 notifyDataSetChanged 调用:val recyclerViewState = recyclerView.layoutManager.onSaveInstanceState() adapter.notifyDataSetChanged() recyclerView.layoutManager.onRestoreInstanceState(recyclerViewState)
  • 这仅在您 notifyDataSetChanged 使用相同的数据时有效,如果数据更改,例如插入/删除/更改了新批次的项目,您可以使用 notifyItemRangeInserted(removed/changed) 和适当的参数
【解决方案2】:

我也有类似的问题。我想出了以下解决方案。

使用notifyDataSetChanged 是个坏主意。您应该更具体,然后RecyclerView 将为您保存滚动状态。

例如,如果您只需要刷新,或者换句话说,您希望重新绑定每个视图,只需这样做:

adapter.notifyItemRangeChanged(0, adapter.getItemCount());

【讨论】:

  • 我尝试了 adapter.notifyItemRangeChanged(0, adapter.getItemCount());,它也可以用作 .notifyDataSetChanged()
  • 这对我有帮助。我在位置 0 处插入了几个项目,notifyDataSetChanged 滚动位置会改变以显示新项目。但是,如果使用notifyItemRangeInserted(0, newItems.size() - 1)RecyclerView 会保持滚动状态。
  • 非常非常好的答案,我一整天都在寻找该答案的解决方案。谢谢你,非常感谢..
  • adapter.notifyItemRangeChanged(0, adapter.getItemCount());应该避免,如最新的 Google I/O(观看有关 recyclerview 进出的演示文稿)所示,最好(如果您有知识)告诉 recyclerview 准确地更改了哪些项目,而不是一概而论的刷新的所有视图。
  • 它不工作,recyclerview 刷新到顶部,即使我添加了
【解决方案3】:

编辑:要恢复完全相同的明显位置,就像让它看起来完全一样,我们需要做一些不同的事情(见下文如何恢复准确的 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;
        }
    };
    

这将始终将准确的滚动位置存储在 mScrollY 中。

  1. 将此变量存储在您的 Bundle 中,并在状态恢复中将其恢复到不同的变量,我们将其称为 mStateScrollY。

  2. 在状态恢复和之后,您的 RecyclerView 已重置其所有数据,并使用以下内容重置滚动:

    mRecyclerView.scrollBy(0, mStateScrollY);
    

就是这样。

注意,将滚动恢复到不同的变量,这很重要,因为 OnScrollListener 将使用 .scrollBy() 调用,随后会将 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 和恢复它保持不变。

【讨论】:

  • 我不明白你能发布完整的例子吗?
  • 如果你不想让我发布我的整个活动,这实际上是一个完整的例子。
  • 我什么也不懂。如果在列表的开头添加了项目,那么停留在相同的滚动位置不会出错,因为您现在实际上将查看占据该区域的不同项目?
  • 是的,在这种情况下,您必须在恢复时调整位置,但是,这不是问题的一部分,因为通常在旋转时您不会添加或删除项目。那将是奇怪的行为,并且可能不符合用户的利益,除了可能存在的一些特殊情况,老实说,我无法想象。
【解决方案4】:

使用@DawnYu 回答包裹notifyDataSetChanged() 保持滚动位置,如下所示:

val recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState() 
adapter.notifyDataSetChanged() 
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)

【讨论】:

  • 完美...最佳答案
  • 我一直在寻找这个答案很长时间。非常感谢。
【解决方案5】:

是的,您可以通过只创建一次适配器构造函数来解决此问题,我在这里解释编码部分:

if (appointmentListAdapter == null) {
        appointmentListAdapter = new AppointmentListAdapter(AppointmentsActivity.this);
        appointmentListAdapter.addAppointmentListData(appointmentList);
        appointmentListAdapter.setOnStatusChangeListener(onStatusChangeListener);
        appointmentRecyclerView.setAdapter(appointmentListAdapter);

    } else {
        appointmentListAdapter.addAppointmentListData(appointmentList);
        appointmentListAdapter.notifyDataSetChanged();
    }

现在你可以看到我已经检查了适配器是否为空,并且只有在它为空时才初始化。

如果适配器不为空,那么我确信我已经初始化了我的适配器至少一次。

所以我只是将列表添加到适配器并调用 notifydatasetchanged。

RecyclerView 始终保持最后滚动的位置,因此您不必存储最后位置,只需调用 notifydatasetchanged,recycler 视图始终刷新数据而不到顶部。

谢谢 快乐编码

【讨论】:

  • 我认为这是公认的答案@Konrad Morawski
【解决方案6】:

@DawnYu 的最佳答案有效,但 recyclerview 将首先滚动到顶部,然后返回到预期的滚动位置,导致“闪烁状”反应,这不愉快。

要刷新recyclerView,尤其是在来自另一个Activity之后,不闪烁,并且保持滚动位置,你需要执行以下操作。

  1. 确保使用 DiffUtil 更新回收站视图。在此处阅读更多信息:https://www.journaldev.com/20873/android-recyclerview-diffutil
  2. 在您的活动恢复时,或在您想要更新您的活动时,将数据加载到您的回收站视图。使用 diffUtil,只会在 recyclerview 上进行更新,同时保持其位置。

希望这会有所帮助。

【讨论】:

    【解决方案7】:

    对于将DataBinding 用于RecyclerView 的人来说,这是一个选项。 我的适配器中有var recyclerViewState: Parcelable?。我使用BindingAdapter 和@DawnYu 答案的变体来设置和更新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"/>
    

    【讨论】:

      【解决方案8】:

      我犯了这样的错误,也许它会帮助某人:)

      如果每次新数据到来时都使用recyclerView.setAdapter,那么每次使用它都会调用适配器clear() 方法,这会导致recyclerview 刷新并重新开始。要摆脱这种情况,您需要使用adapter.notiftyDatasetChanced()

      【讨论】:

        【解决方案9】:

        我没有使用过 Recyclerview,但我在 ListView 上使用过。 Recyclerview 中的示例代码:

        setOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                rowPos = mLayoutManager.findFirstVisibleItemPosition();
        

        它是用户滚动时的监听器。性能开销并不显着。并且第一个可见位置是这样准确的。

        【讨论】:

        • 好的,findFirstVisibleItemPosition 返回“第一个可见视图的适配器位置”,但是偏移量呢。
        • Recyclerview 的可见位置是否有与 ListView 的 setSelection 方法类似的方法?这就是我为 ListView 所做的。
        • 如何使用带有 dy 参数的 setScrollY 方法,您将保存一个值?如果它有效,我会更新我的答案。我没有使用过 Recyclerview,但我想在我未来的应用中使用。
        • 请看我下面关于如何保存滚动位置的回答。
        • 好吧,下次我会记住这一点,我没有恶意给你投反对票。但是我不同意简短和不完整的答案,因为当我开始时,其他开发人员认为很多背景信息并没有真正帮助我的简短答案,因为它们通常太不完整,我不得不问很多澄清问题,你通常不能在旧的stackoverflow帖子上做。还要注意康拉德没有接受任何答案,这表明这些答案并没有解决他的问题。虽然我明白你的一般观点。
        【解决方案10】:

        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
        

        【讨论】:

          【解决方案11】:
           mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
                  @Override
                  public void onChanged() {
                      mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
                  }
              });
          

          这里的解决方案是在新消息出现时继续滚动回收视图。

          onChanged() 方法检测对 recyclerview 执行的操作。

          【讨论】:

            【解决方案12】:

            这在 Kotlin 中对我有用。

            1. 在构造函数中创建适配器并交出数据
            class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
            
            1. 更改此属性并调用 notifyDataSetChanged()
            adapter.currentPole = pole
            adapter.notifyDataSetChanged()
            

            滚动偏移不会改变。

            【讨论】:

              【解决方案13】:

              创建扩展并将其完全用于您的应用程序,如果您使用的是 DiffUtil,则无需添加 adapter.notifyDataSetChanged()

              fun RecyclerView.reStoreState(){
                  val recyclerViewState = this.layoutManager?.onSaveInstanceState()
                  this.layoutManager?.onRestoreInstanceState(recyclerViewState)
              }
              

              然后像下面这样使用它

              yourRecyclerView.reStoreState()
              adapter.submitList(yourData)
              yourRecyclerView.adapter = adapter
              

              【讨论】:

                【解决方案14】:
                @BindingAdapter("items")
                fun <T> RecyclerView.setItems(items: List<T>?) {
                  (adapter as? ListAdapter<T, *>)?.submitList(items) {
                    layoutManager?.onSaveInstanceState().let {
                      layoutManager?.onRestoreInstanceState(it)
                    }
                  }
                }
                

                【讨论】:

                  【解决方案15】:

                  如果您在 recyclerview 项目中有一个或多个 EditText,请禁用这些的自动对焦,将此配置放在 recyclerview 的 视图中:

                  android:focusable="true"
                  android:focusableInTouchMode="true"
                  

                  当我开始从 recyclerview 项目启动的另一项活动时遇到了这个问题,当我回来并使用 notifyItemChanged(position) 设置一个项目中的一个字段更新时,RV 的滚动移动了,我的结论是, EditText Items 的自动对焦,上面的代码解决了我的问题。

                  最好的。

                  【讨论】:

                    【解决方案16】:

                    如果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);
                    }
                    

                    【讨论】:

                      【解决方案17】:

                      我遇到了一个项目列表的问题,每个项目都有一个以分钟为单位的时间,直到它们“到期”并需要更新。我会更新数据,然后调用

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

                      【讨论】:

                        猜你喜欢
                        • 2013-07-12
                        • 1970-01-01
                        • 1970-01-01
                        • 2012-10-21
                        • 2011-07-31
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2020-02-29
                        相关资源
                        最近更新 更多