RecyclerViewでデータを更新し、スクロール位置を維持する


96

RecyclerViewnotifyDataSetChangedアダプターを呼び出して)に表示されるデータを更新し、スクロール位置が正確に元の位置にリセットされていることを確認するにはどうすればよいですか?

良い結果が得られた場合、必要なListViewことは、取得getChildAt(0)、チェック、その後の同じ正確なデータのgetTop()呼び出しだけsetSelectionFromTopです。

の場合は出来ないようですRecyclerView

私はLayoutManager確かにそれを提供するwhich を使用することになっていると思いscrollToPositionWithOffset(int position, int offset)ますが、位置とオフセットを取得する適切な方法は何ですか?

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

または、仕事を完了するためのよりエレガントな方法はありますか?


RecyclerViewまたはLayoutManagerの幅と高さの値についてです。:このソリューションをチェックstackoverflow.com/questions/28993640/...
serefakyuz

@Konard、私の場合、Seekbar実装のオーディオファイルのリストがあり、次の問題で実行されています。2)notifyItemChanged(pos)を保持している間、リストが動かなくなり、簡単にスクロールできません。質問としてお伺いしますが、そのような問題を修正するためのより良いアプローチが得られた場合はお知らせください。
CoDe 2017年

回答:


139

私はこれを使います。^ _ ^

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

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

それはより簡単です、それがあなたを助けることを願っています!


1
これが正解です。非同期のデータ読み込みのためだけにトリッキーになりますが、復元を延期できます。
EpicPandaForce 2017

完璧な+2まさに私が探していたもの
Adeel Turk '25

@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()として機能します
Mani

4
これは私の場合に役立ちました。私は位置0にいくつかの項目を挿入していましたがnotifyDataSetChanged、スクロール位置を変更すると、新しい項目が表示されます。ただし、キープを使用notifyItemRangeInserted(0, newItems.size() - 1)するとRecyclerView、スクロール状態が保持されます。
Carlosph、2015年

非常に非常に良い答え、私は一日中その答えの解決策を探しています。...そんなに、ありがとう
Gundu Bandgar

adapter.notifyItemRangeChanged(0、adapter.getItemCount()); 最新のGoogle I / O(recyclerviewの入出力に関するプレゼンテーションをご覧ください)に示されているように、避ける必要があります。フラットキャッチオールリフレッシュではなく、変更されたアイテムを正確にrecyclerviewに伝える方が適切です(知識がある場合)。すべてのビューの。
A. Steenbergen

3
追加しましたが、動作していません
。recyclerview

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. この変数をバンドルに保存し状態の復元で別の変数に復元します。これをmStateScrollYと呼びます。

  2. 状態の復元、RecyclerViewがすべてのデータをリセットした後、次のようにしてスクロールをリセットします。

    mRecyclerView.scrollBy(0, mStateScrollY);

それでおしまい。

スクロールを別の変数に復元することに注意してください。OnScrollListenerは.scrollBy()で呼び出され、その後mScrollYをmStateScrollYに格納されている値に設定するため、これは重要です。これを行わないと、mScrollYのスクロール値が2倍になります(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);
}

フラグメントでの状態保存は同様の方法で機能しますが、実際の状態保存には少し余分な作業が必要ですが、それを扱う記事はたくさんあるので、スクロールを保存する原理を見つけるのに問題はないはずですY復元は同じままです。


完全な例を投稿できますか?

これは、実際に私が私の活動全体を投稿したくない場合に得ることができる完全な例に近いものです
A. Steenbergen

何も手に入れません。リストの最初に項目が追加された場合、同じスクロール位置に留まることは間違いありません。この領域を引き継いだ別の項目を実際に見ているからです。
android開発者

はい、その場合、復元時に位置を調整する必要がありますが、通常は回転中にアイテムを追加または削除しないため、これは問題の一部ではありません。それは奇妙で振る舞いであり、私が正直に描くことができない存在するかもしれないいくつかの特別な場合を除いて、おそらくユーザーの利益にはならないでしょう。
A. Steenbergen 2017年

9

はい、アダプターコンストラクターを1回だけ作成することでこの問題を解決できます。ここでコーディングの部分を説明します。

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の場合にのみ初期化することがわかります。

アダプタがnullでない場合、少なくとも1回はアダプタを初期化したことが保証されます。

そのため、アダプターにリストを追加して、notifydatasetchangedを呼び出します。

RecyclerViewは常にスクロールされた最後の位置を保持するため、最後の位置を保存する必要はありません。notifydatasetchangedを呼び出すだけで、リサイクラービューは常に先頭に移動せずにデータを更新します。

ハッピーコーディングありがとう


これは受け入れられた回答@Konrad Morawski
Jithin Judeに

5

@DawnYu回答を使用して、次のnotifyDataSetChanged()ようにラップしてスクロール位置を維持します。

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

パーフェクト...ベストアンサー
jlandyr

私は長い間この答えを探していました。どうもありがとうございました。
Alaa AbuZarifa

1

@DawnYuによるトップの回答は機能しますが、recyclerviewは最初に上部にスクロールしてから、意図したスクロール位置に戻り、「ちらつきのような」反応を引き起こし、快適ではありません。

recyclerViewを更新するには、特に別のアクティビティから来た後、ちらつくことなく、スクロール位置を維持するには、次の操作を行う必要があります。

  1. DiffUtilを使用してリサイクラービューを更新していることを確認します。詳しくは、https//www.journaldev.com/20873/android-recyclerview-diffutilをご覧ください。
  2. アクティビティの再開時、またはアクティビティを更新する時点で、データをリサイクラービューにロードします。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、2015

1
Recyclerviewに、表示位置のListViewのsetSelectionメソッドと同様のメソッドはありますか?これは私がリストビューのためにやったことです。
オリジナルAndroid

1
保存する値であるdyパラメーターでsetScrollYメソッドを使用するのはどうですか?うまくいけば、答えを更新します。Recyclerviewを使用していませんが、将来のアプリで使用したいと考えています。
オリジナルのAndroid、

スクロール位置を保存する方法については、以下の私の回答を参照してください。
A. Steenbergen、2015年

1
承知しました。次回はそのことを念頭に置いて、悪意をもってあなたに反対票を投じませんでした。ただし、他の開発者が多くの背景情報を想定している短い回答を始めたとき、私はあまり役に立たず、明確な質問をたくさんしなければならなかったので、私は短い回答と不完全な回答には同意しません。通常、古いstackoverflowポストでは実行できません。また、Konradが回答を受け入れなかったことにも注意してください。これは、これらの回答で問題が解決されなかったことを示しています。私はあなたの一般的なポイントを参照してください。
A. Steenbergen、2015年

-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アイテム内に1つ以上のEditTextがある場合は、これらのオートフォーカスを無効にして、この構成をrecyclerviewのビューに配置します。

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

recyclerviewアイテムから起動された別のアクティビティを開始したときにこの問題が発生しました。戻って、1つのアイテムの1つのフィールドの更新をnotifyItemChanged(position)で設定し、RVのスクロールを移動しました。私の結論は、EditTextのオートフォーカスでした。アイテム、上のコードは私の問題を解決しました。

ベスト。


-3

oldPositionと位置が同じ場合は戻ります。

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);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.