【问题标题】:RecyclerView notifyItemRangeInserted not maintaining scroll positionRecyclerView notifyItemRangeInserted 不保持滚动位置
【发布时间】:2018-02-28 04:18:42
【问题描述】:

我有一个简单的回收视图,其中包含项目(提示)和底部的加载微调器。

这是项目计数和项目视图类型方法的外观:

  @Override
  public int getItemViewType(int position) {
    if (position == getItemCount() - 1) { // last position
      return LOADING_FOOTER_VIEW_TYPE;
    }
    else {
      return TIP_VIEW_TYPE;
    }
  }

  @Override
  public int getItemCount() {
    return tips.size() + 1; // + 1 for the loading footer
  }

基本上,我的所有物品下方都有一个加载微调器。

我像这样创建一次适配器:

  public TipsListAdapter(TipsActivity tipsActivity, ArrayList<Tip> tips) {
    this.tipsActivity = tipsActivity;
    this.tips = tips;
  }

然后,一旦我获取了其他项目,我就会像这样调用 add:

public void addTips(List<Tip> tips) {

    // hide the loading footer temporarily
    isAdding = true;
    notifyItemChanged(getItemCount() - 1);

    // insert the new items
    int insertPos = this.tips.size(); // this will basically give us the position of the loading spinner
    this.tips.addAll(tips);
    notifyItemRangeInserted(insertPos, tips.size());


    // allow the loading footer to be shown again
    isAdding = false;
    notifyItemChanged(getItemCount() - 1);

  }

这里奇怪的是,当我这样做时,滚动位置会转到最底部。似乎它跟随加载微调器。这只发生在第一次添加时(即当只有加载微调器最初显示时)。随后的添加保持正确的滚动位置(插入项目的位置)。

如果我像这样将notifyItemRangeInserted() 更改为notifyItemRangeChanged(),则不会发生这种情况:

public void addTips(List<Tip> tips) {

    // hide the loading footer temporarily
    isAdding = true;
    notifyItemChanged(getItemCount() - 1);

    // insert the new items
    int insertPos = this.tips.size(); // this will basically give us the position of the loading spinner
    this.tips.addAll(tips);
    notifyItemRangeChanged(insertPos, tips.size());


    // allow the loading footer to be shown again
    isAdding = false;
    notifyItemChanged(getItemCount() - 1);

  }

如果我像这样简单地调用notifyDataSetChanged() 也不会发生这种情况:

  public void addTips(List<Tip> tips) {

    this.tips.addAll(tips);
    notifyDataSetChanged();

  }

这是在我的 Activity 中设置适配器的代码:

  public void setAdapter(@NonNull ArrayList<Tip> tips) {
    if (!tips.isEmpty()) { // won't be empty if restoring state
      hideProgressBar();
    }

    tipsList.setAdapter(new TipsListAdapter(this, tips));
  }

 public void addTips(List<Tip> tips) {
    hideProgressBar();
    getAdapter().addTips(tips);
    restorePageIfNecessary();
  }

  private TipsListAdapter getAdapter() {
    return (TipsListAdapter) tipsList.getAdapter();
  }

注意:

  • 我不会在任何地方手动设置滚动位置。
  • 我打电话给setAdapter()onResume()
  • addTips() 在我从服务器获取项目后被调用

如果您需要我的代码的任何其他部分,请告诉我。

【问题讨论】:

  • 请显示您的活动setAdapter 代码
  • 或完整的活动代码更好..
  • @SantanuSur 我不想发布我的全部活动,因为有大量不相关的代码。但我添加了我的setAdapter() 和addTips() 代码。除了这两个地方,我不碰适配器
  • 更新了我的答案,请检查..@Sree

标签: android android-support-library android-recyclerview recyclerview-layout


【解决方案1】:

这只发生在第一次添加时(即当只有加载微调器最初显示时)。随后的添加保持正确的滚动位置(插入项目的位置)。

RecyclerView 在调用更具体的数据集更改方法(如 notifyItemRangeInserted() 而不是 notifyDataSetChanged())时具有内置行为,该方法试图让用户在操作之前查看“相同的东西”。

当数据集发生变化时,用户可以看到的第一个项目优先作为“锚点”,以保持用户查看大致相同的内容。如果可能,RecyclerView 将尝试在适配器更新后保持此“锚”视图可见。

在第一次加载时,第一项(only 项)是加载指示器。因此,当您加载新提示并更新适配器时,此行为将优先考虑将加载指示器保持在屏幕上。由于加载指示器保留在列表的末尾,这会将列表滚动到底部。

在随后的加载中,第一个项目不是加载指示器,它不会移动。所以RecyclerView 不会出现滚动,因为它不必这样做来保持“锚”在屏幕上。

我的建议是检查insertPos,看看它是否为零。如果是,则意味着这是第一次加载,因此您应该通过调用notifyDataSetChanged() 来更新适配器以避免这种锚定行为。否则,请按照您当前的方式致电notifyItemRangeInserted()。

【讨论】:

  • 啊,有道理。我假设notifyItemRangeChanged 的工作方式与notifyDataSetChanged 相同?
  • @Sree 不完全正确。 notifyItemRangeChanged() 会告诉适配器范围内的项目已经改变,应该重新绑定,但范围外的项目没有改变,不需要重新绑定。 notifyDataSetChanged() 会告诉适配器整套项目已经改变,一切都应该重新绑定。考虑到您正在向数据集添加项目,不重新绑定范围之外的项目可能会有问题。
  • ty...这也丰富了我的知识.. xD
  • @BenP。啊,好吧,你知道为什么notifyItemRangeChanged() 为我工作吗?实际上,我最初假设与您所说的相同,但是当它最终起作用时,我感到很惊讶。
  • @Sree 的意思是在你解释之前,她假设`两者`的工作方式相同
【解决方案2】:

在设置new TipsListAdapter(this, tips); 时,从onResume 尽快 删除setAdapter 代码

每次创建适配器的新引用时...创建字段 mAdapter 然后将其设置为 onCreate 。 RecyclerView 不记得滚动的位置,因为每次创建适配器的新引用时.. onResume 被无限调用 当活动处于运行状态时..

所以要么在onCreate 中设置Adapter,使用new 运算符为适配器创建引用,要么,

在onResume 中使用mAdapter 字段变量引用..

【讨论】:

  • 那行不通。在onResume() 中设置它并不重要,因为每次状态更改只调用一次。其次,这也不是notifyItemRangeChanged 和notifyDataSetChanged 没有重现问题的原因。并查看我是如何获得适配器的——在我原来的问题中进行了编辑。我总是引用同一个适配器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-10
  • 2017-01-23
  • 1970-01-01
  • 1970-01-01
  • 2011-03-22
  • 1970-01-01
相关资源
最近更新 更多