将项添加到ListView,保持滚动位置而不看滚动跳转

Bri*_*uis 26 android android-listview

我正在构建类似于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);
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

然后用户将看到的是快速闪烁到ListView的顶部,然后快速闪回到正确的位置.这个问题很明显,并且被很多人发现:setSelection()直到之后notifyDataSetChanged()重新开始并且不重要ListView.所以我们必须要有post()机会画画.但那看起来很糟糕.

我通过使用反射来"修复"它.我讨厌它.在其核心,就是我要完成的任务是重置的第一位置ListView,而无需通过抽奖周期的rigamarole下去,直到后,我给自己定的位置.要做到这一点,ListView有一个有用的领域:mFirstPosition.通过gawd,这正是我需要调整的!不幸的是,它是包私有的.还不幸的是,似乎没有任何方式以编程方式设置它或以任何不涉及无效循环的方式影响它...产生丑陋的行为.

因此,反映失败的后备:

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);
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

它有用吗?是.这很可怕吗?是.它将来会起作用吗?谁知道?有没有更好的办法?那是我的问题.

如何在没有反思的情况下实现这一点

答案可能是"写下你自己ListView可以处理的事情".我只会问你是否看过代码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;
            }
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

很酷.

Luk*_*rog 17

正如我在评论中所说,OnPreDrawlistener可能是解决问题的另一种选择.使用监听器的想法是跳过显示ListView两种状态之间(添加数据之后和将选择设置到正确位置之后).在OnPreDrawListener(设置为listViewReference.getViewTreeObserver().addOnPreDrawListener(listener);)中,您将检查当前的可见位置,ListView并将其与ListView应显示的位置进行对比.如果那些不匹配,则使侦听器的方法返回false跳过框架并将选择设置ListView在正确的位置.设置正确的选择将再次触发绘图监听器,这次位置将匹配,在这种情况下,您将取消注册OnPreDrawlistener并返回true.