Android RecyclerView:notifyDataSetChanged()IllegalStateException


130

我正在尝试使用notifyDataSetChanged()更新recycleview的项目。

这是我的recycleview适配器中的onBindViewHolder()方法。

@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(),同样的异常。有任何秘密方法可以更新以通知适配器某些更改吗?


我现在有同样的问题。将setoncheckchanged侦听器放入viewholder构造函数中会给我同样的错误
filthy_wizard

Answers:


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()中的通知,在哪里设置侦听器都没有关系。
Yaroslav Mytkalyk '16年

6
我更喜欢此解决方案stackoverflow.com/a/32373999/1771194,并在注释方面进行了一些改进。它还使我可以在RecyclerView中创建“ RadioGroup”。
Artem

如何获得列表中的商品位置?
filthy_wizard

2
这对我来说在Viewholder中不起作用。仍然会发生当机错误。我需要更改arraylist中的vars。很奇怪。不太确定我可以在哪里挂listiner。
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调用上创建侦听器。使其成为一个字段。
Artem

1
使用字段当然更好,我只是举一个有效的例子。但是,感谢您的警告,我将更新答案。
JoniDS

1
无论如何,实际上我每次都需要绑定一个新的侦听器,因为该侦听器每次都需要一个更新的位置变量。因此,这是一个很好的答案,因此我不必使用Handler。
摇滚李

这绝对是最好的方法,因为从不建议保留公认的答案(stackoverflow.com/a/31069171/882251)建议的全局状态。
达尔文'18

最简单的一个!谢谢 !!
DalveerSinghDaiya

39

使用a Handler来添加项目并notify...()从中致电Handler给我解决了此问题。


3
这是正确的答案,您无法在设置时更改项目(通过调用onBindViewHolder)。在这种情况下,您必须在当前循环结束时通过调用Handler.post()来调用notifyDataSetChanged
pjanecze

1
@ user1232726如果在主线程上创建处理程序,则不必指定Looper(默认为调用线程looper)。是的,这是我的建议。否则,您也可以手动指定Looper。
cybergen '16

不幸的是,当我向下滚动并再次向上滚动时,我的复选框没有保持选中状态。嘘 大声笑
filthy_wizard

@ user1232726搜索答案或提出描述您问题的新问题。
cybergen '16

2
我强烈不建议使用此答案,因为这是解决问题的一种不可靠的方法。这样做越多,您的代码就越难以理解。请参考Moonsoo的答案以了解问题,并参考JoniDS的答案以解决问题。
Kalpesh Patel

26

我不太了解,但我也遇到了同样的问题。我通过使用解决onClickListnercheckbox

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),则将丢失此操作。这是唯一的问题
Vlad

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
这对我有用。想知道此解决方案有什么问题吗?
Sayooj Valsan

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

在这里,当recyclerview准备好处理该位置时,我们为该位置创建一个runnable,以notifyItemChanged。


5

您的CheckBox项在调用时处于可更改的可绘制状态,notifyDataSetChanged();因此会发生此异常。尝试致电notifyDataSetChanged();您的观点。例如:

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

4

起初,我认为Moonsoo的答案(已接受的答案)对我不起作用,因为我无法setOnCheckedChangeListener()在ViewHolder构造函数中初始化我的代码,因为我每次都需要绑定它,以便获得更新的position变量。但是我花了很长时间才意识到他在说什么。

这是他正在谈论的“循环方法调用”的示例:

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,如果你已经在通知该项目范围已经改变的中间。但是我只需要更新UI来显示它的打开或关闭,而不希望实际触发任何东西。

这是我从JoniDS的答案中学到的解决方案,它可以防止无限循环。只要我们在设置Checked之前将侦听器设置为“ null”,那么它将在不触发侦听器的情况下更新UI,从而避免了无限循环。然后我们可以设置监听器。

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

您应该避免在onBindViewHolder中一次又一次地初始化OnCheckedChangeListener(这种方式需要较少的GC)。应该在onCreateViewHolder中调用它,然后通过调用holder.getAdapterPosition()获得位置。
Android开发人员

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 '17

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()调用包装在try / catch块中解决了该问题。这只是捕获了异常,但仍然允许我的适配器注册状态更改并更新显示!

希望这对某人有帮助!

编辑:我承认,这可能不是处理问题的正确/成熟方式,但是由于它似乎没有通过未处理异常而引起任何问题,所以我想我愿意分享一下,以防万一对别人来说足够了。


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()的方法,CompoundButtononCheckedChanged(CompoundButton compoundButton, boolean isChecked)
例如

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

0

使用Checkbox和RadioButton时,我遇到了同样的问题。notifyDataSetChanged()notifyItemChanged(position)工作代替。我向isChecked数据模型添加了一个布尔字段。然后,我更新了布尔值,并在中onCheckedChangedListener调用notifyItemChanged(adapterPosition)。这可能不是最好的方法,但是对我有用。布尔值用于检查是否已检查项目。


0

大多数情况下是因为notifydatasetchanged调用了复选框的onCheckedchanged事件,并且在该事件中再次发生notifydatasetchanged

要解决该问题,您只需检查是否已通过编程方式选中了复选框或用户按下了它即可。有方法isPressed

因此,将整个列表器代码包装在isPressed方法中。和完成。

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

适配器类中的复选框

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退出时,发生了问题。这将导致使用输入文本更新模型,然后通过实时数据观察刷新回收者视图。

问题是光标/焦点保留在EditText中。

使用以下方法删除焦点后:

editText.clearFocus() 

通知回收者视图的数据更改方法确实停止引发此错误。

我认为这是解决此问题的可能原因/解决方案之一。可能由于完全不同的原因而导致此异常可能以其他方式解决。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.