views:

93

answers:

2

I have a listview with 200 items. I use a custom view for each row. There is a code that takes some time to calculate, and because of this the list hangs out on scrolling and loads in slow (2-3sec).

I have subclassed SimpleCursorAdapter, and using Filterable and SectionIndexer.

I have in mind to show initially the name of the record, and put in a thread the calculation, and will show up later when it's done.

How do I push back some work, and later update the listview to include the calculated data? This should show up on fly without user interaction.

+1  A: 

Hmm, interesting question. Here are some quick thoughts:

1) Put the values which can be retrieved directly, i.e. the name of the record as you put it, straight into the list. Leave default values, such as "Loading...", for everything that might take a while to compute.

2) Create a second thread to do the calculations, but to make sure it doesn't calculate everything use the ListView's onScrollStateChanged listener. This lets you only do the background work when the list is either idle, or being scrolled slowly by touch-scroll. In other words, don't bother when it's in 'fling' mode since the rows would be long past before the calculations finished.

3) I'm not entirely sure how you can enqueue the loading stage in the thread. You only want to have one loading thread - opening a new thread for each row would be rather messy and also pointless as the phones aren't multicore - but that means the thread has to have a queueing system. What I mean is that the thread has to be aware of what rows are waiting for calculation, but I haven't thought of how you'd do that yet. However, this would also have to be tied to which rows are visible. There's no point in the thread calculating information for rows long past in the list.

4) Whenever the loading thread finishes a particular row, you could call notifyDatasetChanged() on the list adapter. This might get a bit heavy if the ListView tries to refresh its data for every row rather than just those visible, but you'd have to experiment to see.

I hope these ideas help a bit!


In response to the comment: Assuming you're asking when a row is visible, you can do that by implementing ListView.OnScrollListener. I took inspiration from ApiDemos List13, but here are the relevant bits of code:

public class YourClass extends ListActivity implements ListView.OnScrollListener {
    public void onCreate(Bundle sIS){
        (...)
        // Note, you may have to write your own list adapter, extending BaseAdapter
        // See List13 in Api Demos for an example if you're not sure how to do that
        setListAdapter(yourImageAdapter); 
        getListView().setOnScrollListener(this);
    }

    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
            int totalItemCount) {
        Log.d(LOG_TAG, "onScroll, firstVisibleItem = " + firstVisibleItem +
                ", visibleItemCount = " + visibleItemCount
                + ", totalItemCount = " + totalItemCount);
    }

    public void onScrollStateChanged(AbsListView view, int scrollState) {
        Log.d(LOG_TAG, "onScrollStateChanged called, scrollState = " + scrollState);
        switch (scrollState) {
        case OnScrollListener.SCROLL_STATE_IDLE:
            int first = view.getFirstVisiblePosition();
            int count = view.getChildCount();

            Log.d(LOG_TAG, "first = " + first + ", count = " + count);

            break;
        case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
            //Scrolling with the finger touching the screen
            break;

        case OnScrollListener.SCROLL_STATE_FLING:
            // User 'threw' the list up or down and it's scrolling rapidly
            break;
        }
    }
}

That tells you everything about your list: whether it's scrolling, what's visible, etc. If you use that with the loading thread to only do the background work for the visible rows, that should help reduce unnecessary overhead.

Another point about BaseAdapter: I haven't tried using any other form of list adapter so maybe it's the same with all of them, but one very useful thing with BaseAdapter is that it lets you handle the 'inflation' of each row individually. Every time the list is about to display a new row, getView is called. You can put custom code in there to do things like put "Loading..." if the information hasn't been calculated yet, or actually display it if it has.

By the way, what are you actually calculating in this list that takes so long?

Steve H
How can I detect when a row is painted?
Pentium10
I've edited my answer to provide more information.
Steve H
I have a contact list, I calculate upcoming birthday of the contact and it's age (display age eg: 21 in 7 months)
Pentium10
+2  A: 

Since android the Android UI is singlethreaded it will be blocked whenever you perform a long operation.

You could do regular java threading and have a Handler() in the main UI class that you can feed back information into. But..

The simplest way would be to create an AsyncTask(). They are java threads with a slight change. They can perform a UI operation before/after the main calculation. So for example:

 private class AndroidThread extends AsyncTask {
      protected Object doInBackground(Object... params) {
           return result;
      }

      protected void onPostExecute(Object result) {
           // do things here with the result of the doInBackground function
           // Like: "UiElement"."performTask(result)"
      }
 }

For more information on AsyncTask see this

Robert Foss