RecyclerView berkedip setelah notifyDatasetChanged ()

113

Saya memiliki RecyclerView yang memuat beberapa data dari API, termasuk url gambar dan beberapa data, dan saya menggunakan networkImageView untuk memuat gambar secara malas.

@Override
public void onResponse(List<Item> response) {
   mItems.clear();
   for (Item item : response) {
      mItems.add(item);
   }
   mAdapter.notifyDataSetChanged();
   mSwipeRefreshLayout.setRefreshing(false);
}

Berikut implementasi untuk Adaptor:

public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
        if (isHeader(position)) {
            return;
        }
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        MyViewHolder holder = (MyViewHolder) viewHolder;
        final Item item = mItems.get(position - 1); // Subtract 1 for header
        holder.title.setText(item.getTitle());
        holder.image.setImageUrl(item.getImg_url(), VolleyClient.getInstance(mCtx).getImageLoader());
        holder.image.setErrorImageResId(android.R.drawable.ic_dialog_alert);
        holder.origin.setText(item.getOrigin());
    }

Masalahnya adalah ketika kita menyegarkan di recyclerView, itu berkedip untuk beberapa saat di awal yang terlihat aneh.

Saya hanya menggunakan GridView / ListView dan berfungsi seperti yang saya harapkan. Tidak ada blincking.

konfigurasi untuk RecycleView di onViewCreated of my Fragment:

mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        mRecyclerView.setHasFixedSize(true);

        mGridLayoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager();
        mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return mAdapter.isHeader(position) ? mGridLayoutManager.getSpanCount() : 1;
            }
        });

        mRecyclerView.setAdapter(mAdapter);

Adakah yang menghadapi masalah seperti itu? apa alasannya?

Ali
sumber

Jawaban:

126

Coba gunakan ID stabil di RecyclerView.Adapter Anda

setHasStableIds(true)dan timpa getItemId(int position).

Tanpa ID yang stabil, setelah itu notifyDataSetChanged(), ViewHolders biasanya ditetapkan ke posisi yang tidak sama. Itulah alasan mengapa saya berkedip.

Anda dapat menemukan penjelasan yang bagus di sini.

Anatoly Vdovichev
sumber
1
Saya menambahkan setHasStableIds (true), yang memecahkan kedipan, tetapi di GridLayout saya, item masih berubah tempat saat bergulir. Apa yang harus saya timpa di getItemId ()? Terima kasih
Balázs Orbán
3
Adakah ide tentang bagaimana kita harus menghasilkan ID?
Mauker
Kamu Menakjubkan! Google TIDAK. Google juga tidak tahu tentang ini ATAU mereka tahu dan tidak ingin kami tahu! THANK YOU MAN
MBH
1
mengacaukan posisi item, item berasal dari database firebase dalam urutan yang benar.
MuhammadAliJr
1
@Mauker Jika objek Anda memiliki nomor unik, Anda dapat menggunakannya atau jika Anda memiliki string unik, Anda dapat menggunakan object.hashCode (). Itu bekerja dengan sangat baik bagi saya
Simon Schubert
106

Menurut halaman terbitan ini .... ini adalah animasi perubahan item tampilan daur ulang default ... Anda dapat mematikannya .. coba ini

recyclerView.getItemAnimator().setSupportsChangeAnimations(false);

Ubah di versi terbaru

Dikutip dari blog pengembang Android :

Perhatikan bahwa API baru ini tidak kompatibel dengan versi sebelumnya. Jika sebelumnya Anda mengimplementasikan ItemAnimator, Anda bisa memperluas SimpleItemAnimator, yang menyediakan API lama dengan menggabungkan API baru. Anda juga akan melihat bahwa beberapa metode telah dihapus seluruhnya dari ItemAnimator. Misalnya, jika Anda memanggil recyclerView.getItemAnimator (). SetSupportsChangeAnimations (false), kode ini tidak akan dikompilasi lagi. Anda bisa menggantinya dengan:

ItemAnimator animator = recyclerView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
  ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}
Sabeer Mohammed
sumber
5
@abeer masih masalah yang sama. Itu tidak menyelesaikan masalah.
Shreyash Mahajan
3
@delive beri tahu saya jika Anda menemukan solusi untuk ini
Shreyash Mahajan
Saya menggunakan picasso, itu sesuatu, bukan berkedip.
8
Kotlin: (recyclerView.itemAnimator sebagai? SimpleItemAnimator) ?. supportChangeAnimations = false
Pedro Paulo Amorim
Ini berfungsi, tetapi masalah lain datang (NPE) java.lang.NullPointerException: Mencoba membaca dari bidang 'int android.support.v7.widget.RecyclerView $ ItemAnimator $ ItemHolderInfo.left' pada referensi objek null Apakah ada cara untuk mengatasi ini?
Navas pk
46

Ini hanya bekerja:

recyclerView.getItemAnimator().setChangeDuration(0);
Hamzeh Soboh
sumber
itu alternatif yang baik untuk codeItemAnimator animator = recyclerView.getItemAnimator (); if (animator instanceof SimpleItemAnimator) {((SimpleItemAnimator) animator) .setSupportsChangeAnimations (false); }code
ziniestro
1
menghentikan semua animasi TAMBAHKAN dan HAPUS
MBH
11

Saya memiliki masalah yang sama saat memuat gambar dari beberapa url dan kemudian imageView berkedip. Diselesaikan dengan menggunakan

notifyItemRangeInserted()    

dari pada

notifyDataSetChanged()

yang menghindari untuk memuat ulang data lama yang tidak berubah.

Wesely
sumber
9

coba ini untuk menonaktifkan animasi default

ItemAnimator animator = recyclerView.getItemAnimator();

if (animator instanceof SimpleItemAnimator) {
  ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}

ini cara baru untuk menonaktifkan animasi sejak dukungan android 23

cara lama ini akan berfungsi untuk versi lama pustaka dukungan

recyclerView.getItemAnimator().setSupportsChangeAnimations(false)
Mohamed Farouk
sumber
4

Dengan asumsi mItemsadalah koleksi yang mendukung AndaAdapter , mengapa Anda menghapus semuanya dan menambahkan kembali? Anda pada dasarnya mengatakan bahwa semuanya telah berubah, jadi RecyclerView me-rebinds semua tampilan daripada yang saya asumsikan perpustakaan Gambar tidak menanganinya dengan benar di mana ia masih mengatur ulang Tampilan meskipun itu adalah url gambar yang sama. Mungkin mereka memiliki beberapa solusi untuk AdapterView sehingga berfungsi dengan baik di GridView.

Alih-alih memanggil notifyDataSetChangedyang akan menyebabkan pengikatan ulang semua tampilan, panggil peristiwa pemberitahuan terperinci (beri tahu ditambahkan / dihapus / dipindahkan / diperbarui) sehingga RecyclerView hanya akan mengembalikan tampilan yang diperlukan dan tidak ada yang akan berkedip.

yigit
sumber
3
Atau mungkin itu hanya "bug" di RecyclerView? Jelas jika itu berfungsi dengan baik selama 6 tahun terakhir dengan AbsListView dan sekarang tidak dengan RecyclerView, berarti ada sesuatu yang tidak beres dengan RecyclerView, bukan? :) Melihat sekilas itu menunjukkan bahwa ketika Anda menyegarkan data di ListView dan GridView, mereka melacak posisi + tampilan, jadi ketika Anda menyegarkan Anda akan mendapatkan viewholder yang persis sama. Sementara RecyclerView mengocok pemegang tampilan, yang mengarah ke kedipan.
vovkab
Bekerja di ListView tidak berarti itu benar untuk RecyclerView. Komponen ini memiliki arsitektur yang berbeda.
yigit
1
Setuju, tetapi terkadang sangat sulit untuk mengetahui item apa yang diubah, misalnya jika Anda menggunakan kursor atau Anda hanya menyegarkan seluruh data. Jadi recyclerview juga harus menangani kasus ini dengan benar.
vovkab
4

Recyclerview menggunakan DefaultItemAnimator sebagai animator defaultnya. Seperti yang Anda lihat dari kode di bawah ini, mereka mengubah alfa pemegang tampilan saat perubahan item:

@Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) {
    ...
    final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
    ...
    ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
    if (newHolder != null) {
        ....
        ViewCompat.setAlpha(newHolder.itemView, 0);
    }
    ...
    return true;
}

Saya ingin mempertahankan sisa animasi tetapi menghapus "flicker" jadi saya mengkloning DefaultItemAnimator dan menghapus 3 baris alfa di atas.

Untuk menggunakan animator baru, cukup panggil setItemAnimator () di RecyclerView Anda:

mRecyclerView.setItemAnimator(new MyItemAnimator());
File Peter
sumber
Ini menghilangkan kedipan tetapi menyebabkan efek kedipan untuk beberapa alasan.
Mohamed Medhat
4

Di Kotlin, Anda dapat menggunakan 'ekstensi kelas' untuk RecyclerView:

fun RecyclerView.disableItemAnimator() {
    (itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
}

// sample of using in Activity:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    // ...
    myRecyclerView.disableItemAnimator()
    // ...
}
Gregory
sumber
1

Hai @Ali mungkin ulangan terlambat. Saya juga menghadapi masalah ini dan menyelesaikannya dengan solusi di bawah ini, ini dapat membantu Anda, silakan periksa.

Kelas LruBitmapCache.java dibuat untuk mendapatkan ukuran cache gambar

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import com.android.volley.toolbox.ImageLoader.ImageCache;

public class LruBitmapCache extends LruCache<String, Bitmap> implements
        ImageCache {
    public static int getDefaultLruCacheSize() {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        final int cacheSize = maxMemory / 8;

        return cacheSize;
    }

    public LruBitmapCache() {
        this(getDefaultLruCacheSize());
    }

    public LruBitmapCache(int sizeInKiloBytes) {
        super(sizeInKiloBytes);
    }

    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight() / 1024;
    }

    @Override
    public Bitmap getBitmap(String url) {
        return get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        put(url, bitmap);
    }
}

VolleyClient.java singleton class [extends Application] ditambahkan di bawah kode

di konstruktor kelas tunggal VolleyClient tambahkan potongan di bawah ini untuk menginisialisasi ImageLoader

private VolleyClient(Context context)
    {
     mCtx = context;
     mRequestQueue = getRequestQueue();
     mImageLoader = new ImageLoader(mRequestQueue,getLruBitmapCache());
}

Saya membuat metode getLruBitmapCache () untuk mengembalikan LruBitmapCache

public LruBitmapCache getLruBitmapCache() {
        if (mLruBitmapCache == null)
            mLruBitmapCache = new LruBitmapCache();
        return this.mLruBitmapCache;
}

Semoga ini bisa membantu Anda.

Pelajar Android
sumber
Terima kasih atas jawaban anda. Itulah yang telah saya lakukan di VollyClient.java saya. Lihatlah di: VolleyClient.java
Ali
Cukup periksa sekali dengan menggunakan kelas LruCache <String, Bitmap>, saya pikir ini akan menyelesaikan masalah Anda. Lihat LruCache
pelajar Android
Apakah Anda pernah memeriksa kode yang saya bagikan dengan Anda di komentar? Apa yang Anda miliki di kelas Anda yang saya lewatkan di sana?
Ali
Anda kehilangan kelas LruCache <String, Bitmap> dan metode sizeOf () yang menimpa seperti yang saya lakukan, semua tampaknya baik-baik saja bagi saya.
Pelajar Android
Oke, saya akan segera mencobanya, tetapi dapatkah Anda menjelaskan kepada saya apa yang telah Anda lakukan di sana yang memberikan keajaiban bagi Anda dan menyelesaikan masalah? source code Bagi saya sepertinya metode sizeOf Anda diganti harus dalam kode sumber.
Ali
1

bagi saya recyclerView.setHasFixedSize(true);bekerja

Pramod
sumber
Saya tidak berpikir item OP memiliki ukuran tetap
Irfandi D. Vendy
ini membantu saya dalam kasus saya
bst91
1

Dalam kasus saya, tidak ada jawaban di atas maupun jawaban dari pertanyaan stackoverflow lain yang memiliki masalah yang sama.

Ya, saya menggunakan animasi khusus setiap kali item diklik, yang saya panggil notifyItemChanged (int position, Object Payload) untuk meneruskan payload ke kelas CustomAnimator saya.

Perhatikan, ada 2 metode onBindViewHolder (...) yang tersedia di Adaptor RecyclerView. Metode onBindViewHolder (...) yang memiliki 3 parameter akan selalu dipanggil sebelum metode onBindViewHolder (...) yang memiliki 2 parameter.

Umumnya, kami selalu mengganti metode onBindViewHolder (...) yang memiliki 2 parameter dan akar utama masalahnya adalah saya melakukan hal yang sama, karena setiap kali notifyItemChanged (...) dipanggil, metode onBindViewHolder (...) kami akan disebut, di mana saya memuat gambar saya di ImageView menggunakan Picasso, dan inilah alasannya memuat lagi terlepas dari dari memori atau dari internet. Sampai dimuat, itu menunjukkan kepada saya gambar placeholder, yang menjadi alasan berkedip selama 1 detik setiap kali saya mengklik itemview.

Kemudian, saya juga mengganti metode onBindViewHolder (...) lain yang memiliki 3 parameter. Di sini saya memeriksa apakah daftar payload kosong, lalu saya mengembalikan implementasi kelas super dari metode ini, jika tidak ada payload, saya hanya mengatur nilai alpha dari itemView pemegang ke 1.

Dan yay saya mendapatkan solusi untuk masalah saya setelah menghabiskan satu hari penuh dengan sedih!

Berikut kode saya untuk metode onBindViewHolder (...):

onBindViewHolder (...) dengan 2 parameter:

@Override
public void onBindViewHolder(@NonNull RecyclerAdapter.ViewHolder viewHolder, int position) {
            Movie movie = movies.get(position);

            Picasso.with(context)
                    .load(movie.getImageLink())
                    .into(viewHolder.itemView.posterImageView);
    }

onBindViewHolder (...) dengan 3 parameter:

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty()) {
            super.onBindViewHolder(holder, position, payloads);
        } else {
            holder.itemView.setAlpha(1);
        }
    }

Berikut kode metode yang saya panggil onClickListener dari itemView viewHolder di onCreateViewHolder (...):

private void onMovieClick(int position, Movie movie) {
        Bundle data = new Bundle();
        data.putParcelable("movie", movie);

        // This data(bundle) will be passed as payload for ItemHolderInfo in our animator class
        notifyItemChanged(position, data);
    }

Catatan: Anda bisa mendapatkan posisi ini dengan memanggil metode getAdapterPosition () viewHolder Anda dari onCreateViewHolder (...).

Saya juga mengganti metode getItemId (int position) sebagai berikut:

@Override
public long getItemId(int position) {
    Movie movie = movies.get(position);
    return movie.getId();
}

dan menelepon setHasStableIds(true); objek adaptor saya dalam aktivitas.

Semoga ini bisa membantu jika tidak ada jawaban di atas yang berhasil!

Parth Bhanushali
sumber
1

Dalam kasus saya, ada masalah yang jauh lebih sederhana, tetapi dapat terlihat / terasa sangat mirip dengan masalah di atas. Saya telah mengubah ExpandableListView menjadi RecylerView dengan Groupie (menggunakan fitur ExpandableGroup Groupie). Tata letak awal saya memiliki bagian seperti ini:

<androidx.recyclerview.widget.RecyclerView
  android:id="@+id/hint_list"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:background="@android:color/white" />

Dengan layout_height disetel ke "wrap_content", animasi dari grup yang diperluas ke grup yang diciutkan terasa seperti itu akan berkedip, tetapi itu sebenarnya hanya menganimasikan dari posisi "salah" (bahkan setelah mencoba sebagian besar rekomendasi di utas ini).

Bagaimanapun, cukup mengubah layout_height menjadi match_parent seperti ini memperbaiki masalah.

<androidx.recyclerview.widget.RecyclerView
  android:id="@+id/hint_list"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@android:color/white" />
Tom
sumber
0

Saya memiliki masalah serupa dan ini berhasil untuk saya. Anda dapat memanggil metode ini untuk mengatur ukuran cache gambar

private int getCacheSize(Context context) {

    final DisplayMetrics displayMetrics = context.getResources().
            getDisplayMetrics();
    final int screenWidth = displayMetrics.widthPixels;
    final int screenHeight = displayMetrics.heightPixels;
    // 4 bytes per pixel
    final int screenBytes = screenWidth * screenHeight * 4;

    return screenBytes * 3;
}
developer_android
sumber
0

untuk aplikasi saya, saya mengalami beberapa perubahan data tetapi saya tidak ingin seluruh tampilan berkedip.

Saya menyelesaikannya dengan hanya memudarkan tampilan lama menjadi 0,5 alpha dan memulai alpha tampilan baru pada 0,5. Ini menciptakan transisi pemudaran yang lebih lembut tanpa membuat tampilan menghilang sepenuhnya.

Sayangnya karena implementasi pribadi, saya tidak dapat membuat subkelas DefaultItemAnimator untuk melakukan perubahan ini, jadi saya harus mengkloning kode dan membuat perubahan berikut

di animateChange:

ViewCompat.setAlpha(newHolder.itemView, 0);  //change 0 to 0.5f

di animateChangeImpl:

oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { //change 0 to 0.5f
Tunda
sumber
0

Menggunakan metode recyclerview yang tepat untuk memperbarui tampilan akan menyelesaikan masalah ini

Pertama, lakukan perubahan dalam daftar

mList.add(item);
or mList.addAll(itemList);
or mList.remove(index);

Kemudian beri tahu menggunakan

notifyItemInserted(addedItemIndex);
or
notifyItemRemoved(removedItemIndex);
or
notifyItemRangeChanged(fromIndex, newUpdatedItemCount);

Semoga ini bisa membantu !!

Sreedhu Madhu
sumber
Benar-benar tidak. Jika Anda membuat beberapa pembaruan per detik (pemindaian BLE dalam kasus saya) itu tidak berfungsi sama sekali. Saya menghabiskan satu hari untuk memperbarui RecyclerAdapter ini ... Lebih baik menyimpan ArrayAdapter. Sayang sekali tidak menggunakan pola MVC, tapi setidaknya itu hampir bisa digunakan.
Gojir4
0

Solusi Kotlin:

(recyclerViewIdFromXML.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
Robert Pal
sumber