2014-11-21 7 views
89

Я пытаюсь обновить элементы recycleview, используя notifyDataSetChanged().Android RecyclerView: notifyDataSetChanged() IllegalStateException

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

+0

имейте это же вопрос сейчас. поместив прослушиватель setoncheckchanged в конструкторе viewholder, дайте мне ту же ошибку –

ответ

99

Вы должны переместить метод '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; 
} 
+0

Я вижу, имеет смысл. Желание платформы предсказать такое простое поведение и дать решение вместо того, чтобы полагаться на флаги. – Arthur

+0

Не имеет значения, где вы устанавливаете слушателя, пока вы не уведомляете «AdapterViewObserver», а 'onBindViewHolder()' в ходе выполнения. –

+4

Я предпочитаю это решение http://stackoverflow.com/a/32373999/1771194 с некоторыми улучшениями в комментарии. Это также позволило мне сделать «RadioGroup» в RecyclerView. –

2

Пока элемент связан с менеджером компоновки, очень вероятно, что вы устанавливаете проверенное состояние вашего флажка, который вызывает обратный вызов.

Конечно, это предположение, потому что вы не опубликовали полную трассировку стека.

Вы не можете изменить содержимое адаптера, в то время как RV пересчитывает компоновку. Вы можете избежать этого, не вызвав notifyDataSetChanged, если проверенное состояние элемента равно значению, отправленному в обратном вызове (что будет иметь место, если вызов checkbox.setChecked вызывает обратный вызов).

+0

Спасибо @yigit! Моя проблема не связана с флажком, но более сложная ситуация, когда я должен был уведомить об этом другой элемент в адаптере, но я получил аналогичный сбой. Я обновил логику уведомлений только для обновления, когда данные действительно меняются, и он разрешил мой сбой. Итак, мое новое правило с RecyclerViews: не уведомлять, что что-то изменилось, когда ничего не изменилось. Большое спасибо за этот ответ! – CodyEngel

17

Не знаю, но у меня была такая же проблема. Я решил эту проблему с помощью 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

это работа для меня. – ingyesid

+0

Хорошая работа) НО только при щелчке (если я виджет медленного перемещения (SwitchCompat), это действие будет пропущено. Это единственная проблема –

33

С помощью Handler для добавления элементов и вызова notify...() из этого Handler исправлена ​​проблема для меня.

+3

Правильный ответ: вы не можете изменять элемент во время его установки (с вызовом onBindViewHolder). в этом случае вам нужно вызвать notifyDataSetChanged в конце текущего цикла, вызвав Handler.post() – pjanecze

+0

- это обработчик, созданный в потоке пользовательского интерфейса? –

+1

@ user1232726 Если вы создаете обработчик в основном потоке, вам не нужно задайте Looper (по умолчанию для петлителя вызывающих потоков). Так что да, это мой совет. В противном случае вы также можете указать Looper вручную. – cybergen

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() в блок try/catch. Это просто попало в исключение, но все же позволило моему адаптеру зарегистрировать изменение состояния и обновить дисплей!

Надеюсь, это поможет кому-то!

EDIT: Я признаю, что это, вероятно, не правильный/зрелый способ справиться с проблемой, но поскольку он не вызывает каких-либо проблем, оставляя исключение необработанным, я думал, дело было достаточно для кого-то другого.

27

Вы можете просто сбросить предыдущий прослушиватель, прежде чем вносить изменения, и вы не получите это исключение.

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

+0

Мне действительно нужно привязывать нового слушателя каждый раз в любом случае, потому что слушателю требуется обновленная переменная положения каждый раз. Так что это отличный ответ, поэтому мне не нужно использовать обработчика. –

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

Сначала я подумал Moonsoo's answer (принятый ответ) не будет работать для меня, потому что я не могу инициализировать моей 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's answer, что предотвратит бесконечный цикл. Пока мы устанавливаем прослушиватель на «null» до того, как мы установим «Проверено», он обновит пользовательский интерфейс без запуска слушателя, избегая бесконечного цикла. Затем мы можем установить слушателя после. Код

JoniDS'S:

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

Вам следует избегать инициализации OnCheckedChangeListener снова и снова в onBindViewHolder (менее GC нужен именно так). Предполагается, что это вызывается в onCreateViewHolder, и вы получаете позицию, вызывая owner.getAdapterPosition(). –

4

вашего CheckBox элемент в изменении вытяжки при вызове notifyDataSetChanged(); так это исключение было бы произошло. Попробуйте позвонить notifyDataSetChanged(); в сообщение вашего вида.Для примера:

buttonView.post(new Runnable() { 
        @Override 
        public void run() { 
         notifyDataSetChanged(); 
        } 
       }); 
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(); 
     } 
    }); 
0

Это происходит потому, что вы, вероятно, установив «слушатель» перед настройкой значения для этого строка, которая заставляет слушателя запускаться при настройке значения для этого флажка.

Что вам нужно сделать, это:

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

Перед notifyDataSetChanged() просто проверить, что с помощью этого метода: recyclerView.IsComputingLayout()

0

Почему бы не проверять 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(); 
       } 
      } 
     }); 
    } 
} 
3

Найдено простое решение -

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

Здесь мы создаем работоспособной в notifyItemChanged для положения, когда recyclerview готов справиться с этим.

1

Простое использование сообщение:

new Handler().post(new Runnable() { 
     @Override 
     public void run() { 
       mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1); 
      } 
     } 
    }); 
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 { 

        } 

       } 
      }; 
     } 
1

Если у вас есть Сообщение об ошибке:

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

Простой, просто делать то, что вызывает в Exception в:

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

Зачем игнорировать это решение? Его правильный ... –

+1

Это сработало для меня. Интересно, есть ли проблемы с этим решением? –

+0

Кто-то хотел отобразить, может быть ... –

0

Для меня я слушал изменение рейтинга рейтингового бара, но на длинном pre ss из нескольких кликов одновременно приложение терпело крах из-за проблемы, а затем обнаружило четкое решение, если я хотел notifydatasetchange(); в bindviewholder обрабатывается с помощью обработчика:

//inside bindViewHolder     
new Handler().post(new Runnable() { 
        @Override 
        public void run() { 
         notifyDataSetChanged(); 
        } 
       }); 

Надеюсь, что это решит проблему.

0

использовать DiffUtil вместо этого, чтобы получить лучшую производительность