Android RecyclerView: notifyDataSetChanged () IllegalStateException


130

NotifyDataSetChanged () kullanarak bir geri dönüşümün öğelerini güncellemeye çalışıyorum.

Bu, geri dönüşüm adaptöründeki onBindViewHolder () yöntemimdir.

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {

     //checkbox view listener
    viewHolder.getCheckbox().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

            //update list items
            notifyDataSetChanged();
        }
    });
}

Yapmak istediğim şey, bir onay kutusunu işaretledikten sonra liste öğelerini güncellemektir. Yine de yasadışı bir istisna alıyorum:"Cannot call this method while RecyclerView is computing a layout or scrolling"

java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
    at android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:1462)
    at android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onChanged(RecyclerView.java:2982)
    at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:7493)
    at android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:4338)
    at com.app.myapp.screens.RecycleAdapter.onRowSelect(RecycleAdapter.java:111)

Ben de notifyItemChanged () kullandım, aynı istisna. Bağdaştırıcıya bir şeylerin değiştiğini bildirmenin gizli bir yolu var mı?


Şimdi aynı sorunu yaşıyorum. setoncheckchanged dinleyiciyi viewholder yapıcısına koymak bana aynı hatayı
veriyor

Yanıtlar:


145

Bağdaştırıcınızın iç sınıfı olan 'setOnCheckedChangeListener ()' yöntemini ViewHolder'a taşımalısınız.

onBindViewHolder()başlatan bir yöntem değildir ViewHolder. Bu yöntem, her geri dönüştürücünün yenilenme adımıdır. Aradığınızda notifyDataSetChanged(), onBindViewHolder()her öğenin numarası olarak aranacaktır .

Bu yüzden , checkBox'ı notifyDataSetChanged()içine koyar onCheckChanged()ve başlatırsanız onBindViewHolder(), döngüsel yöntem çağrısı nedeniyle IllegalStateException alırsınız.

onay kutusunu tıklayın -> onCheckedChanged () -> notifyDataSetChanged () -> onBindViewHolder () -> onay kutusunu ayarla -> onChecked ...

Basitçe, bunu Adapter'e bir bayrak koyarak düzeltebilirsiniz.

bunu dene,

private boolean onBind;

public ViewHolder(View itemView) {
    super(itemView);
    mCheckBox = (CheckBox) itemView.findViewById(R.id.checkboxId);
    mCheckBox.setOnCheckChangeListener(this);
}

@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    if(!onBind) {
        // your process when checkBox changed
        // ...

        notifyDataSetChanged();
    }
}

...

@Override
public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) {
    // process other views 
    // ...

    onBind = true;
    viewHolder.mCheckBox.setChecked(trueOrFalse);
    onBind = false;
}

Anlıyorum, mantıklı. Platformun bu kadar basit davranışı tahmin etmesini ve bayraklara güvenmek yerine bir çözüm vermesini diliyorum ..
Arthur

İşlem AdapterViewObserversırasında bildirimde bulunmadığınız sürece dinleyiciyi nerede ayarladığınız önemli değildir onBindViewHolder().
Yaroslav Mytkalyk

6
Yorumda bazı iyileştirmelerle bu çözümü stackoverflow.com/a/32373999/1771194 tercih ediyorum . Ayrıca RecyclerView'da "RadioGroup" yapmama izin verdi.
Artem

listemdeki öğe pozisyonunu nasıl alırım?
filthy_wizard

2
bu, görüntüleyicide benim için çalışmıyor. hala çökme hatası alıyorum. Dizi listesindeki değişkenleri değiştirmem gerekiyor. çok ilginç. Listeyi nereye ekleyebileceğimden pek emin değilim.
filthy_wizard

46

Değişiklik yapmadan önce önceki dinleyiciyi sıfırlayabilirsiniz ve bu istisnayı almazsınız.

private CompoundButton.OnCheckedChangeListener checkedListener = new CompoundButton.OnCheckedChangeListener() {                      
                        @Override
                        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                            //Do your stuff
                    });;

    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {
        holder.checkbox.setOnCheckedChangeListener(null);
        holder.checkbox.setChecked(condition);
        holder.checkbox.setOnCheckedChangeListener(checkedListener);
    }

2
İyi yanıt, ancak her onBindViewHolder çağrısında dinleyici oluşturmamak daha iyidir. Bir tarla yapın.
Artem

1
Bir alanı kullanmak elbette daha iyidir, ben sadece işe yarayan bir örnek vermiştim. Ama uyarı için teşekkürler, cevabı güncelleyeceğim.
JoniDS

1
Aslında her seferinde yeni bir dinleyici bağlamam gerekiyor çünkü dinleyicinin her seferinde güncellenmiş bir konum değişkenine ihtiyacı var. Dolayısıyla bu harika bir cevap, bu yüzden bir İşleyici kullanmam gerekmiyor.
Rock Lee

Kabul edilen yanıtın ( stackoverflow.com/a/31069171/882251 ) önerdiği küresel durumu korumanız asla önerilmediğinden kesinlikle bunu yapmanın en iyi yoludur .
Darwind

En basit olanı !! Teşekkürler !!
DalveerSinghDaiya

39

HandlerÖğe eklemek ve notify...()buradan aramak için a kullanmak Handlersorunu benim için çözdü.


3
Doğru cevap bu, ayar yapılırken öğeyi değiştiremezsiniz (onBindViewHolder'ı çağırarak). Bu durumda, Handler.post ()
pjanecze

1
@ user1232726 Ana iş parçacığı üzerinde İşleyiciyi oluşturursanız, bir Döngüleyici belirtmeniz gerekmez (varsayılan olarak çağıran iş parçacıkları döngüleyici). Yani evet, bu benim tavsiyem. Aksi takdirde Looper'ı manuel olarak da belirleyebilirsiniz.
cybergen

ne yazık ki, aşağı kaydırdığımda ve tekrar yukarı kaydırdığımda onay kutularım işaretli kalmıyor. boo. lol
filthy_wizard

@ user1232726 bir yanıt arayın veya sorununuzu açıklayan yeni bir soru sorun.
cybergen

2
Şiddetle ediyorum vazgeçirmek bu sorunu çözmek için bir hacky yol olduğu gibi bu cevabı. Bunu daha çok yaparsanız, kodunuzun anlaşılması daha karmaşık hale gelir. Problemi anlamak için Moonsoo'nun cevabına ve problemi çözmek için JoniDS'nin cevabına bakın .
Kalpesh Patel

26

İyi bilmiyorum ama benim de aynı problemim vardı. Bunu kullanarak onClickListnerçözdümcheckbox

viewHolder.mCheckBox.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            if (model.isCheckboxBoolean()) {
                model.setCheckboxBoolean(false);
                viewHolder.mCheckBox.setChecked(false);
            } else {
                model.setCheckboxBoolean(true);
                viewHolder.mCheckBox.setChecked(true);
            }
            notifyDataSetChanged();
        }
    });

Bunu dene, bu yardımcı olabilir!


1
İyi iş) AMA sadece tıklamayla (eğer widget'ı (SwitchCompat) yavaş hareket ettirirsem, bu eylem kaçırılacak. Tek sorun bu
Vlad

12
protected void postAndNotifyAdapter(final Handler handler, final RecyclerView recyclerView, final RecyclerView.Adapter adapter) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (!recyclerView.isComputingLayout()) {
                    adapter.notifyDataSetChanged();
                } else {
                    postAndNotifyAdapter(handler, recyclerView, adapter);
                }
            }
        });
    }

Bağdaştırıcıya iki kez kolayca bildirimde bulunabileceğinizi düşünüyorum.
Максим Петлюк

8

Mesaj Hatası aldığınızda:

Cannot call this method while RecyclerView is computing a layout or scrolling

Basit, Sadece şu istisnaya neden olan şeyi yapın:

RecyclerView.post(new Runnable() {
    @Override
    public void run() {
        /** 
        ** Put Your Code here, exemple:
        **/
        notifyItemChanged(position);
    }
});

1
Bu benim için çalıştı. Bu çözümle ilgili herhangi bir sorun var mı merak ediyorsunuz?
Sayooj Valsan

7

Basit bir çözüm buldum -

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

    private RecyclerView mRecyclerView; 

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        mRecyclerView = recyclerView;
    }

    private CompoundButton.OnCheckedChangeListener checkedChangeListener 
    = (compoundButton, b) -> {
        final int position = (int) compoundButton.getTag();
        // This class is used to make changes to child view
        final Event event = mDataset.get(position);
        // Update state of checkbox or some other computation which you require
        event.state = b;
        // we create a runnable and then notify item changed at position, this fix crash
        mRecyclerView.post(new Runnable() {
            @Override public void run() {
                notifyItemChanged(position));
            }
        });
    }
}

Burada recyclerview bunu işlemeye hazır olduğunda bir pozisyon için notifyItemChanged için bir çalıştırılabilir oluşturuyoruz.


5

CheckBox öğeniz, çağırdığınızda çekilebilir olarak değişiyor, notifyDataSetChanged();bu nedenle bu istisna meydana gelebilir. Görüşünüzü notifyDataSetChanged();gönderirken aramayı deneyin . Örneğin:

buttonView.post(new Runnable() {
                    @Override
                    public void run() {
                        notifyDataSetChanged();
                    }
                });

4

İlk başta Moonsoo'nun cevabının (kabul edilen cevap) benim için işe yaramayacağını düşündüm çünkü setOnCheckedChangeListener()ViewHolder kurucusunda kendimi başlatamıyorum çünkü her seferinde onu bağlamam gerekiyor, böylece güncellenmiş bir pozisyon değişkeni alıyor. Ama ne dediğini anlamam uzun zamanımı aldı.

İşte bahsettiği "döngüsel yöntem çağrısına" bir örnek:

public void onBindViewHolder(final ViewHolder holder, final int position) {
    SwitchCompat mySwitch = (SwitchCompat) view.findViewById(R.id.switch);
    mySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                       if (isChecked) {
                           data.delete(position);
                           notifyItemRemoved(position);
                           //This will call onBindViewHolder, but we can't do that when we are already in onBindViewHolder!
                           notifyItemRangeChanged(position, data.size());
                       }
                   }
            });
    //Set the switch to how it previously was.
    mySwitch.setChecked(savedSwitchState); //If the saved state was "true", then this will trigger the infinite loop.
}

Bununla ilgili tek sorun, anahtarı açmak veya kapatmak için başlatmamız gerektiğinde (örneğin, geçmiş kaydedilmiş durumdan), nofityItemRangeChangedhangi çağrıları onBindViewHoldertekrar arayabilecek dinleyiciyi aramaktır . Sen diyemezsin onBindViewHoldersen zaten ne zaman onBindViewHolder] çünkü, sen olamaz notifyItemRangeChangedsen öğe aralığı değiştiğini bildiren ortasında zaten eğer. Ancak, aslında hiçbir şeyi tetiklemek istemeden, kullanıcı arayüzünü yalnızca açık veya kapalı göstermek için güncellemem gerekiyordu.

İşte JoniDS'in cevabından öğrendiğim sonsuz döngüyü engelleyecek çözüm . Checked ayarlamadan önce dinleyiciyi "null" olarak ayarladığımız sürece, dinleyiciyi tetiklemeden, sonsuz döngüden kaçınarak kullanıcı arayüzünü güncelleyecektir. Sonra dinleyiciyi sonra ayarlayabiliriz.

JoniDS kodu:

holder.checkbox.setOnCheckedChangeListener(null);
holder.checkbox.setChecked(condition);
holder.checkbox.setOnCheckedChangeListener(checkedListener);

Örneğime tam çözüm:

public void onBindViewHolder(final ViewHolder holder, final int position) {
    SwitchCompat mySwitch = (SwitchCompat) view.findViewById(R.id.switch);

    //Set it to null to erase an existing listener from a recycled view.
    mySwitch.setOnCheckedChangeListener(null);

    //Set the switch to how it previously was without triggering the listener.
    mySwitch.setChecked(savedSwitchState); //If the saved state was "true", then this will trigger the infinite loop.

    //Set the listener now.
    mySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (isChecked) {
                data.delete(position);
                notifyItemRemoved(position);
                //This will call onBindViewHolder, but we can't do that when we are already in onBindViewHolder!
                notifyItemRangeChanged(position, data.size());
            }
        }
    });
}

OnCheckedChangeListener'ı onBindViewHolder'da tekrar tekrar başlatmaktan kaçınmalısınız (bu şekilde daha az GC gerekir). Bunun onCreateViewHolder'da çağrılması gerekiyor ve konumu holder.getAdapterPosition () 'u çağırarak alırsınız.
android geliştiricisi

4

Neden RecyclerView.isComputingLayout()durumu aşağıdaki gibi kontrol etmiyorsunuz ?

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

    private RecyclerView mRecyclerView; 

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        mRecyclerView = recyclerView;
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {

        viewHolder.getCheckbox().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (mRecyclerView != null && !mRecyclerView.isComputingLayout()) {
                    notifyDataSetChanged();
                }
            }
        });
    }
}

2

Öğe, düzen yöneticisi tarafından bağlanırken, büyük olasılıkla geri aramayı tetikleyen onay kutunuzun işaretli durumunu ayarlıyorsunuz.

Elbette bu bir tahmindir çünkü yığın izinin tamamını yayınlamadınız.

RV düzeni yeniden hesaplarken adaptör içeriğini değiştiremezsiniz. Öğenin işaretli durumu geri aramada gönderilen değere eşitse notifyDataSetChanged işlevini çağırmayarak bundan kaçınabilirsiniz (bu durum checkbox.setChecked, arama geri aramayı tetikliyorsa böyle olacaktır ).


@Yigit teşekkürler! Sorunumun bir onay kutusuyla ilgisi yoktu, ancak adaptördeki farklı bir öğeyi bildirmem gereken daha karmaşık bir durumdu, ancak benzer bir çökme yaşıyordum. Bildirim mantığımı yalnızca veriler gerçekten değiştiğinde güncellenecek şekilde güncelledim ve çökme sorunumu çözdü. Bu yüzden RecyclerViews ile yeni kuralım: Hiçbir şey değişmediğinde bir şeyin değiştiğini bildirmeyin. Bu cevap için çok teşekkürler!
CodyEngel

2

OnCheckedChangeListener yerine onClickListner'ı onay kutusunda kullanın, bu sorunu çözecektir

viewHolder.myCheckBox.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            if (viewHolder.myCheckBox.isChecked()) {
                // Do something when checkbox is checked
            } else {
                // Do something when checkbox is unchecked                
            }
            notifyDataSetChanged();
        }
    });


1

Basit kullanım Gönderi:

new Handler().post(new Runnable() {
        @Override
        public void run() {
                mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1);
            }
        }
    });

0

Tam olarak bu sorunla karşılaştım! Moonsoo'nun cevabı teknemde gerçekten yüzmediğinde, biraz uğraştım ve benim için işe yarayan bir çözüm buldum.

İlk olarak, işte kodumdan bazıları:

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {

    final Event event = mDataset.get(position);

    //
    //  .......
    //

    holder.mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            event.setActive(isChecked);
            try {
                notifyItemChanged(position);
            } catch (Exception e) {
                Log.e("onCheckChanged", e.getMessage());
            }
        }
    });

Yaptığınız gibi tüm veri kümesi yerine, özellikle değiştirdiğim konum için adaptöre bildirdiğimi fark edeceksiniz. Bununla birlikte, bunun sizin için işe yarayacağını garanti edemesem de, çağrımı notifyItemChanged()bir dene / yakala bloğuna kaydırarak sorunu çözdüm . Bu sadece istisnayı yakaladı, ancak yine de adaptörümün durum değişikliğini kaydetmesine ve ekranı güncellemesine izin verdi!

Umarım bu birine yardımcı olur!

DÜZENLEME: Kabul ediyorum, bu muhtemelen konuyu ele almanın uygun / olgun bir yolu değil, ancak istisnayı ele almadan herhangi bir soruna yol açıyor gibi görünmediğinden, iyi olması durumunda paylaşacağımı düşündüm başka biri için yeterli.


0

Bunun nedeni, muhtemelen o satır için değeri yapılandırmadan önce 'dinleyiciyi' ayarlamanızdır; bu, onay kutusu için 'değeri yapılandırdığınızda' dinleyicinin tetiklenmesini sağlar.

Yapmanız gereken şey:

@Override
public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) {
   viewHolder.mCheckBox.setOnCheckedChangeListener(null);
   viewHolder.mCheckBox.setChecked(trueOrFalse);
   viewHolder.setOnCheckedChangeListener(yourCheckedChangeListener);
}

0
        @Override
        public void onBindViewHolder(final MyViewHolder holder, final int position) {
            holder.textStudentName.setText(getStudentList.get(position).getName());
            holder.rbSelect.setChecked(getStudentList.get(position).isSelected());
            holder.rbSelect.setTag(position); // This line is important.
            holder.rbSelect.setOnClickListener(onStateChangedListener(holder.rbSelect, position));

        }

        @Override
        public int getItemCount() {
            return getStudentList.size();
        }
        private View.OnClickListener onStateChangedListener(final RadioButton checkBox, final int position) {
            return new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (checkBox.isChecked()) {
                        for (int i = 0; i < getStudentList.size(); i++) {

                            getStudentList.get(i).setSelected(false);

                        }
                        getStudentList.get(position).setSelected(checkBox.isChecked());

                        notifyDataSetChanged();
                    } else {

                    }

                }
            };
        }

0

basitçe isPressed()yöntemini kullanın CompoundButton, onCheckedChanged(CompoundButton compoundButton, boolean isChecked)
örn.

public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {   
                      ... //your functionality    
                            if(compoundButton.isPressed()){
                                notifyDataSetChanged();
                            }
                        }  });

0

Checkbox ve RadioButton'ı kullanırken aynı sorunu yaşadım. Değiştirme notifyDataSetChanged()ile notifyItemChanged(position)çalıştı. isCheckedVeri modeline bir Boole alanı ekledim . Sonra Boolean değerini güncelledim ve onCheckedChangedListeneraradım notifyItemChanged(adapterPosition). Bu en iyi yol olmayabilir ama benim için çalıştı. Boole değeri, öğenin kontrol edilip edilmediğini kontrol etmek için kullanılır.


0

çoğunlukla beacause gerçekleşmesi notifydatasetchanged çağrı onCheckedchanged olay onay kutusu ve bu olay yine orada olduğu notifydatasetchanged .

Bunu çözmek için, onay kutusunun programlı olarak işaretlendiğini veya kullanıcının bastığını kontrol edebilirsiniz. bunun için isPressed yöntemi var.

bu nedenle tüm liste kodunu isPressed yönteminin içine sarın. ve bitti.

 holder.mBinding.cbAnnual.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {

                if(compoundButton.isPressed()) {


                       //your code
                        notifyDataSetChanged();   

            }
        });

0

Bu sorunla saatlerce acı çektim ve bu şekilde düzeltebilirsiniz. Ancak başlamadan önce bu çözüme yönelik bazı koşullar var.

MODEL SINIFI

public class SelectUserModel {

    private String userName;
    private String UserId;
    private Boolean isSelected;


    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserId() {
        return UserId;
    }

    public void setUserId(String userId) {
        UserId = userId;
    }

    public Boolean getSelected() {
        return isSelected;
    }

    public void setSelected(Boolean selected) {
        isSelected = selected;
    }
}

ADAPTÖR SINIFINDA KONTROL KUTUSU

CheckBox cb;

ADAPTÖR SINIFI YAPICI VE MODEL LİSTESİ

private List<SelectUserModel> userList;

public StudentListAdapter(List<SelectUserModel> userList) {
        this.userList = userList;

        for (int i = 0; i < this.userList.size(); i++) {
            this.userList.get(i).setSelected(false);
        }
    }

ONBINDVIEW [Lütfen onCheckChange yerine onclick kullanın]

public void onBindViewHolder(@NonNull final StudentListAdapter.ViewHolder holder, int position) {
    holder.cb.setChecked(user.getSelected());
    holder.cb.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {

            int pos = (int) view.getTag();
            Log.d(TAG, "onClick: " + pos);
            for (int i = 0; i < userList.size(); i++) {
                if (i == pos) {
                    userList.get(i).setSelected(true);
// an interface to listen to callbacks
                    clickListener.onStudentItemClicked(userList.get(i));
                } else {
                    userList.get(i).setSelected(false);
                }
            }
            notifyDataSetChanged();
        }
    });

}


-1

Benim için Metin Düzenle'den Bitti, Geri veya dış giriş dokunuşuyla çıktığımda bir sorun oluştu. Bu, modelin giriş metni ile güncellenmesine ve ardından canlı veri gözlemi yoluyla geri dönüşümcü görünümünü yenilemesine neden olur.

Sorun, imlecin / odağın EditText'te kalmasıydı.

Şunu kullanarak odağı sildiğimde:

editText.clearFocus() 

Geri dönüşümcü görünümünün değiştirilen veri yöntemini bildir, bu hatayı atmayı durdurdu.

Bunun, bu sorunun olası nedenlerinden / çözümlerinden biri olduğunu düşünüyorum. Bu istisnanın tamamen farklı bir nedenden kaynaklanabileceği için başka bir şekilde düzeltilmesi mümkündür.

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.