Android 回收视图: notifyDataSetChanged() IllegalStateException

我正在尝试使用 notifyDataSetChanged ()更新回收视图的项目。

这是回收视图适配器中的 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 () ,同样的异常。有什么秘密的方法来更新以通知适配器发生了变化吗?

96381 次浏览

当项被布局管理器绑定时,很可能您正在设置复选框的选中状态,这将触发回调。

当然,这只是一种猜测,因为您没有发布完整的堆栈跟踪。

当 RV 重新计算布局时,不能更改适配器内容。如果项的选中状态等于回调中发送的值(如果调用 checkbox.setChecked触发回调,就会出现这种情况) ,则不调用 notifyDataSetChanged 可以避免这种情况。

我不知道,但我也有同样的问题。我解决了这个通过使用 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();
}
});

试试这个,可能会有帮助!

使用 Handler添加项目和从这个 Handler调用 notify...()为我修复了问题。

您应该将方法“ setOnCheckedChangeListener ()”移动到 ViewHolder,这是适配器上的内部类。

onBindViewHolder()不是初始化 ViewHolder的方法。 这个方法是刷新每个回收物品的步骤。 当您调用 notifyDataSetChanged()时,onBindViewHolder()将被称为每个项目的次数。

因此,如果您将 notifyDataSetChanged()放入 onCheckChanged()并在 onBindViewHolder()中初始化 checkBox,那么由于循环方法调用,您将得到 IllegalStateException。

单击复选框-> onCheckedChanged ()-> notifyDataSetChanged ()-> onBindViewHolder ()-> set 复选框-> onChecked..。

简单地说,您可以通过将一个标志放入 Adapter 来修复这个问题。

试试这个,

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

我碰巧遇到了这个问题!在穆苏的回答并没有真正让我满意之后,我做了一些调整,找到了一个适合我的解决方案。

首先,这是我的一些代码:

    @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 块中。这只是捕获了异常,但仍然允许我的适配器注册状态更改和更新显示!

希望这对谁有帮助!

编辑: 我承认,这可能不是处理这个问题的正确/成熟的方法,但是由于它似乎不会因为不处理异常而导致任何问题,我想我应该分享一下,以防它对其他人来说足够好。

您可以在进行更改之前重置先前的侦听器,这样就不会出现此异常。

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

起初我认为 穆苏的回答(公认的答案)不适合我,因为我不能在 ViewHolder 构造函数中初始化我的 setOnCheckedChangeListener(),因为我每次都需要绑定它,这样它就会得到一个更新的位置变量。但我花了很长时间才明白他在说什么。

下面是他所说的“循环方法调用”的一个例子:

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,而 nofityItemRangeChanged会再次调用 onBindViewHolder。你不能调用 onBindViewHolder时,你已经在 onBindViewHolder] ,因为 如果您正在通知项目范围已更改,则不能使用 notifyItemRangeChanged但我只需要更新的 UI 显示或关闭,不想实际触发任何东西。

下面是我从 乔尼兹的回答中学到的防止无限循环的解决方案。然后我们可以设置监听器后。

乔尼兹的暗号是:

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

当您调用 notifyDataSetChanged();时,您的 CheckBox 项处于可绘制状态,因此将发生此异常。 尝试在视图后调用 notifyDataSetChanged();。例如:

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

在复选框中使用 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();
}
});

之所以会发生这种情况,是因为您可能在配置该行的值之前设置了“侦听器”,这会使侦听器在您为复选框“配置值”时被触发。

你需要做的是:

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

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

找到了一个简单的解决办法

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。

返回文章页面简单使用译者:

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


}


}
};
}

当出现消息错误时:

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

onCheckedChanged(CompoundButton compoundButton, boolean isChecked)中简单地使用 CompoundButtonisPressed()方法
例如:

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

对我来说,问题发生时,我退出了 EditText 的完成,回来,或外部输入触摸。这导致用输入文本更新模型,然后通过实时数据观察刷新回收器视图。

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

当我使用以下方法删除焦点时:

editText.clearFocus()

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

我认为这是解决这个问题的可能原因之一。这个例外可能以另一种方式修复,因为它可能是由完全不同的原因造成的。

我在使用复选框和 RadioButton 时遇到了同样的问题。用 notifyItemChanged(position)代替 notifyDataSetChanged()成功了。我在数据模型中添加了一个布尔字段 isChecked。然后我更新了布尔值,在 onCheckedChangedListener中,我调用了 notifyItemChanged(adapterPosition)。这可能不是最好的办法,但对我很有效。布尔值用于检查是否选中了该项。

主要是因为 通知数据更改调用了复选框的 一切都变了事件,在这个事件中 再来一次就是 通知数据更改

要解决这个问题,你只需要选中复选框是通过编程方式选中的,或者用户按下它。有 被压迫方法。

因此,将整个 listner 代码包装在 isPress 方法中。

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


if(compoundButton.isPressed()) {




//your code
notifyDataSetChanged();


}
});

我为这个问题苦恼了几个小时,这就是你可以解决它的方法。 但是在你开始之前,这个解决方案有一些条件。

模范课程

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

}

 override fun bindItemViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
var item: ThingChannels = items[position]
rowActionBinding.xCbChannel.text = item.channelType?.primaryType
rowActionBinding.xTvRoomName.text = item.thingDetail?.room?.name
rowActionBinding.xCbChannel.isChecked = item.isSelected
rowActionBinding.xCbChannel.tag = position
rowActionBinding.xCbChannel.setOnClickListener {
setSelected(it.tag as Int)
if (onItemClickListener != null) {
onItemClickListener!!.onItemClick(position, null)
}
}
}

过去的答案都不能解决问题!

过去答案的问题

如果用户正在滚动,所有的用户都可以通过接受更改(即不通知适配器更改)来避免这个问题,这意味着如果用户在更改准备就绪时正在滚动,他们将永远不会看到更改。或者,他们建议使用 recylerView.post(),这只会延缓问题的发生。

答案

选择一

停止滚动,然后通知适配器:

recyclerView.stopScroll()
val copy = workingList.toList()


//prevent IndexOutOfBoundsException: Inconsistency detected... (https://stackoverflow.com/questions/41054959/java-lang-indexoutofboundsexception-inconsistency-detected-invalid-view-holder)
workingList.clear()
recyclerViewAdapter?.notifyDataSetChanged()
//set display to correct data
workingList.addAll(copy)
recyclerViewAdapter?.notifyItemRangeInserted(0, workingList.size)

选项二

为了获得更好的用户体验,你可以让他们继续滚动,并监听他们停止滚动的时候,以便更新 UI,但是这种方法只能在你不打算编写一个接受不同 RecyclerView实例的函数的情况下使用,因为如果你向同一个 RecyclerView添加多个监听器,这些监听器都试图被更新,应用程序就会崩溃:

if(recyclerView.scrollState != RecyclerView.SCROLL_STATE_IDLE) //notify RecyclerView
else recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(
recyclerView: RecyclerView,
newState: Int
) {
if(newState == RecyclerView.SCROLL_STATE_IDLE) {
//notify RecyclerView...


val copy = workingList.toList()
                

//prevent IndexOutOfBoundsException: Inconsistency detected... (https://stackoverflow.com/questions/41054959/java-lang-indexoutofboundsexception-inconsistency-detected-invalid-view-holder)
workingList.clear()
recyclerViewAdapter?.notifyDataSetChanged()
//set to display correct data
workingList.addAll(copy)
recyclerViewAdapter?.notifyItemRangeInserted(0, workingList.size)
}
})

注意: 您可以使用选项 # 2,即使您正在编写一个 util 函数,只需保留一个先前注册的实例列表,如果已经注册,则不需要添加侦听器,但是依赖于库/util 类中的状态并不是“干净的编码”。