Menyegarkan data di RecyclerView dan mempertahankan posisi gulirnya

96

Bagaimana seseorang menyegarkan data yang ditampilkan di RecyclerView(memanggil notifyDataSetChangedadaptornya) dan memastikan bahwa posisi gulir diatur ulang ke tempat yang tepat?

Dalam kasus yang baik ListViewsemua yang diperlukan adalah mengambil getChildAt(0), memeriksa getTop()dan memanggil setSelectionFromTopdengan data yang persis sama sesudahnya.

Sepertinya tidak mungkin dalam kasus RecyclerView.

Saya kira saya seharusnya menggunakan its LayoutManageryang memang menyediakan scrollToPositionWithOffset(int position, int offset), tapi apa cara yang tepat untuk mengambil posisi dan offsetnya?

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

Atau adakah cara yang lebih elegan untuk menyelesaikan pekerjaan?

Konrad Morawski
sumber
Ini tentang nilai lebar dan tinggi RecyclerView atau LayoutManager Anda. Periksa solusi ini: stackoverflow.com/questions/28993640/…
serefakyuz
@Konard, Dalam kasus saya, saya memiliki daftar file audio dengan implementasi Seekbar dan berjalan dengan masalah berikut: 1) Untuk beberapa item yang diindeks terakhir segera setelah memicu notifyItemChanged (pos) itu mendorong tampilan di bawah otomatis. 2) Sementara tetap notifyItemChanged (pos) itu macet daftar dan tidak memungkinkan untuk menggulir dengan mudah. Meskipun saya akan mengajukan pertanyaan tetapi beri tahu saya jika Anda memiliki pendekatan yang lebih baik untuk memperbaiki masalah tersebut.
CoDe

Jawaban:

140

Saya menggunakan yang ini. ^ _ ^

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

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

Ini lebih sederhana, semoga bisa membantu Anda!

DawnYu
sumber
1
Ini sebenarnya adalah jawaban yang benar. Ini menjadi rumit hanya karena pemuatan data asinkron, tetapi Anda dapat menunda pemulihan.
EpicPandaForce
sempurna +2 persis seperti yang saya cari
Adeel Turk
@BubbleTree paste.ofcode.org/EEdjSLQbLrcQyxXpBpEQ4m saya mencoba untuk mengimplementasikan ini tetapi tidak berhasil, apa yang saya lakukan salah?
Kaustubh Bhagwat
@KaubBagwat Mungkin Anda harus mencentang "recycViewState! = Null" sebelum menggunakannya.
DawnYu
4
Di mana tepatnya saya harus menggunakan kode ini? dalam metode onResume?
Lucky_girl
47

Saya memiliki masalah yang sangat mirip. Dan saya datang dengan solusi berikut.

Menggunakan notifyDataSetChangedadalah ide yang buruk. Anda harus lebih spesifik, kemudian RecyclerViewakan menyimpan status gulir untuk Anda.

Misalnya, jika Anda hanya perlu menyegarkan, atau dengan kata lain, Anda ingin setiap tampilan di-rebind, cukup lakukan ini:

adapter.notifyItemRangeChanged(0, adapter.getItemCount());
Kaerdan
sumber
2
Saya mencoba adapter.notifyItemRangeChanged (0, adapter.getItemCount ()) ;, itu juga berfungsi sebagai .notifyDataSetChanged ()
Mani
4
Ini membantu dalam kasus saya. Saya memasukkan beberapa item pada posisi 0 dan dengan notifyDataSetChangedposisi scroll akan berubah menampilkan item baru. Namun, jika menggunakan notifyItemRangeInserted(0, newItems.size() - 1)yang RecyclerViewmembuat negara gulir.
Carlosph
jawaban yang sangat sangat bagus, saya mencari solusi dari jawaban itu sepanjang hari. terima kasih banyak ..
Gundu Bandgar
adapter.notifyItemRangeChanged (0, adapter.getItemCount ()); harus dihindari, seperti yang ditunjukkan dalam Google I / O terbaru (lihat presentasi masuk dan keluar dari recyclerview), akan lebih baik (jika Anda memiliki pengetahuan) untuk memberi tahu recyclerview dengan tepat item mana yang berubah daripada penyegaran tangkap semua dari semua tampilan.
A. Steenbergen
3
itu tidak berfungsi, recyclerview menyegarkan ke atas meskipun saya menambahkan
Shaahul
21

EDIT: Untuk mengembalikan posisi semu yang persis sama , seperti di, membuatnya terlihat persis seperti itu, kita perlu melakukan sesuatu yang sedikit berbeda (Lihat di bawah cara mengembalikan nilai scrollY yang tepat):

Simpan posisi dan offset seperti ini:

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

Dan kemudian pulihkan gulungan seperti ini:

LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
manager.scrollToPositionWithOffset(mStatePos, (int) mStateOffset);

Ini mengembalikan daftar ke posisi semu yang sebenarnya . Terlihat jelas karena akan terlihat sama bagi pengguna, tetapi tidak akan memiliki nilai scrollY yang sama (karena kemungkinan perbedaan dalam dimensi tata letak lanskap / potret).

Perhatikan bahwa ini hanya berfungsi dengan LinearLayoutManager.

--- Di bawah ini cara mengembalikan scrollY yang tepat, yang kemungkinan akan membuat daftar terlihat berbeda ---

  1. Terapkan OnScrollListener seperti ini:

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

Ini akan menyimpan posisi gulir yang tepat setiap saat di mScrollY.

  1. Simpan variabel ini di Bundle Anda, dan pulihkan dalam pemulihan status ke variabel lain , kami akan menyebutnya mStateScrollY.

  2. Setelah pemulihan status dan setelah RecyclerView Anda menyetel ulang semua datanya, setel ulang gulir dengan ini:

    mRecyclerView.scrollBy(0, mStateScrollY);

Itu dia.

Berhati-hatilah, Anda mengembalikan scroll ke variabel lain, ini penting, karena OnScrollListener akan dipanggil dengan .scrollBy () dan selanjutnya akan menyetel mScrollY ke nilai yang disimpan di mStateScrollY. Jika Anda tidak melakukan ini, mScrollY akan menggandakan nilai scroll (karena OnScrollListener bekerja dengan delta, bukan scroll absolut).

Penghematan negara dalam kegiatan dapat dicapai seperti ini:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt(ARGS_SCROLL_Y, mScrollY);
}

Dan untuk memulihkan panggilan ini di onCreate () Anda:

if(savedState != null){
    mStateScrollY = savedState.getInt(ARGS_SCROLL_Y, 0);
}

Penyimpanan status dalam fragmen bekerja dengan cara yang sama, tetapi penyimpanan status yang sebenarnya membutuhkan sedikit kerja ekstra, tetapi ada banyak artikel yang membahasnya, jadi Anda seharusnya tidak memiliki masalah untuk mencari tahu caranya, prinsip-prinsip menyimpan gulungan dan memulihkannya tetap sama.

A. Steenbergen
sumber
saya tidak mengerti Anda dapat mengirim contoh lengkap?
Ini sebenarnya adalah contoh lengkap yang bisa Anda dapatkan jika Anda tidak ingin saya memposting seluruh aktivitas saya
A. Steenbergen
Saya tidak mendapatkan sesuatu. Jika ada item yang ditambahkan di awal daftar, bukankah tetap pada posisi scrolling yang sama menjadi salah, karena sekarang Anda sebenarnya akan melihat item berbeda yang mengambil alih area ini?
pengembang android
Ya, dalam hal ini Anda harus menyesuaikan posisi saat memulihkan, namun, ini bukan bagian dari pertanyaan, karena biasanya saat memutar Anda tidak menambah atau menghapus item. Itu akan menjadi aneh dan berperilaku dan mungkin bukan untuk kepentingan pengguna kecuali untuk beberapa kasus khusus yang mungkin ada, yang tidak bisa saya bayangkan, sejujurnya.
A. Steenbergen
9

Ya, Anda dapat menyelesaikan masalah ini dengan membuat konstruktor adaptor hanya satu kali, saya menjelaskan bagian pengkodean di sini:

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

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

Sekarang Anda dapat melihat saya telah memeriksa adaptor adalah null atau tidak dan hanya menginisialisasi ketika null.

Jika adaptor tidak null maka saya yakin bahwa saya telah menginisialisasi adaptor saya setidaknya satu kali.

Jadi saya hanya akan menambahkan daftar ke adaptor dan memanggil notifydatasetchanged.

RecyclerView selalu menahan posisi terakhir yang di-scroll, oleh karena itu Anda tidak perlu menyimpan posisi terakhir, cukup panggil notifydatasetchanged, recycler view selalu menyegarkan data tanpa naik ke atas.

Terima kasih Selamat Coding

Amit Singh Tomar
sumber
Saya pikir ini akan menjadi jawaban yang diterima @Konrad Morawski
Jithin Jude
6

Pertahankan posisi scroll dengan menggunakan jawaban @DawnYu untuk membungkus notifyDataSetChanged()seperti ini:

val recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState() 
adapter.notifyDataSetChanged() 
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)
Morten Holmgaard
sumber
sempurna ... jawaban terbaik
jlandyr
Saya sudah mencari jawaban ini sejak lama. Terima kasih banyak.
Alaa AbuZarifa
2

Jawaban teratas dari @DawnYu berfungsi, tetapi recyclerview pertama-tama akan menggulir ke atas, lalu kembali ke posisi gulir yang diinginkan sehingga menyebabkan reaksi "berkedip seperti" yang tidak menyenangkan.

Untuk menyegarkan recyclerView, terutama setelah datang dari aktivitas lain, tanpa berkedip, dan mempertahankan posisi gulir, Anda perlu melakukan hal berikut.

  1. Pastikan Anda memperbarui tampilan pendaur ulang Anda menggunakan DiffUtil. Baca lebih lanjut tentang itu di sini: https://www.journaldev.com/20873/android-recyclerview-diffutil
  2. Saat melanjutkan aktivitas, atau pada titik Anda ingin memperbarui aktivitas, muat data ke recyclerview Anda. Dengan menggunakan diffUtil, hanya pembaruan yang akan dilakukan pada recyclerview sambil mempertahankan posisinya.

Semoga ini membantu.

Moses Mwongela
sumber
0

Saya belum pernah menggunakan Recyclerview tetapi saya melakukannya di ListView. Kode contoh di Recyclerview:

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

Ini adalah pendengar saat pengguna menggulir. Overhead kinerja tidak signifikan. Dan posisi pertama yang terlihat akurat seperti ini.

Android Asli
sumber
Oke, findFirstVisibleItemPositionmengembalikan "posisi adaptor dari tampilan pertama yang terlihat", tapi bagaimana dengan offsetnya.
Konrad Morawski
1
Apakah ada metode serupa untuk Recyclerview ke metode setSelection ListView untuk posisi yang terlihat? Inilah yang saya lakukan untuk ListView.
Android Asli
1
Bagaimana jika menggunakan metode setScrollY dengan parameter dy, nilai yang akan Anda hemat? Jika berhasil, saya akan memperbarui jawaban saya. Saya belum pernah menggunakan Recyclerview tapi saya ingin menggunakan aplikasi saya di masa mendatang.
Android Asli
Silakan lihat jawaban saya di bawah ini tentang cara menyimpan posisi gulir.
A. Steenbergen
1
Baiklah, saya akan mengingatnya lain kali, saya tidak meremehkan Anda dengan maksud jahat. Namun saya tidak setuju dengan jawaban yang singkat dan tidak lengkap karena ketika saya memulai jawaban singkat di mana pengembang lain menganggap banyak info latar belakang tidak terlalu membantu saya, karena seringkali jawaban tersebut terlalu tidak lengkap dan saya harus mengajukan banyak pertanyaan klarifikasi, sesuatu yang Anda biasanya tidak dapat dilakukan pada posting stackoverflow lama. Perhatikan juga bagaimana tidak ada jawaban yang diterima oleh Konrad, yang mengindikasikan bahwa jawaban tersebut tidak menyelesaikan masalahnya. Meskipun saya mengerti maksud umum Anda.
A. Steenbergen
-1
 mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
        }
    });

Solusinya di sini adalah terus menggulir recyclerview saat pesan baru datang.

Metode onChanged () mendeteksi tindakan yang dilakukan pada recyclerview.

Abhishek Chudekar
sumber
-1

Itu berhasil untuk saya di Kotlin.

  1. Buat Adaptor dan serahkan data Anda di konstruktor
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>()  { ... }
  1. ubah properti ini dan panggil notifyDataSetChanged ()
adapter.currentPole = pole
adapter.notifyDataSetChanged()

Offset gulir tidak berubah.

Chris8447
sumber
-2

Saya mengalami masalah ini dengan daftar item yang masing-masing memiliki waktu dalam beberapa menit sampai mereka 'jatuh tempo' dan perlu diperbarui. Saya akan memperbarui data dan kemudian, telepon

orderAdapter.notifyDataSetChanged();

dan itu akan bergulir ke atas setiap saat. Saya menggantinya dengan

 for(int i = 0; i < orderArrayList.size(); i++){
                orderAdapter.notifyItemChanged(i);
            }

dan itu baik-baik saja. Tidak ada metode lain di utas ini yang berhasil untuk saya. Dalam menggunakan metode ini, itu membuat setiap item berkedip ketika diperbarui jadi saya juga harus meletakkan ini di onCreateView fragmen induk.

RecyclerView.ItemAnimator animator = orderRecycler.getItemAnimator();
    if (animator instanceof SimpleItemAnimator) {
        ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
    }
Matt
sumber
-2

Jika Anda memiliki satu atau beberapa EditText di dalam item recyclerview, nonaktifkan autofokus ini, letakkan konfigurasi ini di tampilan induk recyclerview:

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

Saya mengalami masalah ini ketika saya memulai aktivitas lain yang diluncurkan dari item recyclerview, ketika saya kembali dan mengatur pembaruan satu bidang dalam satu item dengan notifyItemChanged (posisi) gulungan gerakan RV, dan kesimpulan saya adalah, fokus otomatis EditText Item, kode di atas menyelesaikan masalah saya.

terbaik.

Akhha8
sumber
-3

Kembalikan jika oldPosition dan posisinya sama;

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);
}
raditya gumay
sumber