Android RecyclerView:notifyDataSetChanged() IllegalStateException
Posted
技术标签:
【中文标题】Android RecyclerView:notifyDataSetChanged() IllegalStateException【英文标题】:Android RecyclerView : notifyDataSetChanged() IllegalStateException 【发布时间】:2015-01-20 02:55:30 【问题描述】:我正在尝试使用 notifyDataSetChanged() 更新回收视图的项目。
这是我在 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 构造函数中会给我同样的错误 【参考方案1】:您应该将方法“setOnCheckedChangeListener()”移动到 ViewHolder,它是适配器的内部类。
onBindViewHolder()
不是初始化ViewHolder
的方法。
此方法是刷新每个回收站项目的步骤。
当你调用notifyDataSetChanged()
时,onBindViewHolder()
将被调用为每个项目的次数。
所以如果你将notifyDataSetChanged()
放入onCheckChanged()
并初始化onBindViewHolder()
中的checkBox,你会因为循环方法调用而得到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;
【讨论】:
我明白了,有道理。希望平台能够预测这种简单的行为并给出解决方案,而不必依赖标志.. 只要在onBindViewHolder()
进行中时不通知AdapterViewObserver
,在哪里设置监听器都没有关系。
我更喜欢这个解决方案***.com/a/32373999/1771194,并在评论中有一些改进。它还允许我在 RecyclerView 中创建“RadioGroup”。
如何在我的列表中获取项目位置??
这在视图中对我不起作用。仍然得到崩溃错误。我需要更改数组列表中的变量。很奇怪。不太确定我可以在哪里附加监听器。【参考方案2】:
您可以在进行更改之前重置之前的侦听器,并且不会出现此异常。
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);
【讨论】:
很好的答案,但最好不要在每个 onBindViewHolder 调用上创建侦听器。将其作为一个字段。 使用字段当然更好,我只是举一个有效的例子。但是感谢您的警告,我会更新答案。 我实际上每次都需要绑定一个新的监听器,因为监听器每次都需要一个更新的位置变量。所以这是一个很好的答案,所以我不必使用处理程序。 这绝对是最好的方法,因为它从不建议保持全局状态,这是公认的答案 (***.com/a/31069171/882251) 所推荐的。 最简单的一个!!谢谢!!【参考方案3】:使用Handler
添加项目并从此Handler
调用notify...()
为我解决了这个问题。
【讨论】:
这是正确的答案,您不能在设置时更改项目(通过调用 onBindViewHolder)。在这种情况下,您必须通过调用 Handler.post() 在当前循环结束时调用 notifyDataSetChanged @user1232726 如果在主线程上创建 Handler,则不必指定 Looper(默认为调用线程的 Looper)。所以,是的,这是我的建议。否则你也可以手动指定 Looper。 不幸的是,当我向下滚动并再次向上滚动时,我的复选框没有保持选中状态。嘘。大声笑 @user1232726 搜索答案或提出描述您的问题的新问题。 我强烈不鼓励这个答案,因为这是解决问题的一种老套方法。你做的越多,你的代码就越复杂,难以理解。参考Moonsoo's answer了解问题,参考JoniDS's解答解决问题。【参考方案4】:我不太清楚,但我也有同样的问题。我通过在checkbox
上使用onClickListner
解决了这个问题
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();
);
试试这个,这可能会有所帮助!
【讨论】:
干得好)但仅在点击时(如果我慢速移动小部件(SwitchCompat),此操作将被错过。这是唯一的问题【参考方案5】: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);
);
【讨论】:
我想你可以很容易地通知适配器两次。 这是唯一真正最终执行计算的答案,而不是在尝试时它还没有准备好时忽略它。但是,对于这么大的调用堆栈,这不会导致内存不足错误吗? post 调用下一个递归调用需要多长时间?【参考方案6】:找到一个简单的解决方案 -
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准备好处理它的位置。
【讨论】:
这比标记为正确的解决方案更好。【参考方案7】:出现消息错误时:
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);
);
【讨论】:
这对我有用。想知道这个解决方案有什么问题吗?【参考方案8】:起初我认为Moonsoo's answer(已接受的答案)对我不起作用,因为我无法在 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
的侦听器,它再次调用onBindViewHolder
.当您已经在 onBindViewHolder
] 中时,您不能调用 onBindViewHolder
,因为 如果您已经在通知项目范围已更改,则不能 notifyItemRangeChanged
。 但我只需要更新 UI 以打开或关闭它,而不希望实际触发任何东西。
这是我从JoniDS's answer 学到的解决方案,可以防止无限循环。 只要我们在设置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() 获得位置。【参考方案9】:当您调用 notifyDataSetChanged();
时,您的 CheckBox 项目正在更改可绘制对象,因此会发生此异常。
尝试在您的视图中致电notifyDataSetChanged();
。例如:
buttonView.post(new Runnable()
@Override
public void run()
notifyDataSetChanged();
);
【讨论】:
【参考方案10】:为什么不检查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();
);
【讨论】:
【参考方案11】:当布局管理器绑定项目时,很可能您正在设置复选框的选中状态,这会触发回调。
当然这是一个猜测,因为您没有发布完整的堆栈跟踪。
当 RV 重新计算布局时,您无法更改适配器内容。如果项目的选中状态等于回调中发送的值(如果调用 checkbox.setChecked
触发回调,则会出现这种情况),您可以通过不调用 notifyDataSetChanged 来避免它。
【讨论】:
谢谢@yigit!我的问题与复选框无关,而是更复杂的情况,我必须通知适配器中的不同项目,但我遇到了类似的崩溃。我更新了我的通知逻辑,只在数据实际发生变化并且解决了我的崩溃时才更新。所以我对 RecyclerViews 的新规则:当什么都没有改变时,不要通知有什么改变了。非常感谢您的回答!【参考方案12】:在复选框上使用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();
);
【讨论】:
【参考方案13】:在notifyDataSetChanged()
之前,只需使用以下方法检查:recyclerView.IsComputingLayout()
【讨论】:
【参考方案14】:简单使用帖子:
new Handler().post(new Runnable()
@Override
public void run()
mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1);
);
【讨论】:
【参考方案15】:我被这个问题困扰了一个小时,这就是你可以解决的方法。 但在开始之前,此解决方案有一些条件。
模型类
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();
);
【讨论】:
哇,这是最好的答案。【参考方案16】:我遇到了这个确切的问题!在 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 块中解决了这个问题。这只是捕获了异常,但仍然允许我的适配器注册状态更改并更新显示!
希望这对某人有所帮助!
编辑:我承认,这可能不是处理问题的正确/成熟方式,但由于不处理异常似乎不会造成任何问题,我想我会分享以防万一对其他人来说已经足够了。
【讨论】:
【参考方案17】:发生这种情况是因为您可能在配置该行的值之前设置了“侦听器”,这会使侦听器在您为复选框“配置值”时被触发。
你需要做的是:
@Override
public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position)
viewHolder.mCheckBox.setOnCheckedChangeListener(null);
viewHolder.mCheckBox.setChecked(trueOrFalse);
viewHolder.setOnCheckedChangeListener(yourCheckedChangeListener);
【讨论】:
【参考方案18】: @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
;
【讨论】:
【参考方案19】:只需在onCheckedChanged(CompoundButton compoundButton, boolean isChecked)
中使用CompoundButton
的isPressed()
方法例如
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked)
... //your functionality
if(compoundButton.isPressed())
notifyDataSetChanged();
);
【讨论】:
【参考方案20】:我在使用 Checkbox 和 RadioButton 时遇到了同样的问题。用notifyItemChanged(position)
替换notifyDataSetChanged()
有效。我在数据模型中添加了一个布尔字段isChecked
。然后我更新了布尔值并在onCheckedChangedListener
中调用了notifyItemChanged(adapterPosition)
。这可能不是最好的方法,但对我有用。布尔值用于检查该项是否被选中。
【讨论】:
【参考方案21】:这主要是因为 notifydatasetchanged 调用了复选框的 onCheckedchanged 事件,并且在该事件中再次有 notifydatasetchanged。
要解决它,您只需检查复选框是否被编程检查或用户按下它。有 isPressed 方法。
所以将整个listner 代码包装在isPressed 方法中。并且完成了。
holder.mBinding.cbAnnual.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b)
if(compoundButton.isPressed())
//your code
notifyDataSetChanged();
);
【讨论】:
【参考方案22】: 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)
【讨论】:
【参考方案23】:过去的答案都没有解决问题!
过去答案的问题
如果用户正在滚动,他们要么通过吞下更改(即不通知适配器更改)来避免问题,这只是意味着如果用户在更改准备好时正在滚动,他们将永远不会看到改变。或者,他们建议使用 recylerView.post()
来推迟问题。
答案
选项 #1
停止滚动,然后通知适配器:
recyclerView.stopScroll()
val copy = workingList.toList()
//prevent IndexOutOfBoundsException: Inconsistency detected... (https://***.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)
选项 #2
为了更好的用户体验,您可以让他们继续滚动并在他们停止滚动时进行监听以更新 UI,但仅当您不打算编写将接受不同 @ 的函数时才应使用此方法987654323@ 实例,因为如果您将多个侦听器添加到同一个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://***.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,方法是保留以前注册的实例的列表,如果已经注册,则不要添加侦听器,但它不是“干净的编码”依赖库/实用程序类中的状态。
【讨论】:
【参考方案24】:对我来说,当我通过 Done、Back 或外部输入触摸退出 EditText 时出现问题。这会导致使用输入文本更新模型,然后通过实时数据观察刷新回收器视图。
问题是光标/焦点保留在 EditText 中。
当我通过使用删除焦点时:
editText.clearFocus()
回收者视图的通知数据更改方法确实停止抛出此错误。
我认为这是此问题的可能原因/解决方案之一。有可能这个异常可以通过其他方式修复,因为它可能是由完全不同的原因引起的。
【讨论】:
以上是关于Android RecyclerView:notifyDataSetChanged() IllegalStateException的主要内容,如果未能解决你的问题,请参考以下文章
Android RecyclerView嵌套RecyclerView
Android:RecyclerView里面的RecyclerView,滚动到底部
Android教程2020 - RecyclerView获取滑动距离
android recyclerview怎么设置每次只能滑动一页
Android的import android.support.v7.widget.RecyclerView的RecyclerView标红问题