235

How can I create a list where when you reach the end of the list I am notified so I can load more items?

tshepang
  • 10,772
  • 21
  • 84
  • 127
Isaac Waller
  • 32,041
  • 27
  • 93
  • 107
  • 9
    I recommend CommonWare's EndlessAdapter: http://github.com/commonsguy/cwac-endless. I found it from this answer to another question: http://stackoverflow.com/questions/4667064/android-implementing-endless-list-like-android-market/4667102#4667102. – Tyler Collier Jun 07 '11 at 05:25
  • I need same thing..can any one help?..http://stackoverflow.com/questions/28122304/endless-scrolling-listview-not-working –  Jan 30 '15 at 04:00

10 Answers10

269

One solution is to implement an OnScrollListener and make changes (like adding items, etc.) to the ListAdapter at a convenient state in its onScroll method.

The following ListActivity shows a list of integers, starting with 40, adding items when the user scrolls to the end of the list.

public class Test extends ListActivity implements OnScrollListener {

    Aleph0 adapter = new Aleph0();

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setListAdapter(adapter); 
        getListView().setOnScrollListener(this);
    }

    public void onScroll(AbsListView view,
        int firstVisible, int visibleCount, int totalCount) {

        boolean loadMore = /* maybe add a padding */
            firstVisible + visibleCount >= totalCount;

        if(loadMore) {
            adapter.count += visibleCount; // or any other amount
            adapter.notifyDataSetChanged();
        }
    }

    public void onScrollStateChanged(AbsListView v, int s) { }    

    class Aleph0 extends BaseAdapter {
        int count = 40; /* starting amount */

        public int getCount() { return count; }
        public Object getItem(int pos) { return pos; }
        public long getItemId(int pos) { return pos; }

        public View getView(int pos, View v, ViewGroup p) {
                TextView view = new TextView(Test.this);
                view.setText("entry " + pos);
                return view;
        }
    }
}

You should obviously use separate threads for long running actions (like loading web-data) and might want to indicate progress in the last list item (like the market or gmail apps do).

Josef Pfleger
  • 72,585
  • 15
  • 95
  • 99
  • 3
    huh, wouldn't this trigger the adapter size increase if you'd stop scrolling in the middle of the screen? it should only load the next 10 when actually reaching the list bottom. – Matthias Aug 05 '10 at 10:13
  • 9
    I notice a lot of examples like this using BaseAdapter. Wanted to mention that if you're using a database, try using a SimpleCursorAdapter. When you need to add to the list, update your database, get a new cursor, and use SimpleCursorAdapter#changeCursor along with notifyDataSetChanged. No subclassing required, and it performs better than a BaseAdapter(again, only if you're using a database). – brack Oct 20 '10 at 18:51
  • 1
    After calling notifyDataSetChanged(), it will go top of the list, how can I prevent it? – draw Sep 02 '11 at 12:57
  • 2
    A note - I used this approach, but needed to add a check for when visibleCount is 0. For each list, I get one callback to onScroll where firstVisible, visibleCount, and totalCount are all 0, which technically meets the conditional, but is not when I want to load more. – Eric Mill Jan 05 '12 at 22:37
  • Hi Josef ,can you please give me a solution for the same for implementing this if am using a class which will extend an ArrayAdapter instead of BaseAdapter. – TKV Jan 24 '12 at 11:45
  • 5
    Another problem with this code is that the condition `firstVisible + visibleCount >= totalCount` is met multiple times with multiple calls to the listener. If the load-more function is a web request, (most probably it will be), add another check for if a request is going on or not. On the other hand, check if the totalCount is not equal to the Previous total count, because we don't need multiple requests for the same number of items in the list. – Subin Sebastian Aug 24 '12 at 20:14
  • I want to load more items after 10 items are scrolled. but I notice that loading starts before 10th item is visible means on 9th item.So how to stop this? – Atul Bhardwaj Oct 31 '12 at 06:43
  • @SubinSebastian This was exactly the case for me. I made a member variable in the `OnScrollListener` initalized with my `AsyncTask`. Then in `onScroll` I check if that task is equal to `AsyncTask.Status.FINISHED`. If it is then I make a new one, since you can't run a single instance of AsyncTask multiple times. Lastly, before executing, if the condition is met I check if the task is not equal to `Status.RUNNING`. This seems to work on first testing. – theblang Nov 19 '13 at 19:28
  • I have Webservices data with more than 80 list items how can i implement load more while onscroll listener android please any one can provide sample – Harsha Sep 10 '14 at 09:16
  • One recommendation: Add padding (of 10/15) where the comments mention for loadMore. There is just no point in showing a loader to your user when you can detect it early and prefetch it. – Shubham Chaudhary Apr 02 '16 at 10:52
93

Just wanted to contribute a solution that I used for my app.

It is also based on the OnScrollListener interface, but I found it to have a much better scrolling performance on low-end devices, since none of the visible/total count calculations are carried out during the scroll operations.

  1. Let your ListFragment or ListActivity implement OnScrollListener
  2. Add the following methods to that class:

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        //leave this empty
    }
    
    @Override
    public void onScrollStateChanged(AbsListView listView, int scrollState) {
        if (scrollState == SCROLL_STATE_IDLE) {
            if (listView.getLastVisiblePosition() >= listView.getCount() - 1 - threshold) {
                currentPage++;
                //load more list items:
                loadElements(currentPage);
            }
        }
    }
    

    where currentPage is the page of your datasource that should be added to your list, and threshold is the number of list items (counted from the end) that should, if visible, trigger the loading process. If you set threshold to 0, for instance, the user has to scroll to the very end of the list in order to load more items.

  3. (optional) As you can see, the "load-more check" is only called when the user stops scrolling. To improve usability, you may inflate and add a loading indicator to the end of the list via listView.addFooterView(yourFooterView). One example for such a footer view:

    <?xml version="1.0" encoding="utf-8"?>
    
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/footer_layout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:padding="10dp" >
    
        <ProgressBar
            android:id="@+id/progressBar1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_gravity="center_vertical" />
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@+id/progressBar1"
            android:padding="5dp"
            android:text="@string/loading_text" />
    
    </RelativeLayout>
    
  4. (optional) Finally, remove that loading indicator by calling listView.removeFooterView(yourFooterView) if there are no more items or pages.

Richard Le Mesurier
  • 27,993
  • 19
  • 127
  • 242
saschoar
  • 7,772
  • 5
  • 40
  • 44
  • I tried this but it doesn't work for ListFragment. How can it be used it in ListFragment. – zeeshan Jul 13 '13 at 15:35
  • Well, I use it with `ListFragment` as well without any problems -- just follow each of the steps above and you'll be fine ;) Or could you provide any error messages? – saschoar Jul 14 '13 at 21:44
  • What you might have missed is explained in this SO answer: http://stackoverflow.com/a/6357957/1478093 - `getListView().setOnScrollListener(this)` – TBieniek Jul 24 '13 at 09:29
  • 7
    note: adding and removing footer on listview is problematic (footer doesn't show up if setAdapter is called before adding footer), I ended up modifying visibility of the footer view directly without removing it. – dvd Oct 20 '13 at 22:53
  • What has to be added in the loadElements(currentPage)??? I mean do i call my AsyncTask or a thread? – android_developer Nov 28 '13 at 08:19
  • `loadElements(currentPage)` is really just a placeholder for a method to load your next elements. If you get your elements from a network request, it should happen in an extra thread (otherwise you'll get a `NetworkOnMainThreadException`), so the answer is yes. But remember that adding the loaded elements to the adapter must happen on the main thread again (otherwise you'll get a `CalledFromWrongThreadException`). – saschoar Nov 28 '13 at 10:25
  • @saschoar3 does that mean I should have a new asynctask and call my first asynctask in its doinbackground() method??? – android_developer Nov 28 '13 at 14:15
  • Really want to see some bits of your code here (in a gist or so) -- But why don't you have a general AsyncTask that loads a specific page (specified by a parameter) of your data? Then you'd just have to call it with the next page number and can reuse the task for every page. – saschoar Nov 29 '13 at 10:44
  • @saschoar you can check my code here http://stackoverflow.com/questions/20266419/asyntask-with-endless-listview-scroll-in-android i have uploaded my code there please help!!! – android_developer Dec 02 '13 at 08:35
  • Like a breeze. Your answer helped me alot. Just wanted to add that if you are using listfragment then dont forget to call getListView().setOnScrollListener(listener) . And inplement the listener as mentioned in the answer – Prachur Mar 01 '14 at 16:24
  • From my understanding, the performance gain results from the fact that the evaluation here is only triggered when `scrollState == SCROLL_STATE_IDLE`, i.e. when the scrolling has finished. I think that's a reasonable trade-off between speed and user comfort. – saschoar Nov 10 '14 at 15:04
20

You can detect end of the list with help of onScrollListener, working code is presented below:

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    if (view.getAdapter() != null && ((firstVisibleItem + visibleItemCount) >= totalItemCount) && totalItemCount != mPrevTotalItemCount) {
        Log.v(TAG, "onListEnd, extending list");
        mPrevTotalItemCount = totalItemCount;
        mAdapter.addMoreData();
    }
}

Another way to do that (inside adapter) is as following:

    public View getView(int pos, View v, ViewGroup p) {
            if(pos==getCount()-1){
                addMoreData(); //should be asynctask or thread
            }
            return view;
    }

Be aware that this method will be called many times, so you need to add another condition to block multiple calls of addMoreData().

When you add all elements to the list, please call notifyDataSetChanged() inside yours adapter to update the View (it should be run on UI thread - runOnUiThread)

Dariusz Bacinski
  • 7,471
  • 7
  • 33
  • 43
  • 7
    It is bad practice to do other stuff than returning a view in `getView`. For example, you aren't guaranteed that `pos` is viewed by the user. It might simply be ListView measuring stuff. – Thomas Ahle Aug 13 '11 at 10:10
  • I want to load more items after 10 items are scrolled. but I notice that loading starts before 10th item is visible means on 9th item.So how to stop this? – Atul Bhardwaj Oct 31 '12 at 06:44
4

At Ognyan Bankov GitHub i found a simple and working solution!

It makes use of the Volley HTTP library that makes networking for Android apps easier and most importantly, faster. Volley is available through the open AOSP repository.

The given code demonstrates:

  1. ListView which is populated by HTTP paginated requests.
  2. Usage of NetworkImageView.
  3. "Endless" ListView pagination with read-ahead.

For future consistence i forked Bankov's repo.

Community
  • 1
  • 1
oikonomopo
  • 3,707
  • 7
  • 43
  • 65
2

Here is a solution that also makes it easy to show a loading view in the end of the ListView while it's loading.

You can see the classes here:

https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/helper/ListViewWithLoadingIndicatorHelper.java - Helper to make it possible to use the features without extending from SimpleListViewWithLoadingIndicator.

https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/listener/EndlessScrollListener.java - Listener that starts loading data when the user is about to reach the bottom of the ListView.

https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/view/SimpleListViewWithLoadingIndicator.java - The EndlessListView. You can use this class directly or extend from it.

Fernando Camargo
  • 2,885
  • 3
  • 28
  • 46
1

May be a little late but the following solution happened very useful in my case. In a way all you need to do is add to your ListView a Footer and create for it addOnLayoutChangeListener.

http://developer.android.com/reference/android/widget/ListView.html#addFooterView(android.view.View)

For example:

ListView listView1 = (ListView) v.findViewById(R.id.dialogsList); // Your listView
View loadMoreView = getActivity().getLayoutInflater().inflate(R.layout.list_load_more, null); // Getting your layout of FooterView, which will always be at the bottom of your listview. E.g. you may place on it the ProgressBar or leave it empty-layout.
listView1.addFooterView(loadMoreView); // Adding your View to your listview 

...

loadMoreView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
         Log.d("Hey!", "Your list has reached bottom");
    }
});

This event fires once when a footer becomes visible and works like a charm.

tehcpu
  • 872
  • 1
  • 12
  • 19
0

I've been working in another solution very similar to that, but, I am using a footerView to give the possibility to the user download more elements clicking the footerView, I am using a "menu" which is shown above the ListView and in the bottom of the parent view, this "menu" hides the bottom of the ListView, so, when the listView is scrolling the menu disappear and when scroll state is idle, the menu appear again, but when the user scrolls to the end of the listView, I "ask" to know if the footerView is shown in that case, the menu doesn't appear and the user can see the footerView to load more content. Here the code:

Regards.

listView.setOnScrollListener(new OnScrollListener() {

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // TODO Auto-generated method stub
        if(scrollState == SCROLL_STATE_IDLE) {
            if(footerView.isShown()) {
                bottomView.setVisibility(LinearLayout.INVISIBLE);
            } else {
                bottomView.setVisibility(LinearLayout.VISIBLE);
            } else {
                bottomView.setVisibility(LinearLayout.INVISIBLE);
            }
        }
    }

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

    }
});
V-rund Puro-hit
  • 5,270
  • 8
  • 27
  • 48
pabloverd
  • 418
  • 5
  • 6
0

The key of this problem is to detect the load-more event, start an async request for data and then update the list. Also an adapter with loading indicator and other decorators is needed. In fact, the problem is very complicated in some corner cases. Just a OnScrollListener implementation is not enough, because sometimes the items do not fill the screen.

I have written a personal package which support endless list for RecyclerView, and also provide a async loader implementation AutoPagerFragment which makes it very easy to get data from a multi-page source. It can load any page you want into a RecyclerView on a custom event, not only the next page.

Here is the address: https://github.com/SphiaTower/AutoPagerRecyclerManager

ProtossShuttle
  • 1,463
  • 14
  • 34
0

Best solution so far that I have seen is in FastAdapter library for recycler views. It has a EndlessRecyclerOnScrollListener.

Here is an example usage: EndlessScrollListActivity

Once I used it for endless scrolling list I have realised that the setup is a very robust. I'd definitely recommend it.

Shubham Chaudhary
  • 36,933
  • 9
  • 67
  • 78
0

I know its an old question and the Android world has mostly moved on to RecyclerViews, but for anyone interested, you may find this library very interesting.

It uses the BaseAdapter used with the ListView to detect when the list has been scrolled to the last item or when it is being scrolled away from the last item.

It comes with an example project(barely 100 lines of Activity code) that can be used to quickly understand how it works.

Simple usage:

class Boy{

private String name;
private double height;
private int age;
//Other code

}

An adapter to hold Boy objects would look like:


public class BoysAdapter extends EndlessAdapter<Boy>{




        ViewHolder holder = null;


        if (convertView == null) {
            LayoutInflater inflater = LayoutInflater.from(parent
                    .getContext());

            holder = new ViewHolder();

            convertView = inflater.inflate(
                    R.layout.list_cell, parent, false);


            holder.nameView = convertView.findViewById(R.id.cell);

            // minimize the default image.
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        Boy boy = getItem(position);

        try {
            holder.nameView.setText(boy.getName());

            ///Other data rendering codes.

        } catch (Exception e) {
            e.printStackTrace();
        }

        return super.getView(position,convertView,parent);

}

Notice how the BoysAdapter's getView method returns a call to the EndlessAdapter superclass's getView method. This is 100% essential.

Now to create the adapter, do:

   adapter = new ModelAdapter() {
            @Override
            public void onScrollToBottom(int bottomIndex, boolean moreItemsCouldBeAvailable) {

                if (moreItemsCouldBeAvailable) { 
                    makeYourServerCallForMoreItems();
                } else {
                    if (loadMore.getVisibility() != View.VISIBLE) {
                        loadMore.setVisibility(View.VISIBLE);
                    }
                }
            }

            @Override
            public void onScrollAwayFromBottom(int currentIndex) { 
                loadMore.setVisibility(View.GONE);
            }

            @Override
            public void onFinishedLoading(boolean moreItemsReceived) { 
                if (!moreItemsReceived) {
                    loadMore.setVisibility(View.VISIBLE);
                }
            }
        };

The loadMore item is a button or other ui element that may be clicked to fetch more data from the url. When placed as described in the code, the adapter knows exactly when to show that button and when to disable it. Just create the button in your xml and place it as shown in the adapter code above.

Enjoy.

gbenroscience
  • 833
  • 1
  • 8
  • 27