我正在构建一个类似于Hangouts的Android聊天应用程序。为此,我使用了一个垂直的ListView,并将stackFromBottom=true
和transcriptMode="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;
}
});
}
}
AbsListView
(而不是基本的AdapterView
)中的一个有意的设计选择。它根据第一个/最后一个项目的位置而不是ID来同步滚动状态。但是,如果在报告数据集更改时有一个项目被选中,则它会根据ID同步所选项目本身的滚动位置(无论ListAdapter
是否具有稳定的ID)。 - corsair992