RecyclerView, notifyDatasetChanged () sonrasında yanıp sönüyor


114

API'den bazı verileri yükleyen, bir görüntü url'si ve bazı verileri içeren ve tembel yükleme görüntüsü için networkImageView kullanan bir RecyclerView'ım var.

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

İşte Adaptör için uygulama:

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

Sorun, recyclerView'de yenilemeye sahip olduğumuzda, başlangıçta çok kısa bir süre için tuhaf görünüyor.

Bunun yerine GridView / ListView kullandım ve beklediğim gibi çalıştı. Kınlama yoktu.

RecycleView için yapılandırma 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);

Böyle bir sorunla karşılaşan var mı? nedeni ne olabilir?


Yanıtlar:


126

RecyclerView.Adapter'ınızda kararlı kimlikler kullanmayı deneyin.

setHasStableIds(true)ve geçersiz kıl getItemId(int position).

Kararlı kimlikler olmadan notifyDataSetChanged(), ViewHolders genellikle aynı konumlara atanmaz. Benim durumumda göz kırpmanın nedeni buydu.

Bir bulabilirsiniz burada iyi bir açıklama.


1
Titremeyi çözen setHasStableIds (true) ekledim, ancak GridLayout'umdaki öğeler kaydırma sırasında hala yer değiştiriyor. GetItemId () içinde neyi geçersiz kılmalıyım? Teşekkürler
Balázs Orbán

3
Kimliği nasıl oluşturmamız gerektiğine dair bir fikriniz var mı?
Mauker

Müthişsin! Google DEĞİLDİR. Google ya bunu bilmiyor VEYA biliyorlar ve bizim bilmemizi istemiyorlar! THANK YOU MAN
MBH

1
öğe konumlarını bozarsanız, öğeler firebase veritabanından doğru sırada gelir.
MuhammadAliJr

1
@Mauker Nesnenizin benzersiz bir numarası varsa, bunu veya benzersiz bir dizeniz varsa, object.hashCode () öğesini kullanabilirsiniz. Bu benim için mükemmel çalışıyor
Simon Schubert

106

Bu sayı sayfasına göre .... varsayılan geri dönüştürülmüş öğe değiştirme animasyonudur ... Kapatabilirsiniz .. bunu deneyin

recyclerView.getItemAnimator().setSupportsChangeAnimations(false);

En son sürümde değişiklik

Android geliştirici blogundan alıntılanmıştır :

Bu yeni API'nin geriye dönük olarak uyumlu olmadığını unutmayın. Daha önce bir ItemAnimator uyguladıysanız bunun yerine, yeni API'yi sarmalayarak eski API'yi sağlayan SimpleItemAnimator'ı genişletebilirsiniz. Ayrıca bazı yöntemlerin ItemAnimator'dan tamamen kaldırıldığını da fark edeceksiniz. Örneğin, recyclerView.getItemAnimator (). SetSupportsChangeAnimations (false) 'ı çağırıyorsanız, bu kod artık derlenmeyecektir. Bunu şununla değiştirebilirsiniz:

ItemAnimator animator = recyclerView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
  ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}

5
@sabeer hala aynı sorun. Sorunu çözmüyor.
Shreyash Mahajan

3
@delive buna herhangi bir çözüm bulursanız bana bildirin
Shreyash Mahajan

Picasso kullanıyorum, bu bir şey, göz kırpan görünmüyor.

8
Kotlin: (recyclerView.itemAnimator as? SimpleItemAnimator) ?. supportsChangeAnimations = false
Pedro Paulo Amorim

Çalışıyor, ancak başka bir sorun geliyor (NPE) java.lang.NullPointerException: Boş nesne referansında 'int android.support.v7.widget.RecyclerView $ ItemAnimator $ ItemHolderInfo.left' alanından okumaya çalışın Herhangi bir yol var mı bunu çözmek için?
Navas pk

46

Bu basitçe çalıştı:

recyclerView.getItemAnimator().setChangeDuration(0);

bu codeItemAnimator animator'a iyi bir alternatif = recyclerView.getItemAnimator (); if (SimpleItemAnimator animatör örneği) {((SimpleItemAnimator) animator) .setSupportsChangeAnimations (false); }code
ziniestro

1
tüm animasyonları durdurur EKLE ve KALDIR
MBH

11

Bazı url'lerden resim yüklerken aynı sorunu yaşıyorum ve ardından imageView yanıp sönüyor. Kullanarak çözüldü

notifyItemRangeInserted()    

onun yerine

notifyDataSetChanged()

Bu, değiştirilmemiş eski verilerin yeniden yüklenmesini önler.


9

varsayılan animasyonu devre dışı bırakmak için bunu deneyin

ItemAnimator animator = recyclerView.getItemAnimator();

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

bu, android desteği 23'ten beri animasyonu devre dışı bırakmanın yeni yolu

bu eski yöntem, destek kitaplığının eski sürümü için çalışacaktır

recyclerView.getItemAnimator().setSupportsChangeAnimations(false)

4

mItemsKoleksiyonunuzun Adaptersizi destekleyen koleksiyon olduğunu varsayarsak , neden her şeyi kaldırıp yeniden ekliyorsunuz? Temelde ona her şeyin değiştiğini söylüyorsunuz, bu nedenle RecyclerView, aynı resim url'si olmasına rağmen, Görüntü kitaplığının, Görünümü sıfırladığı yerde düzgün şekilde işlemediğini varsaydığımdan, tüm görünümleri yeniden bağlar. Belki de AdapterView için bir çözüm geliştirmişlerdi, böylece GridView'da iyi çalışıyordu.

notifyDataSetChangedTüm görünümlerin yeniden bağlanmasına neden olacak çağrı yerine, ayrıntılı bildirim olaylarını çağırın (eklenen / kaldırılan / taşınan / güncellenen bildirim), böylece RecyclerView yalnızca gerekli görünümleri yeniden bağlar ve hiçbir şey titremeyecektir.


3
Ya da RecyclerView'daki bir "bug" olabilir mi? Açıkçası, son 6 yılda AbsListView ile iyi çalıştıysa ve şimdi RecyclerView ile çalışmıyorsa, RecyclerView ile bir şeylerin yolunda olmadığı anlamına gelir, değil mi? :) Buna hızlı bir bakış, ListView ve GridView'da verileri yenilediğinizde görünüm + konumu izlediklerini gösterir, böylece yenilediğinizde tam olarak aynı görüntü sahibini elde edersiniz. RecyclerView görüntü tutucularını karıştırırken titremeye neden olur.
vovkab

ListView üzerinde çalışmak değil o RecyclerView için doğru olduğu anlamına gelir. Bu bileşenlerin farklı mimarileri vardır.
yiğit

1
Katılıyorum, ancak bazen hangi öğelerin değiştiğini bilmek gerçekten zordur, örneğin imleçler kullanırsanız veya yalnızca tüm verileri yenilerseniz. Bu nedenle, geri dönüşüm uzmanı bu vakayı da doğru şekilde ele almalıdır.
vovkab

4

Recyclerview, varsayılan animatör olarak DefaultItemAnimator'ı kullanır. Aşağıdaki koddan da görebileceğiniz gibi, öğe değişikliğinde görünüm sahibinin alfasını değiştirirler:

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

Animasyonların geri kalanını saklamak ama "titremeyi" kaldırmak istedim, bu yüzden DefaultItemAnimator'ı klonladım ve yukarıdaki 3 alfa satırını kaldırdım.

Yeni animatörü kullanmak için RecyclerView'unuzda setItemAnimator () 'ı çağırmanız yeterlidir:

mRecyclerView.setItemAnimator(new MyItemAnimator());

Yanıp sönmeyi ortadan kaldırdı ama nedense titreyen bir etkiye neden oldu.
Mohamed Medhat

4

Kotlin'de RecyclerView için 'sınıf uzantısı' kullanabilirsiniz:

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()
    // ...
}

1

Hey @Ali, geç tekrar olabilir. Ben de bu sorunla karşılaştım ve aşağıdaki çözümle çözdüm, lütfen kontrol etmenize yardımcı olabilir.

LruBitmapCache.java sınıfı, görüntü önbellek boyutunu elde etmek için oluşturulur

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 sınıfı [genişletilmiş Uygulama] kodun altına eklendi

VolleyClient singleton sınıf yapıcısında, ImageLoader'ı başlatmak için aşağıdaki pasajı ekleyin

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

LruBitmapCache'yi döndürmek için getLruBitmapCache () yöntemini oluşturdum

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

Umarım sana yardımcı olur.


Cevabınız için teşekkürler. VollyClient.java'da tam olarak bunu yaptım. Bir göz atın: VolleyClient.java
Ali

LruCache <String, Bitmap> sınıfını kullanarak bir kez kontrol edin, bence probleminizi çözecektir. LruCache'ye
Android öğrenci

Yorumda sizinle paylaştığım kodu bir kez kontrol ettiniz mi? sınıfında orada kaçırdığım ne var?
Ali

LruCache <String, Bitmap> sınıfını genişletmeyi ve yaptığım gibi sizeOf () yöntemini geçersiz kılmayı kaçırıyorsunuz, kalan her şey bana uygun görünüyor.
Android öğrencisi

Tamam, çok yakında bir denerim, ama bana orada ne yaptın, sihri senin için yaptı ve sorunu çözebilir misin? sourcecode Bana göre overriden sizeOf yönteminizin kaynak kodunda olması gerektiği gibi geliyor.
Ali

1

benim için recyclerView.setHasFixedSize(true);çalıştı


OP'nin eşyalarının sabit boyutta olduğunu sanmıyorum
Irfandi D.Vendy

bu bana yardımcı oldu
bst91

1

Benim durumumda, ne yukarıdakilerin hiçbiri ne de aynı sorunları olan diğer yığın akışı sorularının yanıtları işe yaramadı.

Öğeye her tıklandığında özel animasyon kullanıyordum, bunun için payload'u CustomAnimator sınıfıma geçirmek için notifyItemChanged (int position, Object Payload) çağırıyordum.

RecyclerView Adaptöründe 2 onBindViewHolder (...) yöntemi bulunduğuna dikkat edin. 3 parametreye sahip onBindViewHolder (...) yöntemi, 2 parametreye sahip onBindViewHolder (...) yönteminden önce her zaman çağrılacaktır.

Genel olarak, her zaman 2 parametreye sahip onBindViewHolder (...) yöntemini geçersiz kılıyoruz ve sorunun ana kökü, notifyItemChanged (...) her çağrıldığında, onBindViewHolder (...) yöntemimiz Picasso kullanarak ImageView'da resmimi yüklediğim çağrıldım ve bellekten veya internetten bağımsız olarak yeniden yüklenmesinin nedeni buydu. Yüklenene kadar, öğe görünümüne her tıkladığımda 1 saniyeliğine yanıp sönme nedeni olan yer tutucu resmi gösteriyordu.

Daha sonra 3 parametreye sahip başka bir onBindViewHolder (...) yöntemini de geçersiz kılıyorum. Burada yük listesinin boş olup olmadığını kontrol ediyorum, sonra bu yöntemin süper sınıf uygulamasını döndürüyorum, aksi takdirde yükler varsa, sadece tutucunun itemView'unun alfa değerini 1 olarak ayarlıyorum.

Ve ne yazık ki bir tam günü boşa harcadıktan sonra sorunuma çözüm buldum!

İşte onBindViewHolder (...) yöntemleri için kodum:

onBindViewHolder (...) 2 parametreli:

@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 (...) 3 parametreli:

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

ViewHolder'ın onCreateViewHolder (...) içindeki itemView öğesinin onClickListener'ında çağırdığım yöntemin kodu:

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

Not: Bu konumu, viewHolder'ınızın getAdapterPosition () yöntemini onCreateViewHolder (...) 'dan çağırarak elde edebilirsiniz.

Ayrıca getItemId (int position) yöntemini aşağıdaki gibi geçersiz kıldım:

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

ve setHasStableIds(true);etkinlikte adaptör nesnemi çağırdı .

Umarım yukarıdaki cevaplardan hiçbiri işe yaramazsa bu yardımcı olur!


1

Benim durumumda çok daha basit bir sorun vardı, ancak yukarıdaki soruna çok benzeyebilir / hissedilebilir. Groupie ile bir ExpandableListView'ı RecylerView'a dönüştürdüm (Groupie'nin ExpandableGroup özelliğini kullanarak). İlk düzenimde şöyle bir bölüm vardı:

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

Layout_height "wrap_content" olarak ayarlandığında, genişletilmiş gruptan daraltılmış gruba animasyon yanıp sönecekmiş gibi geliyordu, ancak gerçekten sadece "yanlış" konumdan hareket ediyordu (bu konudaki önerilerin çoğunu denedikten sonra bile).

Her neyse, layout_height'ı bu şekilde match_parent olarak değiştirmek sorunu çözdü.

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

0

Benzer bir sorun yaşadım ve bu benim için çalıştı Görüntü önbelleği boyutunu ayarlamak için bu yöntemi arayabilirsiniz

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

0

Uygulamam için bazı verilerim değişti ancak tüm görünümün yanıp sönmesini istemedim.

Bunu yalnızca eski görünümü 0,5 alfa azaltarak ve yeni görünüm alfa 0,5'ten başlayarak çözdüm. Bu, görünümü tamamen ortadan kaldırmadan daha yumuşak bir solma geçişi yarattı.

Maalesef özel uygulamalar nedeniyle, bu değişikliği yapmak için DefaultItemAnimator alt sınıfını alamadım, bu yüzden kodu klonlamak ve aşağıdaki değişiklikleri yapmak zorunda kaldım

animateChange'de:

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

animateChangeImpl'de:

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

0

Görünümleri güncellemek için uygun geri dönüşüm görüntüleme yöntemlerini kullanmak bu sorunu çözecektir

İlk önce listede değişiklik yapın

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

Sonra kullanarak bildir

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

Umarım bu yardımcı olur !!


Kesinlikle hayır. Saniyeler içinde birkaç güncelleme yapıyorsanız (benim durumumda BLE taraması) hiç çalışmıyor. Bu boktan RecyclerAdapter'ı güncellemek için bir gün geçirdim ... ArrayAdapter'ı korumak daha iyi. MVC kalıbını kullanmamak utanç verici, ama en azından neredeyse kullanılabilir.
Gojir


0

Kotlin çözümü:

(recyclerViewIdFromXML.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.