Implementation of onScrollListener to detect the end of scrolling in a ListView
Asked Answered
E

9

42

I've a ListView displaying some items. I'd like to perform some operation on the items that are currently displayed in the visible portion of the ListView, depending on how the ListView has been scrolled; thus I thought to implements the OnScrollListener of the ListView. Accordingly to the Android api reference, the onScroll method "will be called after the scroll has completed". This seems to me right what I needed, as once the scroll has completed, I perform my actions on the ListView (the onScroll method returns the index of the first item displayed and the number of items displayed).

But once implemented, I see from the LogCat that the onScroll method is not just fired once the scroll has completed, but is fired for every new item that enters the displaying view, from the beginning to the end of the scrolling. This is not the behavior I expect nor I need. The other method of the listener (onScrollStateChanged), instead, does not provide information about the items currently displayed in the ListView.

So, does anyone know how to use this couple of methods to detect the ending of the scroll and get the information about the displayed items? The misalignment between the api reference and the actual behavior of the method confused me a bit. Thanks in advance.

P.S.: I've seen some similar topics around, but nothing helps me understanding how the whole thing works..!

Engraving answered 15/6, 2011 at 13:28 Comment(1)
try this i think it would be help full github.com/commonsguy/cwac-endless you can use endless adapter.Issuance
E
54

In the end I've reached a solution not so much elegant but that worked for me; having figured out that onScroll method is called for every step of the scrolling instead of just being called at the scroll end, and that onScrollStateChanged is actually being called only when scrolling is completed, I do something like this:

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    this.currentFirstVisibleItem = firstVisibleItem;
    this.currentVisibleItemCount = visibleItemCount;
}

public void onScrollStateChanged(AbsListView view, int scrollState) {
    this.currentScrollState = scrollState;
    this.isScrollCompleted();
 }

private void isScrollCompleted() {
    if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
        /*** In this way I detect if there's been a scroll which has completed ***/
        /*** do the work! ***/
    }
}

Practically, everytime the ListView is being scrolled I save the data about the first visible item and the visible item count (onScroll method); when the state of scrolling changes (onScrollStateChanged) I save the state and I call another method which actually understand if there's been a scroll and if it's completed. In this way I also have the data about the visible items that I need. Maybe not clean but works!

Regards

Engraving answered 17/6, 2011 at 10:46 Comment(4)
I used something similar that worked extremely well in my case. Simple, but effective: public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } public void onScrollStateChanged(AbsListView view, int scrollState) { isScrollCompleted(scrollState); } private void isScrollCompleted(int currentScrollState) { if (currentScrollState == OnScrollListener.SCROLL_STATE_IDLE) { mAdapter.notifyDataSetChanged(); } }Sudhir
How can i show 10 items at a time and then load another 10 items using your code. Please help!!!Fatality
No need of currentFirstVisibleItemHachman
currentFirstVisibleItem can be compared with 0 to detect scroll to top.Bentham
M
13
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
  int visibleItemCount, int totalItemCount) {

  if (list.getLastVisiblePosition() == list.getAdapter().getCount() - 1
      && list.getChildAt(list.getChildCount() - 1).getBottom() <= list.getHeight()) {
    // Scroll end reached
    // Write your code here
  }
}
Misdemean answered 13/9, 2013 at 7:21 Comment(0)
T
9
@Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {

            if((firstVisibleItem + visibleItemCount) ==  totalItemCount)
            {
                Log.i("Info", "Scroll Bottom" );
            }
        }});
Tade answered 17/6, 2014 at 1:25 Comment(0)
L
6

To get this behavior down is tricky, and took me quite a while to perfect. The meat of the problem is that the scroll listeners by themselves are not really quite sufficient to detect a "scroll stop" (including the directional buttons/trackball), as far as I could tell. I ended up doing a combination of things that works right as I expect it.

The best way I figured I could do it was to extend ListView and override a few methods:

  ....
  @Override
  public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_UP) {
      startWait();
    }
    return super.onKeyUp(keyCode, event);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
      stopWait();
    }
    if (event.getAction() == MotionEvent.ACTION_UP ||
        event.getAction() == MotionEvent.ACTION_CANCEL) {
      startWait();
    }
    return super.onTouchEvent(event);
  }

  private OnScrollListener customScrollListener = new OnScrollListener() {
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
      int totalItemCount) {
      stopWait();
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
      if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
        startWait();
      } else {
        stopWait();
      }
    }
  };

  //All this waiting could be improved, but that's the idea
  private Thread waitThread = null;
  private int waitCount  = Integer.MIN_VALUE;

  public void stopWait() {
    waitCount = Integer.MIN_VALUE;
  }

  public synchronized void startWait() {
    waitCount = 0;

    if (waitThread != null) {
      return;
    }

    waitThread = new Thread(new Runnable() {
    @Override
    public void run() {
    try {
      for (; waitCount.get() < 10; waitCount++) {
        Thread.sleep(50);
      }

        //Kick it back to the UI thread.
        view.post(theRunnableWithYourOnScrollStopCode); // HERE IS WHERE YOU DO WHATEVER YOU WANTED TO DO ON STOP
      } catch (InterruptedException e) {
      } finally  {
        waitThread = null;
      }
    }
  });
  waitThread.start();
}

Note that you also have to bind the customScrollListener in your constructors. This implementation is nice, I think, because it won't immediately fire the "event", it will wait a bit until it has actually fully stopped scrolling.

Lionhearted answered 15/6, 2011 at 13:55 Comment(2)
Thanks, your answer helps me a lot but it's kinda too tricky for my purpose by now; I've ended with another kind of solution I'm posting below...Engraving
@Lionhearted Do you mean theRunnableWithYourOnScrollStopCode is the place where I can put my function there: like loading data or making a web request? Thanks!Thyself
E
4

Analyzing previous answers, found its own:

In onViewCreated, onCreate or wherever you grab your listview:

ListView list = (ListView) view.findViewById(R.id.[your id]);
    list.setOnScrollListener(this);

Then fill in listener methods:

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
 // yes, just leave it empty
}

public void onScrollStateChanged(AbsListView view, int scrollState) {
    this.isScrollCompleted(view, scrollState);
 }

private void isScrollCompleted(AbsListView view, int scrollState) {
    if (!view.canScrollVertically(1) && this.currentScrollState == SCROLL_STATE_IDLE) {
        // do work
    }
}

Seems does what it should: detect end of list without tons of code and modifications of adapter.

Envisage answered 25/12, 2015 at 15:57 Comment(0)
V
3

Try this one...

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                     int totalItemCount) {

    int position = firstVisibleItem+visibleItemCount;
    int limit = totalItemCount;

    // Check if bottom has been reached
    if (position >= limit && totalItemCount > 0) {
         //scroll end reached
          //Write your code here
    }
}
Verdure answered 21/4, 2015 at 6:49 Comment(0)
E
3

Just more useful representation of e-cal's answer

Listener implementation:

public abstract class OnScrollFinishListener implements AbsListView.OnScrollListener {

    int mCurrentScrollState;
    int mCurrentVisibleItemCount;

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        mCurrentScrollState = scrollState;
        if (isScrollCompleted()) {
            onScrollFinished();
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        mCurrentVisibleItemCount = visibleItemCount;
    }

    private boolean isScrollCompleted() {
        return mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE;
    }

    protected abstract void onScrollFinished();
}

Usage:

    listView.setOnScrollListener(new OnScrollFinishListener() {
         @Override
         protected void onScrollFinished() {
             // do whatever you need here!
         }
    });
Eduino answered 2/10, 2015 at 9:12 Comment(0)
S
0

There is yet one more simple solution. Put a listener to the Adapter of listview and in the getview check if(position== totalItemCount of items in array to be displayed), if yes i call up my listener from adapter and in the Listener Run your operation whatever it be

Synopsis answered 18/4, 2013 at 8:57 Comment(1)
This does not take into account virtualization. You'll think you've reached the end before the last item is actually visible.Pregnant
D
0

I experienced a lot of issues when trying to figure out if the List is at the bottom from the OnScrollListener, the best solution I found is to check from the getView method of the adapter if I'm currntly showing the last row:

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ActivityView view;
        if (convertView==null)
            view = (ActivityView)inflater.inflate(R.layout.activity_item, null);
        else
            view = (ActivityView)convertView;

       view.showRow(activitiesList.get(position),position,controller,this);
        if(position == activitiesList.size() - 1)
        {
            Log.i("BOTTOM","reached");
        }
        return view;
    }

there I can easily create an Interface to notify the Fragment/Activity that I reached the bottom.

cheers.

Dentiform answered 6/5, 2015 at 3:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.