【问题标题】:Maintain scroll position when adding to ListView with reverse endless-scrolling使用反向无限滚动添加到 ListView 时保持滚动位置
【发布时间】:2014-03-29 21:09:03
【问题描述】:

我正在构建一个类似于聊天的 Android 应用程序,类似于环聊。为此,我使用带有stackFromBottom=truetranscriptMode="normal" 的垂直ListView。

列表从较早的消息(顶部)到较年轻的消息(底部)排序。正常状态下,ListView滚动到底部,显示最年轻的消息。

一旦用户滚动到列表顶部,ListView 使用反向无限滚动适配器加载更多(旧)消息。加载较旧的消息后,它们将被添加到列表顶部。

当旧消息添加到列表顶部时,所需的行为是 ListView 将其滚动位置保持在列表底部。也就是说,当旧消息添加到顶部时,滚动视图应该显示与添加旧消息之前相同的消息。

很遗憾,这不起作用。 ListView 不会将滚动位置保持在底部,而是将滚动位置保持在列表顶部。也就是说,将较旧的消息添加到列表后,列表会显示最顶部(即最旧)的消息。

有没有一种简单的方法来告诉 ListView 在向其顶部添加新项目时将其滚动位置保持在底部而不是顶部?

更新

出于演示目的,我已尽可能减少用例和代码。下面的示例创建一个简单的字符串数组列表。单击一个项目将在列表顶部添加另一个项目。长按一个项目将在列表底部添加另一个项目。

在列表底部添加项目时一切正常(长按)。在列表顶部添加项目时(简单点击),则有3种情况:

  • 如果列表滚动到底部,则一切正常。将项目添加到顶部后,列表仍然滚动到底部。
  • 如果列表没有滚动到底部(也没有滚动到顶部),则列表将保持其滚动位置(从顶部开始)。这使得列表看起来“跳”了一个项目。在这种情况下,我希望列表不要“向上跳跃”,即我希望它从底部保持滚动位置。
  • 如果列表滚动到顶部,则会发生与情况 2 相同的情况。首选行为与情况 2 相同。

(最后2个case其实是一样的,我把它们分开是因为case 2可以更好的说明问题。)

代码

public class MyListActivity extends Activity {
  int topIndex = 1;

  int bottomIndex = 20;

  protected final void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.my_list_activity);

    final ListView list = (ListView) findViewById(R.id.list);
    list.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
    list.setStackFromBottom(true);

    final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1);
    for (int i = topIndex; i <= bottomIndex; i++) {
      adapter.add(Integer.toString(i));
    }
    list.setAdapter(adapter);

    list.setOnItemClickListener(new OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
        adapter.insert(Integer.toString(--topIndex), 0);
      }
    });

    list.setOnItemLongClickListener(new OnItemLongClickListener() {
      @Override
      public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
        adapter.add(Integer.toString(++bottomIndex));
        return true;
      }
    });
  }
}

【问题讨论】:

  • 我相信你的意思是 android:stackFromBottom=true
  • 是的,我愿意。已更正,谢谢。
  • 您找到解决方案了吗?也在为此苦苦挣扎。
  • 粗略查看源代码,这似乎是AbsListView(与基础AdapterView 相对)中经过深思熟虑的设计选择。它根据第一个/最后一个项目的 position 而不是 id 同步滚动状态。但是,如果它在报告数据集更改时选择了一个项目,那么它会根据 id 同步所选项目本身的滚动位置(无论ListAdapter 是否具有稳定的 id)。
  • 嗯,那么也许我需要在添加更多项目之前将一个项目设置为选中?

标签: android listview scroll endlessadapter


【解决方案1】:

我已经想通了。无论您在哪里获取新数据,我都会在更改适配器中的数据之前调用以下命令:

int firstVisibleItem = list.getFirstVisiblePosition();
int oldCount = adapter.getCount();
View view = list.getChildAt(0);
int pos = (view == null ? 0 :  view.getBottom());

然后我打电话:

adapter.setData(data);

list.setSelectionFromTop(firstVisibleItem + adapter.getCount() - oldCount + 1, pos);

【讨论】:

  • 和OP一样做;这个答案对我有用:)
  • 有史以来最好的解决方案 :)
【解决方案2】:

在适配器中插入记录后,您可以尝试使用

   yourListView.setSelection(yourAdapter.getCount() - 1);

【讨论】:

    【解决方案3】:

    编辑以说明更多信息:

    我想我已经找到了您的问题。我通过使用独立列表作为 XML 中的唯一视图复制了您描述的用例:

    <ListView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </ListView>
    

    我通过将布局嵌套在父布局中来解决问题以遵循您想要的用例:

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <ListView
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
        </ListView>
    
    </FrameLayout>
    

    【讨论】:

    • 我在初始化活动时将适配器设置为列表一次。之后,将新数据添加到现有适配器,然后是 notifyDataChanged()。正如我所说,列表已经保持滚动位置。然而,它是从顶部开始的。我需要它来保持底部的滚动位置。
    • 不是 100% 清楚这里的区别。因为在你的问题中你说 - “也就是说,当旧消息添加到顶部时,滚动视图应该显示与添加旧消息之前显示的相同消息”;如果它保持滚动位置,它保持当前滚动位置 - 周期。如果滚动位置没有改变,那么从“顶部位置”和“底部位置”保持它并没有真正的区别。
    • 请注意,以下内容来自我的观察。向 ListView 添加新项目时,它会保持滚动位置。这意味着在添加项目后,它将向下滚动与以前完全相同的数量。这在将项目添加到列表底部时非常有用。也就是说,相同的项目在添加新项目之前和之后都是可见的(到底部)。但是,由于我将项目添加到列表的顶部,因此在添加项目(到顶部)后,维护的 scroll-y 会导致显示不同的项目。
    • 我已经测试了两个用例,即 addToTop 是 adapter.insert(item, 0);并且 addToBottom 是 adapter.add(item);,而在我的测试项目中并非如此。另外,我用 android:stackFromBottom="true" 和 android:transcriptMode="normal" 为 ListView 充气,我得到了你想要的功能 - 所以没有看到源代码,很难说问题是什么。
    • 我明天将尝试复制我的代码的重要部分。您的代码的一个区别是我没有使用 ArrayAdapter。我正在使用 BaseAdapter 的自定义子类。因此我没有方法 adapter.insert(item,0) 或 adapter.add(item)。我会查找这些方法的实现,但我不认为它们在这里有什么神奇之处。
    【解决方案4】:

    首先获取 firstVisiblePosition (或倒置后的最后一个。你自己尝试检查一下)然后获取添加到 List 的 View 的顶部,然后使用 setSe;ectionFromTop() 方法,您可以滚动到所需的位置。

    代码:

    int index = mList.getFirstVisiblePosition();
    View v = mList.getChildAt(0); //or upside down (list.size - 1)
    int top = (v == null) ? 0 : v.getTop(); //returns the top of the view its Y co-ordinates. [Doc][1]
    mList.setSelectionFromTop(index, top);
    

    Source.

    【讨论】:

    • 我也一直在摆弄一些自定义代码。您的代码显示了总体思路,但在我的情况下,代码看起来要复杂得多(因为项目已添加到列表的顶部)。我发现代码非常复杂且样板化,并希望可能有更简单的方法。嗯,这是 Android ......所以我应该知道的更好。
    • 这个答案在它的方法上是正确的,但是提供的代码并没有完成任何事情,因为它根据可见项目的 top-based 位置同步滚动状态id 或基于底部的位置,这是 ListView 本身已经做的。
    猜你喜欢
    • 1970-01-01
    • 2012-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-21
    • 2011-03-02
    相关资源
    最近更新 更多