Android RecyclerView:notifyDataSetChanged()IllegalStateException

时间:2014-11-21 20:57:19

标签: android android-recyclerview

我正在尝试使用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(),同样的例外。有任何秘密的更新方式通知适配器有什么变化吗?

23 个答案:

答案 0 :(得分:131)

您应该将方法'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;
}

答案 1 :(得分:39)

使用Handler添加项目并从notify...()调用Handler为我解决了问题。

答案 2 :(得分:39)

您可以在进行更改之前重置上一个侦听器,但您不会获得此异常。

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

答案 3 :(得分:23)

我不太清楚,但我也有同样的问题。我在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();
        }
    });

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

答案 4 :(得分:11)

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

答案 5 :(得分:6)

当您收到消息错误:

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

答案 6 :(得分: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));
            }
        });
    }
}

这里我们为recyclerview准备好处理它的位置创建一个可运行的notifyItemChanged。

答案 7 :(得分:4)

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

以下是&#34;循环方法调用的示例&#34;他在谈论:

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 1}}再次。当您已经在onBindViewHolder]时,您无法致电onBindViewHolder,因为如果您已经在通知项目范围已更改,您不能notifyItemRangeChanged但我只需要更新UI以显示或打开它,不想实际触发任何内容。

这是我从JoniDS's answer学到的可以防止无限循环的解决方案。 只要我们将监听器设置为&#34; null&#34;在我们设置Checked之前,它会在不触发监听器的情况下更新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());
            }
        }
    });
}

答案 8 :(得分:4)

当您调用notifyDataSetChanged();时,

您的CheckBox项目正在更改drawable,因此会发生此异常。 在您的观看信息中尝试致电notifyDataSetChanged();。例如:

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

答案 9 :(得分: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();
        }
    });

答案 10 :(得分:2)

虽然项目受布局管理器约束,但您很可能正在设置复选框的选中状态,这会触发回调。

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

当RV重新计算布局时,您无法更改适配器内容。如果item的checked状态等于回调中发送的值(如果调用checkbox.setChecked触发回调就是这种情况),则可以通过不调用notifyDataSetChanged来避免它。

答案 11 :(得分:1)

notifyDataSetChanged()之前,只需使用此方法检查:recyclerView.IsComputingLayout()

答案 12 :(得分:1)

这个问题困扰了我一个小时,这是解决问题的方法。 但是在开始之前,此解决方案需要满足一些条件。

模型类

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

}

答案 13 :(得分:1)

简单使用帖子:

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

答案 14 :(得分:1)

为什么不按如下方式检查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();
                }
            }
        });
    }
}

答案 15 :(得分: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 {

                    }

                }
            };
        }

答案 16 :(得分:0)

这种情况正在发生,因为您可能正在设置“听众”。在配置该行的值之前,当您配置值&#39;时,会使侦听器被触发。对于复选框。

您需要做的是:

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

答案 17 :(得分:0)

使用复选框和单选按钮时,我遇到了同样的问题。将notifyDataSetChanged()替换为notifyItemChanged(position)可行。我向数据模型添加了布尔字段isChecked。然后,我更新了布尔值,并在onCheckedChangedListener中调用了notifyItemChanged(adapterPosition)。这可能不是最好的方法,但是对我有用。布尔值用于检查项目是否被选中。

答案 18 :(得分: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();   

            }
        });

答案 19 :(得分: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块中解决了问题。这只是捕获了异常,但仍允许我的适配器注册状态更改并更新显示!

希望这有助于某人!

编辑:我承认,这可能不是处理问题的正确/成熟方式,但是因为通过不处理异常似乎没有造成任何问题,我想我&# 39; d分享,以防其他人好。

答案 20 :(得分:0)

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

答案 21 :(得分:-1)

只需在isPressed()中使用CompoundButton的{​​{1}}方法
例如

onCheckedChanged(CompoundButton compoundButton, boolean isChecked)

答案 22 :(得分:-1)

对我来说,当我通过“完成”,“上一步”或外部输入触摸从EditText退出时,出现了问题。这将导致使用输入文本更新模型,然后通过实时数据观察刷新回收者视图。

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

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

editText.clearFocus() 

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

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