使用反向无休止滚动添加到ListView时,保持滚动位置



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

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

当用户滚动到列表顶部时,ListView使用反向无休止滚动适配器来加载更多(旧的)消息。一旦加载了旧邮件,它们就会被添加到列表的顶部。

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

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

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

更新

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

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

  • 如果列表滚动到底部,则一切正常。在将项目添加到顶部之后,列表仍然滚动到底部
  • 如果列表没有滚动到底部(也没有滚动到顶部),那么列表将保持其滚动位置(从顶部开始)。这会使列表显示为向上"跳"一项。在这种情况下,我希望列表不要"向上跳",也就是说,我希望它保持从底部开始的滚动位置
  • 如果列表滚动到顶部,则与情况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;
}
});
}
}

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

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

首先获取firstVisiblePosition(或倒置后的Last。您必须自己尝试检查),然后获取添加到列表中的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);

来源。

编辑以说明其他信息:

我想我发现了你的问题。我通过使用一个独立列表作为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>

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

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

最新更新