android - RecyclerView notifyItemRangeInserted 不保持滚动位置

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

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

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

  @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()
  • addTips() 在我从服务器获取项目后被调用

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

最佳答案

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 在调用更具体的数据集更改方法时具有内置行为(如 notifyItemRangeInserted() 而不是 notifyDataSetChanged() ) 试图让用户看到与操作前“相同的东西”。

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

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

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

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

关于android - RecyclerView notifyItemRangeInserted 不保持滚动位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49016668/

相关文章:

Android JSON 解析 - 从 MySQL 数据库读取数据,解析并在 ListView 中显示结果

java - 如何设置自定义尺寸的背景图片?

android - 允许用户仅访问我的内容提供者中的某些表

android - 对于 recyclerView 中的 TextView,textIsSelectable() 在 "floating text selection toolbar"中缺少项目

android - ScrollView vs RecyclerView 适用于 Android 上的不同 child

android - Mixpanel Analytics for Android 在离线时可以工作吗?

未调用 Android DialogFragment onViewCreated

gradle - 试图弄清楚如何将 GestureDetectorCompat 添加到我的项目中

android - CollapsingToolbarLayout setTitle() 不再工作

Android sticky-footer : Align footer view to table, 直到达到屏幕大小,然后固定在底部