web-dev-qa-db-ja.com

RecyclerViewおよびAdapterデータの更新

これは、その仕組みを知っているか、ソースコードを掘り下げようとする人のためのRecyclerViewの内部動作に関する質問です。ソースへの参照によって裏付けられた回答をお願いします。

元の質問

(より絞り込んだ質問を表示するには、「言い換えれば」までスクロールします)

_notify*_アクション(たとえば、notifyItemInserted())がキューに入れられる方法を理解する必要があります。このリストでバックアップされたアダプターがあると想像してください。

_ArrayList<String> list = Arrays.asList("one", "three", "four");
_

不足している値zerotwoを追加します。

例1

_list.add(1, "two");
// notify the view
adapter.notifyItemInserted(1);


// Seconds later, I go on with zero
list.add(0, "zero");
// notify the view
adapter.notifyItemInserted(0);
_

これはかなり単純明快で、何も言うことはありません。

例2

しかし、2つのアクションが互いに非常に近く、間にレイアウトパスがない場合はどうなりますか?

_list.add(1, "two");
list.add(0, "zero”);
_

私は今何をすべきか?

_adapter.notifyItemInserted(1);
adapter.notifyItemInserted(0);
_

または多分

_adapter.notifyItemInserted(2);
adapter.notifyItemInserted(0);
_

?アダプターの観点からすると、リストは_one, three, four_から_zero, one, two, three, four_にすぐに切り替わったため、2番目のオプションの方が合理的です。

例3

_list.add(0, “zero”);
adapter.notifyItemInserted(0);
list.add(2, “two”);
adapter.notifyItemInserted(...)
_

今はどうですか? _1_または_2_?リストはすぐに更新されましたが、間にレイアウトパスがなかったと思います。

質問

あなたは主要な問題を得ました、そして私はこれらの状況で私がどのように振る舞うべきか知りたいです。実際のケースでは、複数の非同期タスクがinsert()メソッドで終了しています。私は彼らの操作をキューに入れることができますが:

  1. すでに内部キューがあり、確かにある場合、私はそれをしたくありません
  2. 間にレイアウトパスなしで2つのアクションが発生するとどうなるかわかりません。例3を参照してください。

言い換えると

リサイクラーを更新するには、4つのアクションを実行する必要があります。

  1. 私は実際にデータモデルを変更します(たとえば、バッキング配列に何かを挿入します)
  2. adapter.notify*()を呼び出します
  3. リサイクラーが電話を受ける
  4. リサイクラー実行アクション(たとえば、アダプターでgetItem*()およびonBind()を呼び出す)を実行し、変更をレイアウトします。

同時実行性がなく、順番に発生する場合、これを理解するのは簡単です。

_1. => 2. => 3. => 4. => (new update) 1. => 2. => 3. => 4. ...
_

ステップ間で何が起こるか見てみましょう。

  • 1。2。の間:データを変更した直後にnotify()を呼び出すのは開発者の責任です。それで大丈夫です。
  • 2。の間:これはすぐに発生し、ここでは問題ありません。
  • 4。の間:notはすぐに実行されます!私の知る限り。したがって、新しい更新(ステップ1および2)がステップと4-の間に来ることは完全に可能です。 前のアップデートの

この場合、どうなるのか知りたい。私たちはどのように振る舞うべきですか?新しいものを挿入する前に、前の更新のステップ4が行われたことを確認する必要がありますか?もしそうなら?

22
natario

私は以前に同様の質問について考えました、そして私は決めました:

  1. リストの最後に複数のアイテムを直接挿入し、すべてのアニメーションを取得したい場合は、次のようにします。

    list.add("0");
    list.add("1");
    adapter.notifyItemRangeInserted(5, 2); // Suppose there were 5 items before so "0" has index of 5 and we want to insert 2 items.
    
  2. 複数のアイテムをリストの最後に直接挿入したいが、挿入したアイテムごとに個別のアニメーションを取得したい場合は、次のようにします。

    list.add("0");
    list.add("1");
    adapter.notifyItemInserted(0);
    mRecyclerView.postDelayed(new Runnable() {
        @Override
        public void run() {
            // before this happens, Be careful to call other notify* methods. Never call notifyDataSetChanged.
            adapter.notifyItemInserted(1); 
        }
    }, mRecyclerView.getItemAnimator().getAddDuration());
    
  3. 2と同様に、リストの別の位置に複数のアイテムを挿入したい場合。

これがお役に立てば幸いです。

7
ywwynm

それでは、RetrokerViewが通知アイテムで動作するように、少しイントロから始めましょう。また、保存されたViewGroup項目の他のリスト(ListView for ex。)

RecyclerViewには、すでに描画されているビュー項目のキューがあります。そして、notify(...)メソッドを呼び出さない限り、更新については何も知りません。新しいアイテムを追加してRecyclerViewに通知すると、すべてのビューを1つずつチェックするサイクルが開始されます。

_RecyclerView contains and drawn next objects
View view-0 (position 0), view-1 (position 1), View-2 (position 2)

// Here is changes after updating
You added Item View view-new into (position 1) and Notify
RecyclerView starts loop to check changes
RecyclerView received unmodified view-0(position-0) and left them;
RecyclerView found new item view-new(position 1)
RecyclerView removing old item view-1(position 1)
RecyclerView drawing new item view-new(position 1)

// In RecyclerView queue in position-2 was item view-2, 
// But now we replacing previous item to this position
RecyclerView found new item view-1 (new position-2)
RecyclerView removing old item view-2(position 2)
RecyclerView drawing new item view-1(position 2)

// And again same behavior 
RecyclerView found new item view-3 (new position-3)
RecyclerView drawing new item view-1(position 2)

// And after all changes new RecyclerView would be
RecyclerView contains and drawn next objects
View view-0 (position 0), view-new (position 1) view-1 (position 2), View-2 (position 3)
_

これは動作する通知関数のメインフローにすぎませんが、このすべてのアクションがUIスレッド、メインスレッドで発生することを知っておく必要があります。非同期タスクから更新を呼び出すこともできます。そして、2つの質問に答えます-NotifyをRecyclerViewに好きなだけ呼び出すことができ、アクションが正しいキューで行われることを確認してください。

RecyclerViewはどのような使用法でも正しく機能します。より複雑な質問は、アダプターの作業です。まず、アイテムの削除などのアダプタアクションを同期し、インデックスの使用を完全に拒否する必要があります。たとえば、例3の方が良いでしょう。

_Item firstItem = new Item(0, “zero”);
list.add(firstItem);
adapter.notifyItemInserted(list.indexOf(firstItem));
//Other action...
Item nextItem = new Item(2, “two”);
list.add(nextItem);
adapter.notifyItemInserted(list.indexOf(nextItem))
//Other actions
_

UPDATE |

RecyclerView.Adapter Doc に関連しています。ここでは、notifyDataSetChanged()と同じ関数を見ることができます。そして、この_RecyclerView.Adapter_が_Android.database.Observable_拡張子を持つ子アイテムを呼び出す場所については、詳細 About Observable を参照してください。このオブザーバブルホルダーへのアクセスは、RecyclerViewのビュー要素が使用されるまで同期されます。

サポートライブラリバージョン25.0のRecyclerViewも参照してください。Lines 9934-9988;

4
GensaGames

レイアウトパス間で複数の更新を行っても問題にはなりません。 RecyclerViewは、このケースを処理(および最適化)するように設計されています。

RecyclerViewは、RecyclerView.AdapterとRecyclerView.LayoutManagerの間に追加レベルの抽象化を導入して、レイアウト計算中にバッチでデータセットの変更を検出できるようにします。 [...] RecyclerViewには、2つのタイプの位置関連メソッドがあります。

  • レイアウト位置:最新のレイアウト計算におけるアイテムの位置。これは、LayoutManagerから見た位置です。
  • アダプターの位置:アダプター内のアイテムの位置。これは、アダプターから見た位置です。

これら2つの位置は同じです。ただし、adapter.notify *イベントのディスパッチと更新されたレイアウトの計算の間の時間は異なります。

あなたの場合の手順は次のとおりです:

  1. データレイヤーを更新します

  2. あなたはadapter.notify*()を呼び出します

  3. Recyclerviewは変更を記録します(AdapterHelper.mPendingUpdatesコードを正しく理解している場合)。この変更は ViewHolder.getAdapterPosition() には反映されますが、まだ ViewHolder.getLayoutPosition() には反映されません。

  4. ある時点で、recyclerViewは記録された変更を適用します。基本的には、レイアウトの視点をアダプターの視点と一致させます。これは、レイアウトパスの前に発生する可能性があります。

1。2。3。シーケンスは任意の数で発生する可能性があります2。1。の直後に続く限り(およびメインスレッドで発生します)。

(1. => 2. => 3.) ... (1. => 2. => 3.) ... 4. 
2
bwt