Android RecyclerView: notifyDataSetChanged() IllegalStateException
Я пытаюсь обновить элементы recycleview с помощью notifyDataSetChanged().
Это мой метод onBindViewHolder () в адаптере recycleview.
@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();
}
});
}
то, что я хочу сделать, это обновить элементы списка, после того, как я поставлю флажок. Я получаю незаконное исключение, хотя:"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)
Я также использовал notifyItemChanged (), то же исключение. Любой секретный способ обновления, чтобы уведомить адаптер, что что-то изменилось?
19 ответов:
вы должны переместить метод 'setOnCheckedChangeListener ()' в ViewHolder, который является внутренним классом на вашем адаптере.
onBindViewHolder()это не метод, который инициализируетViewHolder. Этот метод является шагом обновления каждого элемента recycler. Когда вы звонитеnotifyDataSetChanged(),onBindViewHolder()будет вызываться как число каждого элемента раз.так что если вы
notifyDataSetChanged()положить вonCheckChanged()и инициализировать флажок вonBindViewHolder(), вы получите IllegalStateException из-за циклического вызова метода.клик флажок - > onCheckedChanged () - > notifyDataSetChanged () - > onBindViewHolder () - > установить флажок - > onChecked...
просто, вы можете исправить это, поставив один флаг в адаптер.
попробуйте это,
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; }
С помощью
Handlerдля добавления элементов и вызовnotify...()от этогоHandlerИсправлена проблема для меня.
вы можете просто сбросить предыдущий прослушиватель, прежде чем вносить изменения, и вы не получите это исключение.
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); }
Я не знаю хорошо, но у меня тоже была такая же проблема. Я решил это с помощью
onClickListneroncheckboxviewHolder.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(); } });попробуйте это, это может помочь!
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); } } }); }
найдено простое решение -
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)); } }); } }здесь мы создаем runnable для notifyItemChanged для позиции, когда recyclerview готов ее обрабатывать.
когда у вас есть сообщение об ошибке:
Cannot call this method while RecyclerView is computing a layout or scrollingПросто, Просто сделайте то, что вызывает исключение в:
RecyclerView.post(new Runnable() { @Override public void run() { /** ** Put Your Code here, exemple: **/ notifyItemChanged(position); } });
сначала я думал ответ Moonsoo (принято отвечать) не будет работать для меня, потому что я не могу инициализировать мой
setOnCheckedChangeListener()в конструкторе ViewHolder, потому что мне нужно привязывать его каждый раз, чтобы он получал обновленную переменную позиции. Но мне потребовалось много времени, чтобы понять, что он говорит.вот пример "кругового вызова метода", о котором он говорит:
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. }единственная проблема с этим, это когда нам нужно инициализируйте переключатель, чтобы он был включен или выключен (например, из прошлого сохраненного состояния), он вызывает прослушиватель, который может вызвать
nofityItemRangeChangedкоторых звонкиonBindViewHolderеще раз. Вы не можете позвонитьonBindViewHolderкогда вы уже находитесь вonBindViewHolder], потому что вы не можетеnotifyItemRangeChangedесли вы уже находитесь в середине уведомления о том, что диапазон элементов изменился. но мне нужно было только обновить пользовательский интерфейс, чтобы показать его или выключить, не желая на самом деле ничего запускать.вот решение, которое я узнал от ответ Джонидса это предотвратит бесконечный цикл. пока мы устанавливаем прослушиватель на "null", прежде чем мы установим флажок, он обновит пользовательский интерфейс, не вызывая прослушивателя, избегая бесконечного цикла. затем мы можем установить слушателя после.
JoniDS это:holder.checkbox.setOnCheckedChangeListener(null); holder.checkbox.setChecked(condition); holder.checkbox.setOnCheckedChangeListener(checkedListener);полное решение моего примера:
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()); } } }); }
ваш элемент флажка находится в изменении drawable при вызове
notifyDataSetChanged();Так что это исключение будет происходить. Попробуйте позвонитьnotifyDataSetChanged();в посте вашего представления. Например:buttonView.post(new Runnable() { @Override public void run() { notifyDataSetChanged(); } });
в то время как элемент привязывается менеджером компоновки, очень вероятно, что вы устанавливаете проверенное состояние своего флажка, который запускает обратный вызов.
конечно, это предположение, потому что вы не опубликовали полную трассировку стека.
вы не можете изменить содержимое адаптера, пока RV пересчитывает макет. Вы можете избежать этого, не вызывая notifyDataSetChanged, если проверенное состояние элемента равно значению, отправленному в обратном вызове (что будет иметь место, если звоню
checkbox.setCheckedвызывает обратный вызов).
используйте onclicklistner on checkbox вместо OnCheckedChangeListener, это решит проблему
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(); } });
до
notifyDataSetChanged()просто проверьте это с помощью этого метода:recyclerView.IsComputingLayout()
почему бы не проверить
RecyclerView.isComputingLayout()состояние следующим образом?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(); } } }); } }
простой пост использования:
new Handler().post(new Runnable() { @Override public void run() { mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1); } } });
я столкнулся с этим вопросом! После того, как ответ Moonsoo на самом деле не пустил мою лодку, я немного повозился и нашел решение, которое сработало для меня.
во-первых, вот мой код:
@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()); } } });вы заметите, что я специально уведомляю адаптер для позиции, которую я меняю, а не весь набор данных, как вы делаете. Это, как говорится, Хотя я не могу гарантировать это будет работать для вас, я решил проблему, обернув мой
notifyItemChanged()вызов в try / catch блок. Это просто поймало исключение, но все же позволило моему адаптеру зарегистрировать изменение состояния и обновить дисплей!надеюсь, это поможет кому-то!
EDIT: я признаю, что это, вероятно, не правильный/зрелый способ справиться с проблемой, но поскольку он, похоже, не вызывает никаких проблем, оставляя исключение необработанным, я подумал, что поделюсь, если это будет достаточно хорошо для кого-то другого.
это происходит потому, что вы, вероятно, устанавливаете "прослушиватель" перед настройкой значения для этой строки, Что заставляет прослушиватель срабатывать при "настройке значения" для флажка.
что вам нужно сделать, это:
@Override public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) { viewHolder.mCheckBox.setOnCheckedChangeListener(null); viewHolder.mCheckBox.setChecked(trueOrFalse); viewHolder.setOnCheckedChangeListener(yourCheckedChangeListener); }
@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 { } } }; }
для меня я слушал изменение рейтинга рейтинговой панели, но при длительном нажатии нескольких кликов сразу приложение рушилось из-за проблемы, а затем нашел четкое решение, если я хотел notifydatasetchange(); в bindviewholder обработал его с помощью обработчика, заданного:
//inside bindViewHolder new Handler().post(new Runnable() { @Override public void run() { notifyDataSetChanged(); } });надеюсь, что это решит проблему.
Внимание! Это исключение, вероятно, сигнализирует о том, что вы обновляете адаптер не от UI-thread! Например, вы заполняете адаптер из RxJava и забыли добавить
.observeOn(AndroidSchedulers.mainThread()).Если вы уверены, что вы этого не сделали, см. решение на Kotlin (спасибо @Брюс):
private var recyclerView: RecyclerView? = null override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { super.onAttachedToRecyclerView(recyclerView) this.recyclerView = recyclerView } fun setNewItem(position: Int, text: String) { // Your changes to an item start here. // ... if (recyclerView?.isComputingLayout == false) { notifyItemChanged(position) } else { recyclerView?.handler?.post { notifyItemChanged(position) } } }
Comments