繁体   English   中英

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

[英]Refreshing data in RecyclerView and keeping its scroll position

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

在好的 ol' ListView情况下,它所需要的只是检索getChildAt(0) ,检查其getTop()并在之后使用相同的确切数据调用setSelectionFromTop

RecyclerView情况下似乎不可能。

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

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

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

我用这个。^_^

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

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

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

我有非常相似的问题。 我想出了以下解决方案。

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

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

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

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

片段中的状态保存以类似的方式工作,但实际的状态保存需要一些额外的工作,但是有很多文章处理这个问题,所以你应该不会有问题找出如何保存滚动条的原则并恢复它保持不变。

通过使用@DawnYu 答案来保持滚动位置,像这样包装notifyDataSetChanged()

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

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

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 视图始终刷新数据而不会上到顶部。

谢谢快乐编码

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

要刷新recyclerView,尤其是从另一个activity来之后,不闪烁,并且保持滚动位置,需要做如下操作。

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

希望这可以帮助。

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

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

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

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

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

这是为RecyclerView使用DataBinding人的一个选项。 我有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"/>

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
 mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
        }
    });

这里的解决方案是在新消息到来时继续滚动 recyclerview。

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

这在 Kotlin 中对我有用。

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

滚动偏移不会改变。

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

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

然后像下面这样使用它

yourRecyclerView.reStoreState()
adapter.submitList(yourData)
yourRecyclerView.adapter = adapter
@BindingAdapter("items")
fun <T> RecyclerView.setItems(items: List<T>?) {
  (adapter as? ListAdapter<T, *>)?.submitList(items) {
    layoutManager?.onSaveInstanceState().let {
      layoutManager?.onRestoreInstanceState(it)
    }
  }
}

如果你在一个 recyclerview 项目中有一个或多个 EditTexts,禁用它们的自动聚焦,把这个配置放在 recyclerview 的视图中:

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

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

最好的事物。

也许这太简单了,但我只是打电话来做这件事

recreate();

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM