【发布时间】:2013-10-11 14:07:04
【问题描述】:
我正在构建一个类似于 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 顶部,然后快速闪回到正确的位置。问题相当明显,发现by many people:setSelection() 不开心,直到notifyDataSetChanged() 和ListView 重绘之后。所以我们必须post() 给视图一个绘制的机会。 但是这看起来很糟糕。
我已经通过使用反射“修复”了它。我讨厌它。在其核心,我想要完成的是重置ListView 的第一个位置,而无需经历绘制周期的繁琐,直到之后我设置了位置。为此,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);
}
});
}
}
有效吗?是的。可怕吗?是的。将来会起作用吗?谁知道?有没有更好的办法? 这是我的问题。
如何在不反思的情况下完成此任务?
答案可能是“写你自己的ListView 来处理这个问题。”我只想问你有没有seen the code for ListView。
编辑:根据 Luksprog 的评论/答案,没有反思的工作解决方案。
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;
}
}
});
}
很酷。
【问题讨论】:
-
我认为您可以尝试使用
OnPreDrawListener。在该侦听器中,检查ListView的当前第一个可见位置是否等于先前的第一个可见项目位置 + 添加的项目数,如果不是,则将ListView上的选择设置为正确的位置并返回 false (跳过此帧)。如果位置匹配,则在同一个OnPreDrawListener中注销监听器(本身)并返回 true。 -
有趣的想法!让我试一试这个想法……如果它成功了,请创建一个真正的答案并获得您的赏金。
-
Luksprog 发布你的答案,经过一些最终完美运行的骗局。好主意!
-
可能这就是你要找的东西:stackoverflow.com/a/28149739/1468093