Android RecyclerView: notifyDataSetChanged () IllegalStateException


130

Я намагаюся оновити елементи рециркуляції за допомогою 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 (), той же виняток. Будь-який секретний спосіб оновити, щоб повідомити адаптер, що щось змінилося?


Я маю це ж питання зараз. якщо помістити слухача, що змінився, перевіривши сетон перевіреного в конструкторі глядача, дайте мені ту саму помилку
filthy_wizard

Відповіді:


145

Вам слід перемістити метод 'setOnCheckedChangeListener ()' до ViewHolder, який є внутрішнім класом вашого адаптера.

onBindViewHolder()не є методом ініціалізації ViewHolder. Цей метод є етапом оновлення кожного елемента рециркулятора. Коли ви телефонуєте notifyDataSetChanged(), onBindViewHolder()буде називатися як кількість кожного пункту разів.

Отже, якщо ви notifyDataSetChanged()поставите onCheckChanged()і ініціалізуєте checkBox 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;
}

Я бачу, має сенс. Бажаю, щоб платформа спрогнозувала таку просту поведінку і дала б рішення, замість того, щоб покладатися на прапори ..
Артур

Це не має значення , де ви встановите слухача до тих пір , поки ви не повідомляють , AdapterViewObserverа onBindViewHolder()в процесі.
Ярослав Миткалик

6
Я вважаю за краще це рішення stackoverflow.com/a/32373999/1771194 з деякими поліпшеннями в коментарях. Це також дозволило мені зробити "RadioGroup" в RecyclerView.
Артем

як я можу отримати позицію предметів у своєму списку ??
filthy_wizard

2
це не працює для мене в глядачі. як і раніше отримати помилку аварії. Мені потрібно змінити VAR в arraylist. дуже дивно. не надто впевнений, куди я можу прикласти список.
filthy_wizard

45

Ви можете просто скинути попереднього слухача, перш ніж вносити зміни, і ви не отримаєте цього винятку.

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
Хороша відповідь, але краще не створювати слухача під час кожного дзвінка onBindViewHolder. Зробіть це як поле.
Артем

1
Використовувати поле, звичайно, краще, я просто наводив приклад, який працює. Але дякую за попередження, відповідь я оновлю.
JoniDS

1
Мені фактично потрібно зв’язувати нового слухача кожен раз, тому що слухачеві потрібно щоразу оновлювати змінну позиції. Тож це чудова відповідь, тому мені не доведеться використовувати обробник.
Рок Лі

Це, безумовно, найкращий спосіб зробити це, оскільки ніколи не рекомендується зберігати глобальний стан, на що приймається відповідь ( stackoverflow.com/a/31069171/882251 ).
Дарвінд

Найпростіший !! Дякую !!
DalveerSinghDaiya

39

Використання Handlerдля додавання елементів та дзвінків notify...()із цього Handlerвирішило проблему для мене.


3
Це правильна відповідь, ви не можете змінити елемент під час його встановлення (з викликом onBindViewHolder). У такому випадку вам потрібно зателефонувати notifyDataSetChanged в кінці поточного циклу, зателефонувавши Handler.post ()
pjanecze

1
@ user1232726 Якщо ви створюєте обробник на головній нитці, вам не потрібно вказувати Looper (за замовчуванням петлевик ниток, що викликає). Так що так, це моя порада. В іншому випадку ви також можете вказати петлю вручну.
кіберген

на жаль, мої прапорці не залишаються прапорцями, коли я прокручую вниз і прокручую назад знову. бу. lol
filthy_wizard

@ user1232726 шукайте відповідь або задайте нове запитання з описом вашої проблеми.
кіберген

2
Я б сильно не відштовхувався від цієї відповіді, оскільки це шалений спосіб вирішення проблеми. Чим більше ви робите це, тим більше ваш код стає складним для розуміння. Зверніться до відповіді Moonsoo, щоб зрозуміти проблему та відповіді JoniDS, щоб вирішити проблему.
Kalpesh Patel

26

Я не знаю добре, але у мене також була така ж проблема. Я вирішив цю проблему, використовуючи onClickListnerна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();
        }
    });

Спробуйте це, це може допомогти!


1
Приємна робота) АЛЕ лише при натисканні (якщо я повільно рухаюся віджет (SwitchCompat), ця дія буде пропущена. Це єдине питання
Влад

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

Я припускаю, що ви можете легко сповістити адаптер двічі.
Максим Петлюк

8

Якщо у вас є помилка повідомлення:

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

1
Це працювало для мене. Цікаво, чи є якесь питання з цим рішенням?
Сайой Валсан

7

Знайшли просте рішення -

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

Тут ми створюємо запуск для повідомленняItemChanged для позиції, коли reciklierview готовий впоратися з цим.


5

під час дзвінка ваш елемент CheckBox змінюється в черговому режимі, notifyDataSetChanged();щоб сталося таке виключення. Спробуйте зателефонувати notifyDataSetChanged();після публікації. Наприклад:

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

4

Спочатку я подумав , що відповідь 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якщо вже посеред повідомлення про те, що діапазон елементів змінився. Але мені потрібно було лише оновити інтерфейс користувача, щоб показувати його або вимикати, не бажаючи насправді нічого запускати.

Ось рішення, яке я дізнався з відповіді JoniDS, що запобіжить нескінченну петлю. Поки ми встановимо слухача на "null" перед тим, як встановити Checked, він оновить інтерфейс користувача, не запускаючи слухача, уникаючи нескінченного циклу. Тоді ми можемо встановити слухача після.

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

Вам слід уникати ініціалізації OnCheckedChangeListener знову і знову в onBindViewHolder (для цього потрібно менше GC). Це повинно бути викликано в onCreateViewHolder, і ви отримаєте позицію, зателефонувавши hold.getAdapterPosition ().
андроїд розробник

4

Чому б не перевірити 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();
                }
            }
        });
    }
}

2

Поки елемент пов'язаний диспетчером макетів, дуже ймовірно, що ви встановлюєте встановлений прапорець свого прапорця, що викликає зворотний виклик.

Звичайно, це здогад, оскільки ви не опублікували повний слід стека.

Ви не можете змінити вміст адаптера, поки RV перераховує макет. Ви можете уникнути цього, не зателефонувавши notifyDataSetChanged, якщо перевірений стан елемента дорівнює значенню, надісланому у зворотному дзвінку (що буде у випадку, якщо виклик checkbox.setCheckedвикликає зворотний виклик).


Дякую @yigit! Моя проблема не була пов'язана з прапором, але більш складною ситуацією, коли мені довелося повідомити про інший елемент в адаптері, але в мене відбувся подібний збій. Я оновив свою логіку сповіщень, щоб оновлюватись лише тоді, коли дані фактично змінюються, і це вирішило мій збій. Тому моє нове правило з RecyclerViews: не повідомляйте, що щось змінилося, коли нічого не змінилося. Велике спасибі за цю відповідь!
CodyEngel

2

Використовуйте onClickListner на галочці замість 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();
        }
    });


1

Просте використання:

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

0

Я зіткнувся з цим точним питанням! Після того, як відповідь 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()дзвінок у блок "пробувати / ловити". Це просто спіймало виняток, але все ж дозволило моєму адаптеру зареєструвати зміни стану та оновити показ!

Сподіваюся, це допоможе комусь!

EDIT: Я визнаю, що це, мабуть, не належний / зрілий спосіб вирішення проблеми, але оскільки, схоже, це не викликає проблем, залишаючи виняток необробленим, я подумав, що поділюся, якщо це було добре достатньо для когось іншого.


0

Це відбувається тому, що ви, ймовірно, встановлюєте "слухача" перед тим, як налаштувати значення для цього рядка, що змушує слухача спрацьовувати, коли ви "налаштовуєте значення" для прапорця.

Що вам потрібно зробити:

@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

просто використовувати isPressed()метод CompoundButtonв onCheckedChanged(CompoundButton compoundButton, boolean isChecked)
напр

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

0

У мене була така ж проблема із використанням прапорця і RadioButton. Заміна notifyDataSetChanged()на notifyItemChanged(position)відпрацьовану. Я додав булеве поле isCheckedдо моделі даних. Потім я оновив булеве значення і в onCheckedChangedListener, я зателефонував notifyItemChanged(adapterPosition). Це може бути не найкращим способом, але працював для мене. Булеве значення використовується для перевірки того, чи перевіряється елемент.


0

в основному це трапляється через те, що сповіщення позначається змінити виклик onCheckedchanged подія прапорець, і в цьому випадку знову є notifydatasetchanged .

Щоб вирішити це, ви можете просто встановити, що прапорець перевіряється програмно або користувач натискає на нього. існує метод isPress для цього.

тому загортайте весь код списку всередині методу isPress. і зроблено.

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

                if(compoundButton.isPressed()) {


                       //your code
                        notifyDataSetChanged();   

            }
        });

0

Я страждав з цією проблемою протягом години, і ось як ви можете її виправити. Але перед тим, як почати, є деякі умови цього рішення.

МОДЕЛЬНИЙ КЛАС

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

ПРОВЕРКА В КЛАСІ ADAPTER

CheckBox cb;

КОНСТРУКТОР КЛАСУ АДАПТЕРУ І СПИСОК МОДЕЛІ

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 [Будь ласка, використовуйте onclick замість 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();
        }
    });

}


-1

Для мене проблема виникла, коли я вийшов з EditText від Done, Back або за допомогою зовнішнього вводу. Це призводить до оновлення моделі з вхідним текстом, а потім оновлення подання рециркулятора за допомогою спостереження за живими даними.

Проблема полягала в тому, що курсор / фокус залишаються в EditText.

Коли я видалив фокус за допомогою:

editText.clearFocus() 

Повідомляти про змінений спосіб перегляду рециркулятора перестала кидати цю помилку.

Я думаю, що це одна з можливих причин / рішень цієї проблеми. Цілком можливо, що цей виняток можна зафіксувати інакше, оскільки він може бути викликаний зовсім іншою причиною.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.