Bagaimana cara memperbarui Data Adaptor RecyclerView?

275

Mencoba mencari tahu apa masalahnya dengan memperbarui RecyclerViewAdaptor.

Setelah saya mendapatkan Daftar produk baru, saya mencoba untuk:

  1. Perbarui ArrayListdari fragmen tempat recyclerViewdibuat, atur data baru ke adaptor, lalu panggil adapter.notifyDataSetChanged(); tidak bekerja.

  2. Buat adaptor baru, seperti yang dilakukan orang lain, dan itu berhasil untuk mereka, tetapi tidak ada perubahan untuk saya: recyclerView.setAdapter(new RecyclerViewAdapter(newArrayList))

  3. Buat metode di Adaptermana memperbarui data sebagai berikut:

    public void updateData(ArrayList<ViewModel> viewModels) {
       items.clear();
       items.addAll(viewModels);
       notifyDataSetChanged();
    }

    Lalu saya memanggil metode ini setiap kali saya ingin memperbarui daftar data; tidak bekerja.

  4. Untuk memeriksa apakah saya dapat memodifikasi recyclerView dengan cara apa pun, dan saya mencoba menghapus setidaknya satu item:

     public void removeItem(int position) {
        items.remove(position);
        notifyItemRemoved(position);
    }

    Segalanya tetap seperti itu.

Ini Adaptor saya:

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> implements View.OnClickListener {

    private ArrayList<ViewModel> items;
    private OnItemClickListener onItemClickListener;

    public RecyclerViewAdapter(ArrayList<ViewModel> items) {
        this.items = items;
    }


    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler, parent, false);
        v.setOnClickListener(this);
        return new ViewHolder(v);
    }

    public void updateData(ArrayList<ViewModel> viewModels) {
        items.clear();
        items.addAll(viewModels);
        notifyDataSetChanged();
    }
    public void addItem(int position, ViewModel viewModel) {
        items.add(position, viewModel);
        notifyItemInserted(position);
    }

    public void removeItem(int position) {
        items.remove(position);
        notifyItemRemoved(position);
    }



    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        ViewModel item = items.get(position);
        holder.title.setText(item.getTitle());
        Picasso.with(holder.image.getContext()).load(item.getImage()).into(holder.image);
        holder.price.setText(item.getPrice());
        holder.credit.setText(item.getCredit());
        holder.description.setText(item.getDescription());

        holder.itemView.setTag(item);
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    @Override
    public void onClick(final View v) {
        // Give some time to the ripple to finish the effect
        if (onItemClickListener != null) {
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    onItemClickListener.onItemClick(v, (ViewModel) v.getTag());
                }
            }, 0);
        }
    }

    protected static class ViewHolder extends RecyclerView.ViewHolder {
        public ImageView image;
        public TextView price, credit, title, description;

        public ViewHolder(View itemView) {
            super(itemView);
            image = (ImageView) itemView.findViewById(R.id.image);
            price = (TextView) itemView.findViewById(R.id.price);
            credit = (TextView) itemView.findViewById(R.id.credit);
            title = (TextView) itemView.findViewById(R.id.title);
            description = (TextView) itemView.findViewById(R.id.description);
        }
    }

    public interface OnItemClickListener {

        void onItemClick(View view, ViewModel viewModel);

    }
}

Dan saya memulai RecyclerViewsebagai berikut:

recyclerView = (RecyclerView) view.findViewById(R.id.recycler);
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 5));
adapter = new RecyclerViewAdapter(items);
adapter.setOnItemClickListener(this);
recyclerView.setAdapter(adapter);

Jadi, bagaimana cara saya benar-benar memperbarui data adaptor untuk menampilkan item yang baru diterima?


Pembaruan: masalahnya adalah tata letak tempat gridView tampak sebagai berikut:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:tag="catalog_fragment"
    android:layout_height="match_parent">

    <FrameLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false"/>

        <ImageButton
            android:id="@+id/fab"
            android:layout_gravity="top|end"
            style="@style/FabStyle"/>

    </FrameLayout>
</LinearLayout>

Lalu saya baru saja menghapus LinearLayoutdan menjadikan FrameLayoutlayout orangtua.

Filip Luchianenco
sumber
items.clear(); items.addAll(newItems);adalah pola yang jelek. Jika Anda benar-benar perlu menyalin defensif di sini, items = new ArrayList(newItems);akan kurang jelek.
Miha_x64
2
@ miha-x64 Anda benar - itu akan menjadi kurang jelek. Masalahnya adalah ini tidak berfungsi. Adaptor tidak mendapat referensi ke referensi. Itu mendapat referensi itu sendiri. Jadi jika Anda membuat dataset baru, ia memiliki referensi baru sementara adaptornya hanya tahu yang lama.
luar biasa

Jawaban:

326

Saya bekerja dengan RecyclerView dan baik menghapus maupun memperbarui berfungsi dengan baik.

1) HAPUS: Ada 4 langkah untuk menghapus item dari RecyclerView

list.remove(position);
recycler.removeViewAt(position);
mAdapter.notifyItemRemoved(position);                 
mAdapter.notifyItemRangeChanged(position, list.size());

Baris kode ini cocok untuk saya.

2) MEMPERBARUI DATA: Satu-satunya hal yang harus saya lakukan adalah

mAdapter.notifyDataSetChanged();

Anda harus melakukan semua ini dalam kode Actvity / Fragment tidak dalam kode Adapter RecyclerView.

Niccolò Passolunghi
sumber
2
Terima kasih. Silakan periksa pembaruan. Entah bagaimana, tata letak linier tidak membiarkan daftar pembaruan RecylerView.
Filip Luchianenco
40
Anda hanya perlu dua baris ini: list.remove (position); mAdapter.notifyItemRemoved (posisi);
feisal
3
Bagi saya, garis recycler.removeViewAt(position);(atau lebih tepatnya recycler.removeAllViews()) adalah yang paling penting.
MSpeed
2
Pertimbangan penting untuk menghindari gangguan yang mengganggu selama penghapusan: Anda tidak perlu memanggil pendaur ulang baris ini .removeViewAt (posisi);
Angelo Nodari
7
NotifyDataSetChanged berlebihan, terutama jika Anda berurusan dengan daftar SANGAT panjang. Muat ulang semuanya untuk beberapa item? Tidak, terima kasih. Juga, recycler.removeViewAt (posisi)? Tidak dibutuhkan. Dan tidak perlu menyebut "mAdapter.notifyItemRemoved (position)" dan "mAdapter.notifyItemRangeChanged (position, list.size ())". Hapus sebagian, atau hapus satu. Beri tahu SEKALI. Panggil salah satu dari mereka.
Vitor Hugo Schwaab
329

Ini adalah jawaban umum untuk pengunjung masa depan. Berbagai cara untuk memperbarui data adaptor dijelaskan. Proses ini mencakup dua langkah utama setiap kali:

  1. Perbarui set data
  2. Beri tahu adaptor perubahan

Sisipkan satu item

Tambahkan "Babi" di indeks 2.

Sisipkan satu item

String item = "Pig";
int insertIndex = 2;
data.add(insertIndex, item);
adapter.notifyItemInserted(insertIndex);

Sisipkan beberapa item

Masukkan tiga hewan lagi di indeks 2.

Sisipkan beberapa item

ArrayList<String> items = new ArrayList<>();
items.add("Pig");
items.add("Chicken");
items.add("Dog");
int insertIndex = 2;
data.addAll(insertIndex, items);
adapter.notifyItemRangeInserted(insertIndex, items.size());

Hapus satu item

Hapus "Babi" dari daftar.

Hapus satu item

int removeIndex = 2;
data.remove(removeIndex);
adapter.notifyItemRemoved(removeIndex);

Hapus beberapa item

Hapus "Unta" dan "Domba" dari daftar.

Hapus beberapa item

int startIndex = 2; // inclusive
int endIndex = 4;   // exclusive
int count = endIndex - startIndex; // 2 items will be removed
data.subList(startIndex, endIndex).clear();
adapter.notifyItemRangeRemoved(startIndex, count);

Hapus semua item

Kosongkan seluruh daftar.

Hapus semua item

data.clear();
adapter.notifyDataSetChanged();

Ganti daftar lama dengan daftar baru

Kosongkan daftar lama lalu tambahkan yang baru.

Ganti daftar lama dengan daftar baru

// clear old list
data.clear();

// add new list
ArrayList<String> newList = new ArrayList<>();
newList.add("Lion");
newList.add("Wolf");
newList.add("Bear");
data.addAll(newList);

// notify adapter
adapter.notifyDataSetChanged();

The adaptermemiliki referensi ke data, jadi penting bahwa saya tidak mengatur datauntuk objek baru. Alih-alih saya membersihkan barang-barang lama dari datadan kemudian menambahkan yang baru.

Perbarui satu item

Ubah item "Domba" sehingga berbunyi "Saya suka domba."

Perbarui satu item

String newValue = "I like sheep.";
int updateIndex = 3;
data.set(updateIndex, newValue);
adapter.notifyItemChanged(updateIndex);

Pindahkan satu item

Pindahkan "Domba" dari posisi 3ke posisi 1.

Pindahkan satu item

int fromPosition = 3;
int toPosition = 1;

// update data array
String item = data.get(fromPosition);
data.remove(fromPosition);
data.add(toPosition, item);

// notify adapter
adapter.notifyItemMoved(fromPosition, toPosition);

Kode

Ini adalah kode proyek untuk referensi Anda. Kode Adaptor RecyclerView dapat ditemukan di jawaban ini .

MainActivity.java

public class MainActivity extends AppCompatActivity implements MyRecyclerViewAdapter.ItemClickListener {

    List<String> data;
    MyRecyclerViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // data to populate the RecyclerView with
        data = new ArrayList<>();
        data.add("Horse");
        data.add("Cow");
        data.add("Camel");
        data.add("Sheep");
        data.add("Goat");

        // set up the RecyclerView
        RecyclerView recyclerView = findViewById(R.id.rvAnimals);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
                layoutManager.getOrientation());
        recyclerView.addItemDecoration(dividerItemDecoration);
        adapter = new MyRecyclerViewAdapter(this, data);
        adapter.setClickListener(this);
        recyclerView.setAdapter(adapter);
    }

    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(this, "You clicked " + adapter.getItem(position) + " on row number " + position, Toast.LENGTH_SHORT).show();
    }

    public void onButtonClick(View view) {
        insertSingleItem();
    }

    private void insertSingleItem() {
        String item = "Pig";
        int insertIndex = 2;
        data.add(insertIndex, item);
        adapter.notifyItemInserted(insertIndex);
    }

    private void insertMultipleItems() {
        ArrayList<String> items = new ArrayList<>();
        items.add("Pig");
        items.add("Chicken");
        items.add("Dog");
        int insertIndex = 2;
        data.addAll(insertIndex, items);
        adapter.notifyItemRangeInserted(insertIndex, items.size());
    }

    private void removeSingleItem() {
        int removeIndex = 2;
        data.remove(removeIndex);
        adapter.notifyItemRemoved(removeIndex);
    }

    private void removeMultipleItems() {
        int startIndex = 2; // inclusive
        int endIndex = 4;   // exclusive
        int count = endIndex - startIndex; // 2 items will be removed
        data.subList(startIndex, endIndex).clear();
        adapter.notifyItemRangeRemoved(startIndex, count);
    }

    private void removeAllItems() {
        data.clear();
        adapter.notifyDataSetChanged();
    }

    private void replaceOldListWithNewList() {
        // clear old list
        data.clear();

        // add new list
        ArrayList<String> newList = new ArrayList<>();
        newList.add("Lion");
        newList.add("Wolf");
        newList.add("Bear");
        data.addAll(newList);

        // notify adapter
        adapter.notifyDataSetChanged();
    }

    private void updateSingleItem() {
        String newValue = "I like sheep.";
        int updateIndex = 3;
        data.set(updateIndex, newValue);
        adapter.notifyItemChanged(updateIndex);
    }

    private void moveSingleItem() {
        int fromPosition = 3;
        int toPosition = 1;

        // update data array
        String item = data.get(fromPosition);
        data.remove(fromPosition);
        data.add(toPosition, item);

        // notify adapter
        adapter.notifyItemMoved(fromPosition, toPosition);
    }
}

Catatan

  • Jika Anda menggunakan notifyDataSetChanged(), maka tidak ada animasi yang akan dilakukan. Ini juga bisa menjadi operasi yang mahal, jadi tidak disarankan untuk digunakan notifyDataSetChanged()jika Anda hanya memperbarui satu item atau serangkaian item.
  • Periksa DiffUtil jika Anda membuat perubahan besar atau kompleks pada daftar.

Pelajaran lanjutan

Suragch
sumber
5
Jawaban yang luar biasa! Apa yang akan menjadi langkah-langkah ketika mengganti item dalam, yang akan menghasilkan jenis tampilan yang berbeda? Apakah itu hanya notifyItemChanged atau penghapusan gabungan yang diikuti oleh sisipan?
Semaphor
Dengan contoh minimal Anda, sepertinya notifyItemChanged sudah cukup. Ini akan memanggil onCreateViewHolder dan onBindViewHolder untuk item yang diubah dan tampilan yang berbeda ditampilkan. Saya memiliki masalah dalam aplikasi yang memperbarui item yang akan menghasilkan tampilan yang berbeda, tetapi tampaknya ada masalah lain.
Semafor
@Suragch ada ide tentang pertanyaan saya: stackoverflow.com/questions/57332222/... Saya mencoba semua yang terdaftar di utas ini tetapi tidak berhasil :(
Bagaimana ketika kita menggunakan DiffUtils untuk melakukan perubahan? Mereka tidak begitu cair seperti memanggil secara manual. Beri tahu <Action> ().
GuilhE
1
Benar-benar menjatuhkan bola di bawah Update single item, seharusnya aku seperti kura-kura
ardnew
46

Inilah yang bekerja untuk saya:

recyclerView.setAdapter(new RecyclerViewAdapter(newList));
recyclerView.invalidate();

Setelah membuat adaptor baru yang berisi daftar yang diperbarui (dalam kasus saya itu adalah database yang diubah menjadi ArrayList) dan pengaturan itu sebagai adaptor, saya mencoba recyclerView.invalidate()dan berfungsi.

martinpkmn
sumber
12
bukankah ini menyegarkan seluruh tampilan alih-alih hanya memperbarui item yang telah berubah?
TWilly
13
Alih-alih membuat adaptor baru setiap kali, yang saya lakukan adalah membuat metode setter di kelas adaptor khusus saya untuk mengatur daftar baru. Setelah itu, sebut saja YourAdapter adapter = (YourAdapter) recyclerView.getAdapter(); adapter.yourSetterMethod(newList); adapter.notifyDataSetChanged();Yang dikatakan, kedengarannya seperti apa yang OP coba terlebih dahulu (hanya menambahkan ini sebagai komentar sehingga dapat membantu orang lain, seperti yang berhasil dalam kasus saya).
Kevin Lee
2
Pembaca, jangan puas dengan ini. Jawaban ini berfungsi, tetapi ada cara yang lebih efisien. Alih-alih memuat kembali semua data , cara yang paling efisien adalah menambahkan data baru ke adaptor.
Sebastialonso
Ya saya setuju dengan @TWilly itu akan menggambar ulang Tampilan lengkap di UI.
Rahul
18

Anda memiliki 2 opsi untuk melakukan ini: menyegarkan UI dari adaptor:

mAdapter.notifyDataSetChanged();

atau segarkan dari recyclerView sendiri:

recyclerView.invalidate();
ediBersh
sumber
15

Pilihan lain adalah menggunakan diffutil . Ini akan membandingkan daftar asli dengan daftar baru dan menggunakan daftar baru sebagai pembaruan jika ada perubahan.

Pada dasarnya, kita dapat menggunakan DiffUtil untuk membandingkan data lama vs data baru dan membiarkannya memanggil notifyItemRangeRemoved, dan notifyItemRangeChanged dan notifyItemRangeDisertakan atas nama Anda.

Contoh cepat menggunakan diffUtet bukannya notifyDataSetChanged:

DiffResult diffResult = DiffUtil
                .calculateDiff(new MyDiffUtilCB(getItems(), items));

//any clear up on memory here and then
diffResult.dispatchUpdatesTo(this);

//and then, if necessary
items.clear()
items.addAll(newItems)

Saya melakukan pekerjaan calculDiff dari utas utama jika itu daftar besar.

j2emanue
sumber
1
meskipun ini benar, bagaimana cara memperbarui data di dalam adaptor Anda? Katakanlah saya menampilkan Daftar <Object> di adaptor dan saya mendapatkan pembaruan baru dengan List <Object>. DiffUtil akan menghitung perbedaan dan mengirimkannya ke adaptor, tetapi tidak melakukan apa-apa dengan daftar asli atau baru (dalam kasus Anda getItems(). Bagaimana Anda tahu data mana dari daftar asli yang perlu dihapus / ditambahkan / perbarui?
vanomart
1
Mungkin artikel ini bisa membantu. .. medium.com/@nullthemall/…
j2emanue
@vanomart Anda hanya perlu langsung mengganti bidang daftar lokal Anda dengan nilai daftar baru seperti yang biasanya Anda lakukan, satu-satunya perbedaan dalam pendekatan ini adalah bahwa alih-alih notifyDataSetChanged () Anda menggunakan DiffUtil untuk memperbarui tampilan.
carrizo
ada ide tentang saya? saya mencoba semuanya yang tercantum di sini tetapi tidak berhasil: stackoverflow.com/questions/57332222/...
Terima kasih atas jawaban Anda! Bisakah Anda jelaskan mengapa saya harus meletakkan "clear" & "addAll" setelah dispatchUpdatesTo ?? Tnx!
Adi Azarya
10

Perbarui Data listview, gridview, dan recyclerview

mAdapter.notifyDataSetChanged();

atau

mAdapter.notifyItemRangeChanged(0, itemList.size());
Pankaj Talaviya
sumber
Tampaknya ini tidak memperbarui data saya sampai pengguna mulai menggulir. Saya telah mencari di semua tempat dan tidak dapat menemukan alasannya ..
SudoPlz
@SudoPlz Anda dapat menggunakan pendengar gulir khusus untuk mendeteksi gulir dan melakukan operasi pemberitahuan Anda seperti stackoverflow.com/a/31174967/4401692
Pankaj Talaviya
Terima kasih Pankaj, tetapi itulah tepatnya yang saya coba hindari. Saya ingin memperbarui semuanya TANPA meminta pengguna menyentuh layar.
SudoPlz
5

Cara terbaik dan paling keren untuk menambahkan data baru ke data saat ini adalah

 ArrayList<String> newItems = new ArrayList<String>();
 newItems = getList();
 int oldListItemscount = alcontainerDetails.size();
 alcontainerDetails.addAll(newItems);           
 recyclerview.getAdapter().notifyItemChanged(oldListItemscount+1, al_containerDetails);
Rushi Ayyappa
sumber
2
Saya melihat orang menggunakan "notifyDataSetChanged" untuk SEMUA sesuatu, bukankah itu berlebihan? Maksud saya, muat ulang seluruh daftar karena beberapa diubah atau ditambahkan ... Saya suka pendekatan ini, sedang menerapkan sekarang, dengan metode "notifyItemRangeInserted".
Vitor Hugo Schwaab
5

Saya telah memecahkan masalah yang sama dengan cara yang berbeda. Saya tidak memiliki data yang saya tunggu dari latar belakang jadi mulailah dengan daftar emty.

        mAdapter = new ModelAdapter(getContext(),new ArrayList<Model>());
   // then when i get data

        mAdapter.update(response.getModelList());
  //  and update is in my adapter

        public void update(ArrayList<Model> modelList){
            adapterModelList.clear(); 
            for (Product model: modelList) {
                adapterModelList.add(model); 
            }
           mAdapter.notifyDataSetChanged();
        }

Itu dia.

Ahmed Ozmaan
sumber
2

Metode ini efisien dan bagus untuk mulai menggunakan dasar RecyclerView.

private List<YourItem> items;

public void setItems(List<YourItem> newItems)
{
    clearItems();
    addItems(newItems);
}

public void addItem(YourItem item, int position)
{
    if (position > items.size()) return;

    items.add(item);
    notifyItemInserted(position);
}

public void addMoreItems(List<YourItem> newItems)
{
    int position = items.size() + 1;
    newItems.addAll(newItems);
    notifyItemChanged(position, newItems);
}

public void addItems(List<YourItem> newItems)
{
    items.addAll(newItems);
    notifyDataSetChanged();
}

public void clearItems()
{
    items.clear();
    notifyDataSetChanged();
}

public void addLoader()
{
    items.add(null);
    notifyItemInserted(items.size() - 1);
}

public void removeLoader()
{
    items.remove(items.size() - 1);
    notifyItemRemoved(items.size());
}

public void removeItem(int position)
{
    if (position >= items.size()) return;

    items.remove(position);
    notifyItemRemoved(position);
}

public void swapItems(int positionA, int positionB)
{
    if (positionA > items.size()) return;
    if (positionB > items.size()) return;

    YourItem firstItem = items.get(positionA);

    videoList.set(positionA, items.get(positionB));
    videoList.set(positionB, firstItem);

    notifyDataSetChanged();
}

Anda dapat menerapkannya di dalam Kelas Adaptor atau dalam Fragmen atau Aktivitas Anda, tetapi dalam hal ini Anda harus instantiate Adaptor untuk memanggil metode pemberitahuan. Dalam kasus saya, saya biasanya menerapkannya di Adaptor.

Teocci
sumber
2

Saya menemukan bahwa cara yang sangat sederhana untuk memuat ulang RecyclerView adalah dengan menelepon saja

recyclerView.removeAllViews();

Ini pertama-tama akan menghapus semua konten RecyclerView dan kemudian menambahkannya lagi dengan nilai yang diperbarui.

Jonathan Persgarden
sumber
2
Tolong jangan gunakan ini. Anda akan meminta masalah.
dell116
1

saya mendapat jawaban setelah waktu yang lama

  SELECTEDROW.add(dt);
                notifyItemInserted(position);
                SELECTEDROW.remove(position);
                notifyItemRemoved(position);
roufal
sumber
0

Jika tidak ada yang disebutkan dalam komentar di atas bekerja untuk Anda. Itu mungkin berarti masalahnya ada di tempat lain.

Satu tempat saya menemukan solusinya adalah cara saya mengatur daftar ke adaptor. Dalam aktivitas saya, daftar adalah variabel instan dan saya mengubahnya secara langsung ketika ada data yang berubah. Karena itu menjadi variabel referensi ada sesuatu yang aneh terjadi. Jadi saya mengubah variabel referensi ke yang lokal dan menggunakan variabel lain untuk memperbarui data dan kemudian beralih ke addAll()fungsi yang disebutkan dalam jawaban di atas.

bitSmith
sumber