web-dev-qa-db-ja.com

RecyclerView notifyItemInserted IllegalStateException

アダプタをRecyclerView.Adapterに移植しています

私が達成したいこと:ユーザーがデータのフェッチを開始したい終わり近くにスクロールダウンするとき、私は最後にi ProgressBarビューを追加して、より多くのデータが来ていることをユーザーに知らせたいです。

BaseAdapterでこれを実装した方法: on getViewで、最後の近くで要求されたビューで、さらにデータのフェッチを開始し、notifyDataSetChangedを呼び出します(ProgressBarビューを表示するには)そして、その後に必要なビューを返しますgetView

RecyclerView.Adapterで試したこと:基本的に同じことを試みましたが、今回はonBindViewHolderメソッドで行いました。

しかし、このメソッド内でnotifyItemInsertedを呼び出そうとすると、次の例外が発生します。

IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling

試したこと:onBindViewHolderからonLayoutChildrenLayoutManagerから呼び出されることに気づいたので、オーバーライドしてnotifyItemInsertedを呼び出してみましたそのsuperが同じ例外を取得しました

どうすれば目標を達成できますか?

29
user1333057
        Handler handler = new Handler();

        final Runnable r = new Runnable() {
            public void run() {
                adapter.notifyDataSetChanged();
            }
        };

        handler.post(r);

Adapter.notifyDataSetChanged();を呼び出すサンプルコードアクティビティから

44
Rafael

この例外をトリガーするRecyclerView.isComputingLayout()メソッドのJavaDocに注目する価値もあります。

/**
 * Returns whether RecyclerView is currently computing a layout.
 * <p>
 * If this method returns true, it means that RecyclerView is in a lockdown state and any
 * attempt to update adapter contents will result in an exception because adapter contents
 * cannot be changed while RecyclerView is trying to compute the layout.
 * <p>
 * It is very unlikely that your code will be running during this state as it is
 * called by the framework when a layout traversal happens or RecyclerView starts to scroll
 * in response to system events (touch, accessibility etc).
 * <p>
 * This case may happen if you have some custom logic to change adapter contents in
 * response to a View callback (e.g. focus change callback) which might be triggered during a
 * layout calculation. In these cases, you should just postpone the change using a Handler or a
 * similar mechanism.
 *
 * @return <code>true</code> if RecyclerView is currently computing a layout, <code>false</code>
 *         otherwise
 */
public boolean isComputingLayout() {
    return mLayoutOrScrollCounter > 0;
}

顕著なフレーズは:

これらの場合、ハンドラーまたは同様のメカニズムを使用して変更を延期する必要があります。

私の場合、別のスレッドからアダプターの内容を変更していましたが、それが(ときどき)衝突を引き起こしていました。更新をUIスレッドのハンドラーにシフトすると、これは自然に解決されます。

20
Kas Hunt

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

9
cybergen

きれいおよび簡単方法:

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

説明:RecyclerViewインスタンスを使用し、postメソッド内で新しいRunnableをメッセージキューに追加しました。実行可能ファイルは、ユーザーインターフェイススレッドで実行されます。これは、バックグラウンドからUIスレッドにアクセスするためのAndroidの制限です(たとえば、バックグラウンドスレッドで実行されるメソッド内))。

5
Shayan Amani

試してみてください

if (!recyclerView.isComputingLayout()) {
        recyclerView.notifyDataSetChanged();
    }

私はこれを使用します:

Handler handler = new Handler();
private void notifyItemInsertedEx(final int position) {
    try {
        notifyItemInserted(position);
    } catch (Exception ex) {
        handler.post(new Runnable(){
            public void run(){
                notifyItemInserted(position);
            }
        });
    }
}
0
A.J.Bauer