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


96

如何刷新显示在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/...
serefakyuz

@Konard,就我而言,我有一个带有Seekbar实现的音频文件列表,并运行以下问题:1)对于最后被索引的项目,一旦触发了notifyItemChanged(pos),它就会自动将视图自动推到底部。2)在保持notifyItemChanged(pos)的同时,它卡住了列表,并且不允许轻易滚动。虽然我会问一个问题,但是请让我知道您是否有更好的方法来解决此问题。
CoDe

Answers:


139

我用这个。^ _ ^

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

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

它比较简单,希望对您有所帮助!


1
这实际上是imo的正确答案。它仅由于异步数据加载而变得棘手,但是您可以推迟恢复。
EpicPandaForce

完美+2正是我想要的
Adeel Turk

@BubbleTree paste.ofcode.org/EEdjSLQbLrcQyxXpBpEQ4m我正在尝试实现此功能,但无法正常工作,我在做什么错?
Kaustubh Bhagwat

@KaustubhBhagwat也许您应该在使用它之前检查“ recyclerViewState!= null”。
DawnYu

4
我到底应该在哪里使用此代码?在onResume方法中?
Lucky_girl

47

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

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

例如,如果您只需要刷新,或者换句话说,您想要重新绑定每个视图,则只需执行以下操作:

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

2
我尝试了adapter.notifyItemRangeChanged(0,adapter.getItemCount());,它也可以用作.notifyDataSetChanged()
玛尼

4
这对我来说很有帮助。我在位置0插入了多个项目,并且notifyDataSetChanged滚动位置会发生变化,显示新项目。但是,如果使用,notifyItemRangeInserted(0, newItems.size() - 1)RecyclerView保持滚动状态。
卡洛斯夫

非常非常好的答案,我整天都在寻找那个答案的解决方案。谢谢你,这么多..
Gundu Bandgar

adapter.notifyItemRangeChanged(0,adapter.getItemCount()); 应该避免,如最新的Google I / O中所述(观看关于recyclerview的演示文稿),最好(如果您有知识的话)告诉recyclerview确切地更改了哪些项目,而不是一目了然地刷新在所有视图中。
A. Steenbergen

3
它不起作用,即使我添加了,recyclerview也刷新到顶部
Shaahul

21

编辑:要恢复完全相同的外观位置,如要使其看起来完全一样,我们需要做一些不同的事情(请参见下面的如何恢复确切的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);

而已。

请注意,将滚动还原到另一个变量,这一点很重要,因为将使用.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的原理并恢复原样。


我不明白,您可以发表完整的例子吗?

这实际上是接近一个完整的例子,你可以得到,如果你不想让我发表我的整个活动
A.斯滕贝亨

我什么都没有 如果列表的开头有添加的项目,那么停留在相同的滚动位置上会不会出错,因为您现在实际上正在寻找接管该区域的不同项目?
Android开发人员

是的,在那种情况下,您必须在还原时调整位置,但是,这不是问题的一部分,因为通常在旋转时,您不会添加或删除项目。这将是奇怪的行为,并且可能不符合用户的利益,除了可能存在的某些特殊情况(我无法坦白地说)。
A. Steenbergen

9

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

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

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

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

如果adapter不为null,则可以确保已经至少初始化了我的适配器一次。

因此,我只将列表添加到适配器并调用notifydatasetchanged。

RecyclerView始终保持滚动的最后一个位置,因此您不必存储最后一个位置,只需调用notifydatasetchanged,Recycler View总是刷新数据而无需转到顶部。

谢谢快乐编码


我认为这将是公认的答案@Konrad Morawski
Jithin Jude 19-10-27

5

通过使用@DawnYu答案来保持滚动位置,notifyDataSetChanged()如下所示:

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

完美...最好的答案
格兰德

我一直在寻找这个答案很长一段时间。非常感谢你。
Alaa AbuZarifa

1

@DawnYu给出的最高答案有效,但是recyclerview将首先滚动到顶部,然后返回到预期的滚动位置,从而导致“闪烁状”反应,这并不令人满意。

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

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

希望这可以帮助。


0

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

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

当用户滚动时,它是侦听器。性能开销并不重要。这样,第一个可见位置就是准确的。


好的,findFirstVisibleItemPosition返回“第一个可见视图的适配器位置”,但是偏移量呢?
Konrad Morawski

1
Recyclerview是否与可见位置的ListView的setSelection方法类似?这就是我为ListView做的。
原始的Android

1
如何将setScrollY方法与dy参数(将保存的值)一起使用?如果有效,我将更新我的答案。我尚未使用Recyclerview,但我想在以后的应用中使用。
原始的Android

请参阅下面有关如何保存滚动位置的答案。
斯坦伯格

1
好吧,下次我会记住这一点,我并没有因为你的顽强而投反对票。但是我不同意简短和不完整的答案,因为当我开始简短的回答时,其他开发人员认为很多背景信息并不能真正帮助我,因为它们通常太不完整,我不得不问很多澄清的问题,通常不能在旧的stackoverflow帖子上执行。还要注意,康拉德没有接受任何答案,这表明这些答案并不能解决他的问题。虽然我明白您的一般观点。
斯坦伯格

-1
 mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
        }
    });

解决方案是在收到新消息时继续滚动recyclerview。

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


-1

那在Kotlin为我工作。

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

滚动偏移量不变。


-2

我遇到了一个问题清单,每个清单都有几分钟的时间,直到“到期”并需要更新。我先更新数据,然后再打电话

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

-2

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

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

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

最好。


-3

如果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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.