在使用反向无限滚动的ListView时如何保持滚动位置不变

18

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

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

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

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

不幸的是,这并不起作用。ListView保持滚动位置在列表顶部,而不是底部。也就是说,在将旧消息添加到列表后,列表显示最上面的(即最古老的)消息。

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

更新

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

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

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

(实际上,最后2种情况是相同的。我将它们分开,因为在情况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。 - Submersed
是的,我会。已经更正了,谢谢。 - Fatih Coşkun
你解决了这个问题吗?我也在为此苦苦挣扎。 - Leo
从源代码的粗略查看来看,这似乎是AbsListView(而不是基本的AdapterView)中的一个有意的设计选择。它根据第一个/最后一个项目的位置而不是ID来同步滚动状态。但是,如果在报告数据集更改时有一个项目被选中,则它会根据ID同步所选项目本身的滚动位置(无论ListAdapter是否具有稳定的ID)。 - corsair992
嗯,那么也许在添加更多项目之前我需要将一个项目设置为选定状态? - Leo
@FatihCoşkun 你想到任何解决方案了吗? - Leo
4个回答

12

我想到了解决办法。无论您从哪里获取新数据,在更改适配器中的数据之前,请调用以下内容:

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

1
做和 OP 做的一样;这个答案对我有用 :) - auroranil
最佳解决方案 :) - NullPointer

3

首先获取第一个可见位置(或最后一个,因为它是倒置的。您需要自己尝试和检查),然后获取添加到列表中的视图的顶部,并使用setSelectionFromTop()方法,您可以滚动到所需位置。

代码:

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...所以我应该知道得更好。 - Fatih Coşkun
1
这个答案的方法是正确的,但是提供的代码并没有实现任何功能,因为它基于可见项的_top-based_位置来同步滚动状态,而不是id或基于底部的位置,而这正是ListView本身已经做到的。 - corsair992

0

编辑以考虑附加信息:

我想我已经找到了你的问题。我通过使用一个独立的列表作为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()方法。正如我所说,该列表已经维护了滚动位置。但它是从顶部开始维护的。我需要它从底部开始维护滚动位置。 - Fatih Coşkun
我已经更新了问题,并提供了一个最小化的代码,以演示问题。 - Fatih Coşkun
我测试了你的解决方案,但对我无效。在测试时,请注意不要在向顶部添加新项时滚动到底部。因为在这种情况下,它会起作用。 - Fatih Coşkun
我按照描述测试了所有三种用例,在我的Nexus 7和Nexus 4上都可以正常工作。 - Submersed
很奇怪,我也在 Nexus 4 上进行测试。在情况2中,在将项目添加到顶部(简单点击)后,您的视图显示与单击之前相同的项目? - Fatih Coşkun
显示剩余5条评论

-2

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接