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リスナーを配置すると、同じエラーが発生します
filthy_wizard

回答:


145

メソッド 'setOnCheckedChangeListener()'をアダプタの内部クラスであるViewHolderに移動する必要があります。

onBindViewHolder()を初期化するメソッドではありませんViewHolder。このメソッドは、各リサイクラーアイテムを更新するステップです。を呼び出すとnotifyDataSetChanged()onBindViewHolder()各アイテムの回数として呼び出されます。

したがって、checkBox をにnotifyDataSetChanged()入れてonCheckChanged()初期化するとonBindViewHolder()、循環メソッド呼び出しのためにIllegalStateExceptionが発生します。

チェックボックスをクリック-> onCheckedChanged()-> notifyDataSetChanged()-> onBindViewHolder()->セットチェックボックス-> onChecked ...

簡単に言うと、アダプタにフラグを1つ入れることでこれを修正できます。

これを試して、

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

6
私はこのソリューションstackoverflow.com/a/32373999/1771194を好んでいます。また、RecyclerViewで「RadioGroup」を作成することもできました。
Artem

リスト内のアイテムの位置を取得するにはどうすればよいですか?
filthy_wizard 2016年

2
これは私にとってビューホルダーでは機能しません。それでもクラッシュエラーが発生します。arraylistの変数を変更する必要があります。非常に奇妙な。私がlistinerをどこに接続できるかあまりにもわかりません。
filthy_wizard 2016

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、2016年

1
もちろん、フィールドを使用する方が良いです。私は機能する例を示しただけでした。しかし、警告をありがとう、私は答えを更新します。
JoniDS 2016年

1
リスナーは毎回更新された位置変数を必要とするため、実際にはとにかく毎回新しいリスナーをバインドする必要があります。これは素晴らしい答えなので、ハンドラを使用する必要はありません。
ロックリー

受け入れられた回答(stackoverflow.com/a/31069171/882251)が推奨するグローバル状態を維持することは決して推奨されないため、これは間違いなくそれを行う最良の方法です。
ダーウィンド2018年

最も簡単なもの!ありがとう!!
DalveerSinghDaiya 2018年

39

を使用Handlerしてアイテムを追加しnotify...()、これから呼び出すとHandler、問題が解決しました。


3
それが正解です。(onBindViewHolderを呼び出して)設定中はアイテムを変更できません。その場合は、Handler.post()を呼び出して、現在のループの最後にnotifyDataSetChangedを呼び出す必要があります
pjanecze

1
@ user1232726メインスレッドでハンドラーを作成する場合、ルーパーを指定する必要はありません(デフォルトでは呼び出しスレッドルーパーに設定されます)。はい、これは私のアドバイスです。それ以外の場合は、ルーパーを手動で指定することもできます。
cybergen

残念ながら、下にスクロールして再び上にスクロールしても、チェックボックスがチェックされたままになりません。ブー。lol
filthy_wizard 2016

@ user1232726回答を検索するか、問題を説明する新しい質問をしてください。
cybergen 2016

2
これは問題を解決するためのハッキーな方法であるため、私はこの答えを強くお勧めしません。これを行うほど、コードが理解しにくくなります。問題を理解するにはMoonsooの回答を、問題を解決するにはJoniDSの回答を参照してください。
Kalpesh Patel 2016

26

よくわかりませんが、同じ問題もありました。私はこれを使っ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
すばらしい仕事ですが、クリック時のみです(ウィジェット(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);
                }
            }
        });
    }

アダプターに2回簡単に通知できると思います。
МаксимПетлюк

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 2017

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が処理する準備ができたときに位置を通知するランナブルを作成します。


5

CheckBoxアイテムは、呼び出し時にドローアブルを変更するnotifyDataSetChanged();ため、この例外が発生します。notifyDataSetChanged();あなたの見解のポストに電話してみてください。例えば:

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

4

最初はMoonsooの答え(受け入れられた答え)は、初期化できないため機能しないと思いました。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しかし、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 2017

2

OnCheckedChangeListenerの代わりにチェックボックスでonClickListnerを使用すると、問題が解決します

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

大抵beacause起こる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

私は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 [ onCheckChangeの代わりにonclickを使用してください]

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

私にとって、Done、Back、または外部入力タッチによってEditTextを終了したときに問題が発生しました。これにより、入力テキストでモデルが更新され、ライブデータの監視を通じてリサイクラービューが更新されます。

問題は、カーソル/フォーカスがEditTextに残っていることでした。

私が使用してフォーカスを削除したとき:

editText.clearFocus() 

リサイクラビューのデータ変更メソッドに通知すると、このエラーがスローされなくなりました。

これは、この問題の考えられる理由/解決策の1つだと思います。この例外は、まったく異なる理由で発生する可能性があるため、別の方法で修正できる可能性があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.