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 (), то же исключение. Любой секретный способ обновления, чтобы уведомить адаптер, что что-то изменилось?

737   19  

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

Я не знаю хорошо, но у меня тоже была такая же проблема. Я решил это с помощью onClickListner on checkbox

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

попробуйте это, это может помочь!

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

    Ничего не найдено.