【问题标题】:RecyclerView and Adapter data updatesRecyclerView 和 Adapter 数据更新
【发布时间】:2016-11-09 13:11:26
【问题描述】:

这是一个关于 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

但是如果两个动作彼此非常接近,并且中间没有布局传递怎么办?

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,因此第二个选项似乎更合理。

示例 3

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

现在呢? 12 ?列表在之后立即更新,但我确信两者之间没有布局传递。

问题

您遇到了主要问题,我想知道在这些情况下我应该如何表现。真实情况是我有多个异步任务以 insert() 方法结束。我可以将他们的操作排入队列,但是:

  1. 如果已经有内部队列,我不想这样做,而且肯定有
  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.3. 之间:这会立即发生,这里没有问题。
  • 3.4. 之间:这不会立即发生!据我所知。所以很有可能在步骤 34 之间有一个新的更新(步骤 12之前的更新

我想了解在这种情况下会发生什么。 我们应该如何表现? 我是否应该确保在插入新内容之前确实执行了上一次更新的第 4 步?如果有怎么办?

【问题讨论】:

  • 例如2:可以使用notifyitemrange方法。示例 3:使用 notifyiteminserted(2)
  • @Divyesh 我的测试显示了一个不同的故事,所以我想要一个参考代码支持的答案。此外,我使用小索引(0 和 1)来说明问题,但请把它们视为随机的(例如 0 和 25)。此外,如果您查看源代码,您会发现 notifyItemInserted(position) 始终被视为 notifyItemRangeInserted(position, 1)。您使用哪一个在这里没有什么区别。
  • 对于多个操作,我认为你应该先全部插入,然后使用 notifydatasetchnged

标签: android android-recyclerview


【解决方案1】:

之前我也想过类似的问题,我决定:

  1. 如果我想直接在列表末尾插入超过 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. 如果我想直接在列表末尾插入超过 1 个项目,但是 想要为每个插入的项目获取单独的动画,我应该:

    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. 如果我想在列表的不同位置插入超过 1 个项目, 类似于 2。

希望这能有所帮助。

【讨论】:

    【解决方案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)
    

    这只是工作通知函数的主要流程,但应该知道所有这些操作都发生在 UI 线程、主线程上,甚至您可以从异步任务调用更新。并回答您 2 个问题 - 您可以根据需要向 RecyclerView 调用 Notify,并确保您的操作位于正确的队列中。

    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 扩展的子项,请参阅更多About Observable。对这个 Observable Holder 的访问是同步的,直到 RecyclerView 中的 View Element 释放使用。

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

    【讨论】:

    • 我的问题是,如果我修改后备数组 (list.add) 并通知 RecyclerView while(或 before)RecyclerView能够执行最后一个 notify() 操作(例如您正在谈论的循环)
    • @natario 我已经回答了,即使您使用处理程序任务来启动您的操作,每秒一百次通知,回收器队列与更新数据同步。并且会一一做所有的动作。
    • 关于这个问题(见上文,我添加了一个部分来明确问题),你告诉我第 4 步立即发生,在第 3 步之后同步进行。你能用对源代码的引用?在我看来,这不是真的。见android.support.v7.widget.AdapterHelper类。
    【解决方案3】:

    如果您在布局传递之间进行多次更新,这应该不是问题。 RecyclerView 旨在处理(和优化)这种情况:

    RecyclerView 在 RecyclerView.Adapter 和 RecyclerView.LayoutManager 能够 在布局计算期间批量检测数据集更改。 [...] RecyclerView中有两种位置相关的方法:

    • 布局位置:最近一次布局计算中项目的位置。这是来自 LayoutManager 的位置 观点。
    • 适配器位置:适配器中项目的位置。这是从适配器的角度来看的位置。

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

    在你的情况下,步骤是:

    1. 你更新数据层

    2. 你拨打adapter.notify*()

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

    4. 在某些时候,recyclerView 会应用记录的更改,基本上它会协调布局的观点与适配器的观点。这似乎可以在布局传递之前发生。

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

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

    【讨论】:

    • and both happen on the main thread 为什么 1 应该发生在主线程上?除此之外,我会尽快回复您要求澄清。
    • 否则不能保证 2) 紧跟在 1) 之后。有延迟。我认为实际的顺序应该是 0)收集数据(在后台线程上)然后发布结果(通过事件总线或处理程序)并在主线程上原子地执行 1)和 2)
    • 即适配器看到的数据应该立即更改,并且切换应该发生在主线程上
    • 是的,我的意思是as long as 2 immediately follows 1 (and both happen on the main thread) 会更正确为as long as 2 immediately follows 1 (and 2 happens on the main thread)
    【解决方案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))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-12
      • 1970-01-01
      • 1970-01-01
      • 2021-12-22
      相关资源
      最近更新 更多