RecyclerView和适配器数据更新

这是一个关于RecyclerView内部行为的问题,这个行为对于那些熟悉它的机制或愿意深入研究源代码的人来说是一个问题。 我想通过引用来源回答一个答案。

原来的问题

(向下滚动到“换句话说”一个更有针对性的问题)

我需要了解如何notify*行动(例如, notifyItemInserted() )是否入队。 想象一下,我有一个由这个列表备份的适配器:

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

我想添加zerotwo ,这是缺less的值。

例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

但是如果这两个行动彼此非常接近,而且没有布局之间呢?

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

我现在应该怎么做?

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

或者可能

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

? 从适配器的angular度来看,列表立即从one, three, four切换到zero, one, two, three, four所以第二个选项似乎更合理。

例3

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

那么现在呢? 12 ? 该列表后立即更新,但我相信之间没有布局通过。

你有主要问题,我想知道我应该如何在这些情况下行事。 真正的情况是,我有多个asynchronous任务结束在一个insert()方法。 我可以排队他们的行动,但是:

  1. 我不想这样做,如果已经有一个内部队列,那当然是
  2. 我不知道如果两个动作都没有布局传递,会发生什么情况,请参见示例3。

换一种说法

要更新回收商,必须发生四项行动:

  1. 我实际上改变了数据模型(例如将某些东西插入到后备数组中)
  2. 我打电话给adapter.notify*()
  3. 回收商接到电话
  4. Recycler 执行操作(例如,在适配器上调用getItem*()onBind() ),并列出更改。

没有并发性时很容易理解,并且按顺序发生:

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

我们来看看步骤之间会发生什么。

  • 12之间 :我想说开发者有责任在更改数据之后立即调用notify()。 没关系。
  • 23之间 :这种情况立即发生,这里没有问题。
  • 3.4.之间 :这不会立即发生! 据我所知。 因此,完全有可能在上一次更新的步骤34之间进行新的更新(步骤12 )。

我想了解在这种情况下会发生什么。 我们应该如何performance? 在插入新东西之前,我是否应确保上一次更新的第4步发生? 如果这样怎么样?

Solutions Collecting From Web of "RecyclerView和适配器数据更新"

我曾经想过类似的问题,于是我决定:

  1. 如果我想直接插入多个项目到列表的末尾,并想获得所有的animation,我应该:

     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. 如果我想直接插入超过1个项目到列表的末尾,但是想要为每个插入的项目分离animation,我应该:

     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。

希望这可以帮助。

因此,让我们从小介绍开始RecyclerView与通知项目。 和保存的ViewGroup项目的其他列表(例如ListView)相当简单。

RecyclerView具有查看已经绘制的项目的队列。 而且不知道你的更新,没有调用notify(...)方法。 当您添加新的项目并通知RecyclerView时,它将开始逐个检查所有视图的循环。

 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) 

这只是工作通知函数的主要stream程,但是应该知道所有这些操作都发生在UI线程,主线程,甚至可以从asynchronous任务调用更新。 并回答你2问题 – 您可以尽可能多地调用通知给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 

更新|

与RecyclerView.Adapter Doc相关,您可以在其中看到与notifyDataSetChanged()相同的函数。 在RecyclerView.Adapter调用android.database.Observable扩展的子项时,请参阅更多关于Observable 。 访问这个Observable Holder是同步的,直到View元素在RecyclerView中的释放使用。

请参阅支持库版本25.0中的RecyclerView 9934 – 9988行;

如果在布局阶段之间进行多个更新,这应该不成问题。 RecyclerView旨在处理(和优化)这种情况:

RecyclerView在RecyclerView.Adapter和RecyclerView.LayoutManager之间引入了一个额外的抽象级别,以便能够在布局计算过程中检测批次中的数据集更改。 […] RecyclerView中有两种位置相关的方法:

  • 布局位置:项目在最新布局计算中的位置。 这是从LayoutManager的angular度来看的。
  • 适配器位置:适配器中物品的位置。 这是从适配器的angular度来看的。

除了调度adapter.notify *事件和计算更新的布局之间的时间之外,这两个位置是相同

在你的情况下,步骤是:

  1. 你更新数据层

  2. 你可以调用adapter.notify*()

  3. recyclerviewlogging更改(如果我正确理解代码,则在AdapterHelper.mPendingUpdates )。 此更改将反映在ViewHolder.getAdapterPosition()中 ,但尚未在ViewHolder.getLayoutPosition()中反映 。

  4. 在某些时候,recyclerView会应用所logging的更改,基本上它会将布局的angular度与适配器的angular度进行协调。 看起来,这可能发生在布局通过之前。

1.2.3.序列可以发生任何次数,只要2.紧跟在1.之后 (两者都发生在主线程上)。

 (1. => 2. => 3.) ... (1. => 2. => 3.) ... 4. 
 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))