我正在构建一个类似于 Google Hangouts 聊天界面的界面。新消息将添加到列表底部。向上滚动到列表顶部将触发加载以前的消息历史记录。当历史记录从网络传入时,这些消息将添加到列表的顶部,并且不应从触发加载时用户停止的位置触发任何类型的滚动。换句话说,“加载指示器”显示在列表顶部:
然后用任何加载的历史记录就地替换它。
我已经完成了所有这些工作......除了一件事我必须诉诸反思才能完成。有很多问题和答案仅涉及在将项目添加到附加到 ListView 的适配器时保存和恢复滚动位置。我的问题是,当我执行以下操作时(简化但应该是不言自明的):
public void addNewItems(List<Item> items) {
final int positionToSave = listView.getFirstVisiblePosition();
adapter.addAll(items);
listView.post(new Runnable() {
@Override
public void run() {
listView.setSelection(positionToSave);
}
});
}
然后用户将看到快速闪到 ListView 的顶部,然后快速闪回到正确的位置。问题相当明显并被发现被很多人 https://groups.google.com/forum/#!topic/android-developers/EnyldBQDUwE: setSelection()
直到之后才不开心notifyDataSetChanged()
和重绘ListView
。所以我们必须post()
给视图一个绘制的机会。But看起来很糟糕。
我已经通过使用反射“修复”了它。我恨它。其核心,我想要完成的是重置ListView
无需经历抽签周期的繁琐,直到after我已经定好位置了为此,ListView 有一个有用的字段:mFirstPosition
。天哪,这正是我需要调整的!不幸的是,它是包私有的。另外不幸的是,似乎没有任何方法可以以编程方式设置它或以任何不涉及无效循环的方式影响它......产生丑陋的行为。
因此,反思失败后的回退:
try {
Field field = AdapterView.class.getDeclaredField("mFirstPosition");
field.setAccessible(true);
field.setInt(listView, positionToSave);
}
catch (Exception e) { // CATCH ALL THE EXCEPTIONS </meme>
e.printStackTrace();
listView.post(new Runnable() {
@Override
public void run() {
listView.setSelection(positionToSave);
}
});
}
}
有效吗?是的。是不是很丑陋?是的。将来有用吗?谁知道?有没有更好的办法?这就是我的问题。
我如何在不反思的情况下实现这一目标?
一个答案might是“自己写的ListView
可以解决这个问题。”我只是问你是否已经看到了代码ListView http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.1.1_r1/android/widget/ListView.java.
编辑:根据 Luksprog 的评论/答案,没有反思的工作解决方案。
Luksprog https://stackoverflow.com/users/493939/luksprog推荐一个OnPreDrawListener()
。迷人!我以前也搞过 ViewTreeObservers,但从来没有搞过其中之一。经过一番混乱之后,以下类型的事情似乎工作得相当完美。
public void addNewItems(List<Item> items) {
final int positionToSave = listView.getFirstVisiblePosition();
adapter.addAll(items);
listView.post(new Runnable() {
@Override
public void run() {
listView.setSelection(positionToSave);
}
});
listView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if(listView.getFirstVisiblePosition() == positionToSave) {
listView.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
else {
return false;
}
}
});
}
很酷。