增加项目的ListView,保持滚动位置并没有看到一个滚动跳跃 [英] Adding items to ListView, maintaining scroll position and NOT seeing a scroll jump

查看:204
本文介绍了增加项目的ListView,保持滚动位置并没有看到一个滚动跳跃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我建立类似于谷歌视频群聊的界面聊天界面。新消息被添加到该列表的底部。向上滚动到列表的顶部将触发previous消息历史的负荷。当历史从网络进入时,这些消息被添加到列表的顶部,而不应触发从当负载被触发的用户已经停止的位置的任何种类的滚动。换言之,一个负载指示符被显示在列表的顶部:

I'm building an interface similar to the Google Hangouts chat interface. New messages are added to the bottom of the list. Scrolling up to the top of the list will trigger a load of previous message history. When the history comes in from the network, those messages are added to the top of the list and should not trigger any kind of scroll from the position the user had stopped when the load was triggered. In other words, a "loading indicator" is shown at the top of the list:

,然后更换原位任何加载历史。

Which is then replaced in-situ with any loaded history.

我把所有这方面的工作......,除了一件事,我不得不求助于反射来实现。有很多的问题和答案涉及仅仅是保存和添加项目到连接到ListView适配器时,恢复滚动位置。我的问题是,当我像做以下(简化,但应该是不言自明):

I have all of this working... except one thing that I've had to resort to reflection to accomplish. There are plenty of questions and answers involving merely saving and restoring a scroll position when adding items to the adapter attached to a ListView. My problem is that when I do something like the following (simplified but should be self-explanatory):

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的顶部,然后快速闪回正确的位置。现在的问题是相当明显的,受到很多人的发现 setSelection ()不满后才 notifyDataSetChanged()的ListView 的重绘。因此,我们必须后()的观点给它一个抽奖机会。 但是的,看起来很可怕。

Then what the user will see is a quick flash to the top of the ListView, then a quick flash back to the right location. The problem is fairly obvious and discovered by many people: setSelection() is unhappy until after notifyDataSetChanged() and a redraw of ListView. So we have to post() to the view to give it a chance to draw. But that looks terrible.

我固定它通过使用反射。我讨厌它。在它的核心,我想做到的是复位的ListView的第一个位置无需经过抽签周期的rigamarole到的的我已经设置的位置。要做到这一点,有ListView中的一个有用的领域: mFirstPosition 。通过天啊,这正是我需要调​​整!不幸的是,它的封装和私营部门。同样遗憾的是,似乎没有任何办法以编程方式设置,或以任何方式不涉及无效周期影响吧...产生的丑恶行为。

I've "fixed" it by using reflection. I hate it. At its core, what I want to accomplish is reset the first position of the ListView without going through the rigamarole of the draw cycle until after I've set the position. To do that, there's a helpful field of ListView: mFirstPosition. By gawd, that's exactly what I need to adjust! Unfortunately, it's package-private. Also unfortunately, there doesn't appear to be any way to set it programmatically or influence it in any way that doesn't involve an invalidate cycle... yielding the ugly behavior.

所以,反思与失败回退:

So, reflection with a fallback on failure:

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 ,可以处理这个问题。我只是问你是否已经<一个href="http://grep$c$c.com/file/repository.grep$c$c.com/java/ext/com.google.android/android/4.1.1_r1/android/widget/ListView.java">seen在$ C $下的ListView

An answer might be "write your own ListView that can handle this." I'll merely ask whether you've seen the code for ListView.

编辑:工作没有基于Luksprog的评论/回答反射解决方案

Luksprog 推荐的在preDrawListener() 。迷人!我搞砸ViewTreeObservers过,但从来没有其中的一个。经过一番插科打诨,以下类型的事情似乎很完美的工作。

Luksprog recommended an OnPreDrawListener(). Fascinating! I've messed with ViewTreeObservers before, but never one of these. After some messing around, the following type of thing appears to work quite perfectly.

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;
            }
        }
    });
}

非常酷的。

推荐答案

正如我在我的评论,一个在preDrawlistener 表示,可能是另一种选择来解决问题。使用监听器的想法是跳过显示(后增加的数据和选择设置到合适的位置后)两种状态之间的的ListView 。在在preDrawListener (设置 listViewReference.getViewTreeObserver()插件preDrawListener(听众); ),你检查的ListView当前可见的位置和反对该的ListView 应显示的位置进行测试。如果这些不匹配,则使听者的方法返回跳过帧,然后将选择的的ListView 到正确的位置。设置适当的选择将再次触发平局听者,这一次的位置将进行匹配,在这种情况下,你注销在preDrawlistener 并返回

As I said in my comment, a OnPreDrawlistener could be another option to solve the problem. The idea of using the listener is to skip showing the ListView between the two states(after adding the data and after setting the selection to the right position). In the OnPreDrawListener(set with listViewReference.getViewTreeObserver().addOnPreDrawListener(listener);) you'll check the current visible position of the ListView and test it against the position which the ListView should show. If those don't match then make the listener's method return false to skip the frame and set the selection on the ListView to the right position. Setting the proper selection will trigger the draw listener again, this time the positions will match, in which case you'd unregister the OnPreDrawlistener and return true.

这篇关于增加项目的ListView,保持滚动位置并没有看到一个滚动跳跃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆