RecyclerView notifyItemRangeInserted 不保持滚动位置

RecyclerView notifyItemRangeInserted not maintaining scroll position

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

项目计数和项目视图类型方法如下所示:

  @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;
  }

然后一旦我获取了额外的项目,我就会像这样调用添加:

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();
  }

注:

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

尽快从 onResume 中删除 setAdapter 代码,因为您正在设置 new TipsListAdapter(this, tips);

Every time a new reference of the adapter is created...make field mAdapter and then set it in onCreate . RecyclerView doesnt remember the scrolled position because everytime a new reference of adapter is being created.. onResume gets called infinitely when activity is in running state..

因此,您可以在 onCreate 中使用新运算符为适配器创建引用来设置适配器,或者,

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

This only happens on the first add (i.e. when there is only the loading spinner showing initally). subsequent adds maintains the proper scroll position (the position where the items were inserted).

RecyclerView 在调用 more-specific 数据集更改方法(如 notifyItemRangeInserted() 而不是 notifyDataSetChanged())时具有 built-in 行为,试图留住用户看着"the same thing"和手术前一样。

当数据集发生变化时,用户可以看到的第一个项目优先为 "anchor",以使用户看到大致相同的东西。如果可能,RecyclerView 将尝试在适配器更新后保持此 "anchor" 视图可见。

在第一次加载时,第一个项目( 项目)是加载指示器。因此,当您加载新提示并更新适配器时,此行为将优先保留加载指示器 on-screen。由于加载指示器保留在列表的末尾,这会将列表滚动到底部。

在随后的加载中,第一项不是加载指示器,它不会移动。所以 RecyclerView 不会出现滚动,因为它不必这样做来保持 "anchor" on-screen.

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