Próbuję zaktualizować elementy widoku recyklingu przy użyciu notifyDataSetChanged ().
To jest moja metoda onBindViewHolder () w adapterze recyklingowym.
@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();
}
});
}
Chcę zaktualizować pozycje na liście po zaznaczeniu pola wyboru. Mam jednak nielegalny wyjątek:"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)
Użyłem również notifyItemChanged (), ten sam wyjątek. Jakikolwiek tajny sposób aktualizacji w celu powiadomienia adaptera, że coś się zmieniło?
android
android-recyclerview
Arthur
źródło
źródło
Odpowiedzi:
Należy przenieść metodę „setOnCheckedChangeListener ()” do ViewHolder, która jest klasą wewnętrzną adaptera.
onBindViewHolder()
nie jest metodą inicjującąViewHolder
. Ta metoda jest krokiem odświeżania każdego elementu recyklingu. Kiedy zadzwonisznotifyDataSetChanged()
,onBindViewHolder()
zostanie wywołany jako liczba razy każdej pozycji.Więc jeśli
notifyDataSetChanged()
wstawiszonCheckChanged()
checkBox i zainicjujesz goonBindViewHolder()
, otrzymasz IllegalStateException z powodu cyklicznego wywołania metody.kliknij pole wyboru -> onCheckedChanged () -> notifyDataSetChanged () -> onBindViewHolder () -> ustaw pole wyboru -> onChecked ...
Po prostu możesz to naprawić, umieszczając jedną flagę w adapterze.
Spróbuj tego,
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; }
źródło
AdapterViewObserver
, gdyonBindViewHolder()
w toku.Możesz po prostu zresetować poprzedniego odbiornika przed wprowadzeniem zmian i nie otrzymasz tego wyjątku.
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); }
źródło
Używanie a
Handler
do dodawania przedmiotów i dzwonienianotify...()
z tegoHandler
rozwiązania rozwiązało problem.źródło
Nie wiem dobrze, ale miałem ten sam problem. Rozwiązałem to, używając
onClickListner
oncheckbox
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(); } });
Spróbuj tego, to może pomóc!
źródło
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); } } }); }
źródło
Znalazłem proste rozwiązanie -
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)); } }); } }
Tutaj tworzymy runnable do notificationItemChanged dla pozycji, gdy recyclinglerview jest gotowy do obsługi.
źródło
Gdy pojawi się komunikat o błędzie:
Cannot call this method while RecyclerView is computing a layout or scrolling
Proste, po prostu zrób to, co powoduje wyjątek w:
RecyclerView.post(new Runnable() { @Override public void run() { /** ** Put Your Code here, exemple: **/ notifyItemChanged(position); } });
źródło
Na początku myślałem, że odpowiedź Moonsoo (zaakceptowana odpowiedź) nie zadziała dla mnie, ponieważ nie mogę zainicjować mojego
setOnCheckedChangeListener()
w konstruktorze ViewHolder, ponieważ muszę go wiązać za każdym razem, aby uzyskać zaktualizowaną zmienną pozycji. Ale zajęło mi dużo czasu, zanim zrozumiałem, o czym mówi.Oto przykład „okrągłego wywołania metody”, o którym mówi:
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. }
Jedynym problemem jest to, że kiedy musimy zainicjować przełącznik, aby był włączony lub wyłączony (na przykład z poprzedniego stanu zapisanego), jest to wywołanie słuchacza, który może zadzwonić,
nofityItemRangeChanged
który dzwonionBindViewHolder
ponownie. Nie możesz zadzwonić,onBindViewHolder
kiedy już jesteś w środkuonBindViewHolder
], ponieważ nie możesz,notifyItemRangeChanged
jeśli jesteś już w trakcie powiadamiania o zmianie zakresu przedmiotów. Ale musiałem tylko zaktualizować interfejs użytkownika, aby go włączyć lub wyłączyć, nie chcąc niczego uruchamiać.Oto rozwiązanie, którego nauczyłem się z odpowiedzi JoniDS, które zapobiegnie nieskończonej pętli.Tak długo, jak ustawimy odbiornik na „null” przed ustawieniem Checked, wówczas zaktualizuje on interfejs użytkownika bez wyzwalania detektora, unikając nieskończonej pętli. Następnie możemy ustawić słuchacza po.
Kod JoniDS:
holder.checkbox.setOnCheckedChangeListener(null); holder.checkbox.setChecked(condition); holder.checkbox.setOnCheckedChangeListener(checkedListener);
Pełne rozwiązanie mojego przykładu:
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()); } } }); }
źródło
Twój element CheckBox zmienia się do rysowania po wywołaniu,
notifyDataSetChanged();
więc ten wyjątek wystąpiłby. Spróbuj zadzwonićnotifyDataSetChanged();
pocztą swojego widoku. Na przykład:buttonView.post(new Runnable() { @Override public void run() { notifyDataSetChanged(); } });
źródło
Dlaczego nie sprawdzić
RecyclerView.isComputingLayout()
stanu w następujący sposób?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(); } } }); } }
źródło
Podczas gdy element jest wiązany przez menedżera układu, jest bardzo prawdopodobne, że ustawiasz zaznaczony stan swojego pola wyboru, który uruchamia wywołanie zwrotne.
Oczywiście to przypuszczenie, ponieważ nie opublikowałeś pełnego śladu stosu.
Nie można zmienić zawartości adaptera, gdy RV ponownie oblicza układ. Możesz tego uniknąć, nie wywołując notifyDataSetChanged, jeśli stan zaznaczenia elementu jest równy wartości wysłanej w wywołaniu zwrotnym (co będzie miało miejsce w przypadku wywołania
checkbox.setChecked
wywołania zwrotnego).źródło
Użyj onClickListner na polu wyboru zamiast OnCheckedChangeListener, rozwiąże to problem
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(); } });
źródło
Zanim
notifyDataSetChanged()
po prostu to sprawdź tą metodą:recyclerView.IsComputingLayout()
źródło
Proste użycie Post:
new Handler().post(new Runnable() { @Override public void run() { mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1); } } });
źródło
Cierpiałem z tym problemem przez godzinę i tak można to naprawić. Ale zanim zacząłeś, istnieją pewne warunki dotyczące tego rozwiązania.
KLASA MODELOWA
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; } }
CHECKBOX W KLASIE ADAPTERÓW
KONSTRUKTOR KLASY ADAPTERÓW I LISTA MODELI
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 [Użyj onclick zamiast onCheckChange]
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(); } });
}
źródło
Natknąłem się dokładnie na ten problem! Po tym, jak odpowiedź Moonsoo tak naprawdę nie unosiła mojej łodzi na wodzie, trochę się pogubiłem i znalazłem rozwiązanie, które działało dla mnie.
Po pierwsze, oto część mojego kodu:
@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()); } } });
Zauważysz, że specjalnie powiadamiam adapter o zmienianej pozycji, zamiast o całym zestawie danych, tak jak robisz. Biorąc to pod uwagę, chociaż nie mogę zagwarantować, że to zadziała, rozwiązałem problem, zawijając moje
notifyItemChanged()
wywołanie blokiem try / catch. To po prostu złapało wyjątek, ale nadal pozwalało mojemu adapterowi zarejestrować zmianę stanu i zaktualizować wyświetlacz!Mam nadzieję, że to komuś pomoże!
EDYCJA: Przyznaję, że prawdopodobnie nie jest to właściwy / dojrzały sposób rozwiązania problemu, ale ponieważ nie wydaje się, aby powodował jakiekolwiek problemy, pozostawiając wyjątek bez obsługi, pomyślałem, że podzielę się, na wypadek, gdyby był dobry wystarczy dla kogoś innego.
źródło
Dzieje się tak, ponieważ prawdopodobnie ustawiasz „detektor” przed skonfigurowaniem wartości w tym wierszu, co powoduje, że detektor zostanie wyzwolony, gdy „skonfigurujesz wartość” dla pola wyboru.
Musisz:
@Override public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) { viewHolder.mCheckBox.setOnCheckedChangeListener(null); viewHolder.mCheckBox.setChecked(trueOrFalse); viewHolder.setOnCheckedChangeListener(yourCheckedChangeListener); }
źródło
@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 { } } }; }
źródło
wystarczy użyć
isPressed()
metodyCompoundButton
naonCheckedChanged(CompoundButton compoundButton, boolean isChecked)
przykład
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { ... //your functionality if(compoundButton.isPressed()){ notifyDataSetChanged(); } } });
źródło
Miałem ten sam problem, używając pola wyboru i przycisku RadioButton. Wymiana
notifyDataSetChanged()
znotifyItemChanged(position)
działało. Dodałem pole BooleanisChecked
do modelu danych. Następnie zaktualizowałem wartość logiczną ionCheckedChangedListener
zadzwoniłemnotifyItemChanged(adapterPosition)
. To może nie jest najlepszy sposób, ale dla mnie zadziałał. Wartość logiczna służy do sprawdzania, czy element jest zaznaczony.źródło
przeważnie dzieje się tak, ponieważ notifydatasetchanged wywołanie onCheckedzmienione zdarzenie pola wyboru iw tym przypadku ponownie jest notifydatasetchanged .
aby go rozwiązać, wystarczy zaznaczyć, że pole wyboru jest zaznaczone przez programowe lub naciśnięte przez użytkownika. istnieje metoda isPressed dla niego.
więc zawiń cały kod listnera wewnątrz metody isPressed. i gotowe.
holder.mBinding.cbAnnual.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) { if(compoundButton.isPressed()) { //your code notifyDataSetChanged(); } });
źródło
U mnie problem wystąpił, gdy wyszedłem z EditText przez Gotowe, Wstecz lub zewnętrzne dotknięcie. Powoduje to aktualizację modelu za pomocą tekstu wejściowego, a następnie odświeżenie widoku recyklera poprzez obserwację danych na żywo.
Problem polegał na tym, że kursor / fokus pozostały w EditText.
Kiedy usunąłem fokus za pomocą:
editText.clearFocus()
Powiadamiaj, że metoda widoku recyklera danych została zmieniona, przestała zgłaszać ten błąd.
Myślę, że jest to jeden z możliwych powodów / rozwiązań tego problemu. Możliwe, że ten wyjątek można naprawić w inny sposób, ponieważ może to wynikać z zupełnie innego powodu.
źródło