Detect when ListView has reached the bottom - onScroll() or onScrollStateChanged()?
Asked Answered
F

2

6

This seems a question with many answers already; however, I can't find a common approach to this. I'm trying to add data to a ListView when the bottom is reached; data are retrieved from Internet using an AsyncTask. ListView already has an adapter attached to it.

So, searching for a good way to accomplish this, I arrived at two different approaches.

  1. The first one, the onScrollStateChanged() approach, is basically related to this page. However, it use parameters that don't have correspondence in the actual API. At the same time, this link use the right API, but I don't know if in the right way. I tried with the first of the two links, but it's kind of meh. Debugging the app, diff values vary a lot and I don't know how to correctly interpet the expression. Also, I don't know how to fix an offset from which I can start to retrieving data; I mean, I'd like to execute code not when I'm about to reach the bottom, but just before I reach it. Also, sometimes it get called even if we scroll to the top.
  2. The second one is the onScroll() approach, that is used in this answer or, in a different way, in this code. I tried adapting the last of the two codes, but it cause many problems and the data are loaded even if we don't reach the bottom of the list.

So, what approach is best? When and why should I prefer one or the other? Which of the two should I use in my case?

Faefaeces answered 27/1, 2015 at 18:23 Comment(7)
I find it best to use the adapter, in getView check if it's the last item in your dataset, this means you've scrolled to the end of the list.Sacrament
@Sacrament sure, but in which method do you use it?Faefaeces
Do you already use a custom adapter?Sacrament
yes, I'm using a custom adapter for my listviewFaefaeces
So you override getView which is passed a position parameter. Check the position against the size of your adapter to see if you've reached the end.Sacrament
@Sacrament thanks, it's pretty straightforward and simply does its work. I implemented it in onScrollStateChanged(), but still I'm not too sure about it.Faefaeces
Sorry my suggestion has nothing to do with scroll listeners, the adapter's getView is executed whenever an item in your ListView is created. As you scroll it is called again and again. The point of my comment is to suggest a third approach which is to execute your method to load more data within this method. But only when the position parameter indicates that you've reached the end of the dataset.Sacrament
S
18

Here's some code for my suggested third approach, which I use in my own projects. I use the adapter's getView method to detect when the end of the list has been reached.

public View getView(int position, View convertView, ViewGroup parent) {
    // handle recycling/creating/initializing view
    if(reachedEndOfList(position)) loadMoreData();
    return convertView;
}

private boolean reachedEndOfList(int position) {
    // can check if close or exactly at the end
    return position == getSize() - 1;
}

private void loadMoreData() {
    // Perhaps set flag to indicate you're loading and check flag before proceeding with AsyncTask or whatever
}
Sacrament answered 27/1, 2015 at 19:7 Comment(6)
thanks for your time and your reply. I was wonder if it is theoretically right: what an adapter should/shouldn't do? Is it its job to load more data when the scroll reach the bottom?Faefaeces
I personally think it's the most suitable place for it. I also let the adapter handle loading the initial data. Also while data is loading I use the adapter to show a mock item with a loading icon by incrementing the adapter size and using a different view type to render the extra item differently. I think it works very well.Sacrament
Thanks for your help, I think you're right. Waiting for some other replies before accept your answer.Faefaeces
How can i call my main activity class loaddata() method inside getview() method ??Pinko
@SudhanshuGaur you can use interface to do thatOrosco
This is THE best solution to this problem. Thank you. @tigerjack89 mark this as the answer already...Pneumatics
T
0

You can use an adapter to detect when the listview has been scrolled to its bottom as @darnmason has done in the accepted answer above, but I discovered that sometimes when the list is scrolled very fast, the getView method might not finish processing the last position in the adapter last...maybe because it was still rendering some earlier position.

This annoying effect caused a button that was to fade into view when I scrolled to the bottom of the list to sometimes fail to render.

Here is the solution with the annoying effect, which in principle is similar to @darnmason's solution:

public abstract class MyAdapter extends BaseAdapter {

public View getView(int position, View convertView, ViewGroup parent) {

//your code for getView here...


    if(position == this.getCount() - 1){
                onScrollToBottom(position);
            }
    else{
                onScrollAwayFromBottom(position);
             }
   return convertView;
}


    public abstract void onScrollToBottom(int bottomIndex);
    public abstract void onScrollAwayFromBottom(int currentIndex);


}

This solution detects when the list is scrolled to the bottom and when it is scrolled away from the bottom.

To eliminate the annoying effect, simply modify as follows:

public abstract class MyAdapter extends BaseAdapter {

public View getView(int position, View convertView, ViewGroup parent) {

//your code for getView here...


    if(position == this.getCount() - 1){
                onScrollToBottom(position);
            }
    else{
           AdapterView adapterView = (AdapterView) parent;
                int count = adapterView.getCount();
         if(adapterView.getLastVisiblePosition() == count - 1){
//The adapter was faking it, it is already at the bottom!
                    onScrollToBottom(count - 1);
          }
         else {
//Honestly! The adapter is truly not at the bottom.
                    onScrollAwayFromBottom(position);
               }
             }
   return convertView;
}


    public abstract void onScrollToBottom(int bottomIndex);
    public abstract void onScrollAwayFromBottom(int currentIndex);


}

Now call your adapter as you normally would, like this:

MyAdapter adapter = new MyAdapter(){

  @Override
            public void onScrollToBottom(int bottomIndex) {
/*loadMore is a button that fades into view when you are not at the bottom of the list so you can tap and load more data*/
                    loadMore.show();

            }

            @Override
            public void onScrollAwayFromBottom(int currentIndex) {
/*loadMore is a button that fades out of view when you are not at the bottom of the list*/
                  loadMore.hide();

            }

}

When implemented this way, the adapter becomes very efficient at detecting when the list has been scrolled to its bottom.

All it needed was a little cooperation from the list!

Thallus answered 26/3, 2019 at 5:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.