簡體   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