Android - ListView duplicate events

In my app I have a ListView backed by ArrayAdapter. In it I'm detecting events in OnScrollListener#onScroll method to find end of the list. I noticed that on the phone (MyTouch) both track-ball and gesture/touch scrolling will trigger the event twice. On the emulator I get the same behavior with a scrolling wheel and click-and-drug scrolling. However in emulator if I use down-arrow button to scroll the event is fired only once.

Here's the code:

this.view.setOnScrollListener(new OnScrollListener() {
    @Override
    public void onScroll(final AbsListView view, final int first, 
                                final int visible, final int total) {
        // detect if last item is visible
        if (visible < total && (first + visible == total)) {
            Log.d("OnScrollListener - end of list", "fvi: " + 
               first + ", vic: " + visible + ", tic: " + total);
            // this line gets called twice
            onLastListItemDisplayed(total, visible);
        }
    }
}

How do I suppress or handle this behavior? I need just a single event and trying not to revert to silly hacks such as boolean field.

As far as I can tell - both events have identical stacktrace

Thread [<3> main] (Suspended (breakpoint at line 116 in SearchResultsView$4)) 
 SearchResultsView$4.onScroll(AbsListView, int, int, int) line: 116 
 ListView(AbsListView).invokeOnItemScrollListener() line: 655 
 ListView.arrowScrollImpl(int) line: 2256 
 ListView.arrowScroll(int) line: 2172 
 ListView.commonKey(int, int, KeyEvent) line: 1977 
 ListView.onKeyMultiple(int, int, KeyEvent) line: 1929 
 KeyEvent.dispatch(KeyEvent$Callback) line: 899 
 ListView(View).dispatchKeyEvent(KeyEvent) line: 3647 
 ListView(ViewGroup).dispatchKeyEvent(KeyEvent) line: 744 
 ListView.dispatchKeyEvent(KeyEvent) line: 1909 
 FrameLayout(ViewGroup).dispatchKeyEvent(KeyEvent) line: 746 
 LinearLayout(ViewGroup).dispatchKeyEvent(KeyEvent) line: 746 
 PhoneWindow$DecorView(ViewGroup).dispatchKeyEvent(KeyEvent) line: 746 
 PhoneWindow$DecorView.superDispatchKeyEvent(KeyEvent) line: 1708 
 PhoneWindow.superDispatchKeyEvent(KeyEvent) line: 1197 
 SearchResultsView(Activity).dispatchKeyEvent(KeyEvent) line: 1967 
 PhoneWindow$DecorView.dispatchKeyEvent(KeyEvent) line: 1684 
 ViewRoot.deliverKeyEventToViewHierarchy(KeyEvent, boolean) line: 2329 
 ViewRoot.handleFinishedEvent(int, boolean) line: 2299 
 ViewRoot.handleMessage(Message) line: 1621 
 ViewRoot(Handler).dispatchMessage(Message) line: 99 
 Looper.loop() line: 123 
 ActivityThread.main(String[]) line: 4203 
 Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method] 
 Method.invoke(Object, Object...) line: 521 
 ZygoteInit$MethodAndArgsCaller.run() line: 791 
 ZygoteInit.main(String[]) line: 549 
 NativeStart.main(String[]) line: not available [native method]

P.S. Bug is created

P.P.S. And closed with very useful reply The trackball and the touch screen send several motion events very rapidly. This behavior is expected.

13.10.2009 18:54:33
I remember running across this once too. Never did find a solution. Good luck.
Jeremy Logan 13.10.2009 19:30:26
I posted a bug at Android bug-tracking site, see if they can at least comment on that
Bostone 13.10.2009 20:01:39
2 ОТВЕТА
РЕШЕНИЕ

Since good people of Google are saying that this is as intended here's an ugly brutal way of dealing with this problem that works for me. Basically the assumption here is that as events are issued I compare last saved first item index with one that is passed in the onScroll call. I will only process event if these do not match:

    this.view.setOnScrollListener(new OnScrollListener() {
        private int lastSavedFirst = -1;
        @Override
        public void onScroll(final AbsListView view, final int first, final int visible, final int total) {
            // detect if last item is visible
            if (visible < total && (first + visible == total)) {
                // only process first event
                if (first != lastSavedFirst) {
                    lastSavedFirst = first;
                    Log.d("OnScrollListener - end of list", "fvi: " + first + ", vic: " + visible + ", tic: "
                            + total);
                    onLastListItemDisplayed(total, visible);
                }
            }
        }

        @Override
        public void onScrollStateChanged(final AbsListView view, final int scrollState) {
            // Log.d("OnScrollListener", "state: " + scrollState);
        }
    });
4
13.10.2009 20:55:28

I've got also this problem with multiple events firing of while I was scrolling through the ListView. I've intended to load more ListItems that moment when I was passing a treshold value. However, this always resulted in multiple called AsyncTasks which populated my ListView faster than wanted.

I've found a good solution in this blogpost. It basically includes a check for a boolean, although you dislike this solution. I find it also some kind of hacky but I did not came up with another solution though.

0
20.02.2012 21:24:15