How can I update a single row in a ListView?

Viewed 149773

I have a ListView which displays news items. They contain an image, a title and some text. The image is loaded in a separate thread (with a queue and all) and when the image is downloaded, I now call notifyDataSetChanged() on the list adapter to update the image. This works, but getView() is getting called too frequently, since notifyDataSetChanged() calls getView() for all visible items. I want to update just the single item in the list. How would I do this?

Problems I have with my current approach are:

  1. Scrolling is slow
  2. I have a fade-in animation on the image which happens every time a single new image in the list is loaded.
11 Answers

The answers are clear and correct, I'll add an idea for CursorAdapter case here.

If youre subclassing CursorAdapter (or ResourceCursorAdapter, or SimpleCursorAdapter), then you get to either implement ViewBinder or override bindView() and newView() methods, these don't receive current list item index in arguments. Therefore, when some data arrives and you want to update relevant visible list items, how do you know their indices?

My workaround was to:

  • keep a list of all created list item views, add items to this list from newView()
  • when data arrives, iterate them and see which one needs updating--better than doing notifyDatasetChanged() and refreshing all of them

Due to view recycling the number of view references I'll need to store and iterate will be roughly equal the number of list items visible on screen.

I made up another solution, like RecyclyerView method void notifyItemChanged(int position), create CustomBaseAdapter class just like this:

public abstract class CustomBaseAdapter implements ListAdapter, SpinnerAdapter {
    private final CustomDataSetObservable mDataSetObservable = new CustomDataSetObservable();

    public boolean hasStableIds() {
        return false;
    }

    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }

    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }

    public void notifyItemChanged(int position) {
        mDataSetObservable.notifyItemChanged(position);
    }

    public void notifyDataSetInvalidated() {
        mDataSetObservable.notifyInvalidated();
    }

    public boolean areAllItemsEnabled() {
        return true;
    }

    public boolean isEnabled(int position) {
        return true;
    }

    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        return getView(position, convertView, parent);
    }

    public int getItemViewType(int position) {
        return 0;
    }

    public int getViewTypeCount() {
        return 1;
    }

    public boolean isEmpty() {
        return getCount() == 0;
    } {

    }
}

Don't forget to create a CustomDataSetObservable class too for mDataSetObservable variable in CustomAdapterClass, like this:

public class CustomDataSetObservable extends Observable<DataSetObserver> {

    public void notifyChanged() {
        synchronized(mObservers) {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

    public void notifyInvalidated() {
        synchronized (mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onInvalidated();
            }
        }
    }

    public void notifyItemChanged(int position) {
        synchronized(mObservers) {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            mObservers.get(position).onChanged();
        }
    }
}

on class CustomBaseAdapter there is a method notifyItemChanged(int position), and you can call that method when you want update a row wherever you want (from button click or anywhere you want call that method). And voila!, your single row will update instantly..

In addition to this solution (https://stackoverflow.com/a/3727813/5218712) just want to add that it should work only if listView.getChildCount() == yourDataList.size(); There could be additional view inside ListView.

Example of how the child elements are populated:  listView.mChildren array

Related