views:

2654

answers:

3

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

+1  A: 

Doesn't a ListAdaptor do exactly what you want? It represents a virtual list of items that only need to be loaded in as they are needed.

hacken
ListAdapter needs to know the full count up front, and most such adapters are set up to wrap the full content (e.g., CursorAdapter wraps a Cursor, which holds the full result set of a query). My guess is that Isaac is interested in putting an Android front-end on a Web service API that uses paging, so you don't necessarily know how many there are up front and need to do an HTTP fetch each time to get a new batch.
CommonsWare
I think the list adaptor can change it's size (or data set). So when it is asked for the last item it triggers off a fetch. Obviously from a UI point of view this is bad. You should be trying to hide the latency from the user by fetching before the user gets to the end.
hacken
This was my first idea, but it does seem messy - a ListView will sometimes request the last item much before it is needed.
Isaac Waller
+12  A: 

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
Thanks, this works perfectly.
Isaac Waller
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
@Matthias you are absolutely right, I modified the code accordingly. Thanks for the feedback!
Josef
cool, now it looks pretty much like the solution we use -- thanks :-)
Matthias
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
+2  A: 

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)

darbat
Edit: added getView() method
darbat