android – 添加项目到ListView,保持滚动位置,不看到滚动跳转

我正在建立类似Google环聊聊天界面的界面。新消息将添加到列表的底部。向上滚动到列表顶部将触发上一条消息历史记录的加载。当历史记录来自网络时,这些消息被添加到列表的顶部,并且不应该触发从触发负载时用户停止的位置开始的任何滚动。换句话说,“加载指示器”显示在列表的顶部:

然后将其与任何加载的历史原位替换。

我有所有这些工作…除了一件事,我不得不诉诸反射完成。有很多问题和答案,只涉及在将项目添加到附加到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的第一个位置,而不经过绘制周期的rigamarole,直到我设置了位置。要做到这一点,有一个有用的领域的ListView:mFirstPosition。通过gawd,这正是我需要调整!不幸的是,它是package-private。不幸的是,似乎没有任何方式来设置它以编程方式或影响它以任何方式,不涉及无效循环…产生丑陋的行为。

所以,反射与故障后退:

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(在添加数据之后和将选择设置到正确的位置之后)。在OnPreDrawListener(设置listViewReference.getViewTreeObserver()。addOnPreDrawListener(listener);),你将检查ListView的当前可见位置,并测试它的位置ListView应该显示。如果那些不匹配,那么使侦听器的方法返回false以跳过框架,并将ListView上的选择设置到正确的位置。设置正确的选择将再次触发绘制监听器,这时位置将匹配,在这种情况下,您将取消注册OnPreDrawlistener并返回true。

转载注明原文:android – 添加项目到ListView,保持滚动位置,不看到滚动跳转 - 代码日志