Android ListView Refresh Single Row

62,649

Solution 1

One option is to manipulate the ListView directly. First check if the index of the updated row is between getFirstVisiblePosition() and getLastVisiblePosition(), these two give you the first and last positions in the adapter that are visible on the screen. Then you can get the row View with getChildAt(int index) and change it.

Solution 2

As Romain Guy explained a while back during the Google I/O session, the most efficient way to only update one view in a list view is something like the following (this one update the whole view data):

ListView list = getListView();
int start = list.getFirstVisiblePosition();
for(int i=start, j=list.getLastVisiblePosition();i<=j;i++)
    if(target==list.getItemAtPosition(i)){
        View view = list.getChildAt(i-start);
        list.getAdapter().getView(i, view, list);
        break;
    }

Assuming target is one item of the adapter.

This code retrieve the ListView, then browse the currently shown views, compare the target item you are looking for with each displayed view items, and if your target is among those, get the enclosing view and execute the adapter getView on that view to refresh the display.

As a side note invalidate doesn't work like some people expect and will not refresh the view like getView does, notifyDataSetChanged will rebuild the whole list and end up calling getview for every displayed items and invalidateViews will also affect a bunch.

One last thing, one can also get extra performance if he only needs to change a child of a row view and not the whole row like getView does. In that case, the following code can replace list.getAdapter().getView(i, view, list); (example to change a TextView text):

((TextView)view.findViewById(R.id.myid)).setText("some new text");

In code we trust.

Solution 3

This simpler method works well for me, and you only need to know the position index to get ahold of the view:

// mListView is an instance variable

private void updateItemAtPosition(int position) {
    int visiblePosition = mListView.getFirstVisiblePosition();
    View view = mListView.getChildAt(position - visiblePosition);
    mListView.getAdapter().getView(position, view, mListView);
}

Solution 4

The following code worked for me. Note when calling GetChild() you have to offset by the first item in the list since its relative to that.

int iFirst = getFirstVisiblePosition();
int iLast = getLastVisiblePosition();

if ( indexToChange >= numberOfRowsInSection() ) {
    Log.i( "MyApp", "Invalid index. Row Count = " + numberOfRowsInSection() );
}
else {      
    if ( ( index >= iFirst ) && ( index <= iLast ) ) {
        // get the view at the position being updated - need to adjust index based on first in the list
        View vw = getChildAt( sysvar_index - iFirst );
        if ( null != vw ) {
            // get the text view for the view
            TextView tv = (TextView) vw.findViewById(com.android.myapp.R.id.scrollingListRowTextView );
            if ( tv != null ) {
                // update the text, invalidation seems to be automatic
                tv.setText( "Item = " + myAppGetItem( index ) + ". Index = " + index + ". First = " + iFirst + ". Last = " + iLast );
            }
        }
    }
}  

Solution 5

There is another much more efficient thing you can do, if it fits your use-case that is.

If you are changing the state and can somehow call the proper (by knowing the position) mListView.getAdapter().getView() it will the most efficient of all.

I can demonstrate a really easy way to do it, by creating an anonymous inner class in my ListAdapter.getView() class. In this example I have a TextView showing a text "new" and that view is set to GONE when the list item is clicked:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // assign the view we are converting to a local variable
    View view = convertView;

    Object quotation = getItem(position);
    // first check to see if the view is null. if so, we have to inflate it.
    if (view == null)
        view = mInflater.inflate(R.layout.list_item_quotation, parent, false);

    final TextView newTextView = (TextView) view.findViewById(R.id.newTextView);

    view.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (mCallbacks != null)
                mCallbacks.onItemSelected(quotation.id);
            if (!quotation.isRead()) {
                servicesSingleton.setQuotationStatusReadRequest(quotation.id);
                quotation.setStatusRead();
                newTextView.setVisibility(View.GONE);
            }
        }
    });

    if(quotation.isRead())
        newTextView.setVisibility(View.GONE);
    else
        newTextView.setVisibility(View.VISIBLE);

    return view;
}

The framework automatically uses the correct position and you do have to worry about fetching it again before calling getView.

Share:
62,649

Related videos on Youtube

Thizzer
Author by

Thizzer

Software developer at heart. Interested in all things Tech.

Updated on July 05, 2022

Comments

  • Thizzer
    Thizzer almost 2 years

    After I have gotten the data for a single row of a ListView, I want to update that single row.

    Currently I am using notifyDataSetChanged(); but that makes the View react very slowly. Are there any other solutions?

  • Nacho Coloma
    Nacho Coloma almost 11 years
    You should probably just link your other answer stackoverflow.com/a/9987616/1026132 instead of copying and pasting here. This would help to reduce the amount of duplicate content in SO.
  • hadi
    hadi over 10 years
    Thanks. This would work if I am editing the current layout of the child. But what if I want to change the layout altogether (by inflating a different xml)? If I call newView(...) on the adapter, it gives me a different view which however is not put in the listView.
  • Zyoo
    Zyoo over 10 years
    how to implement this with different view type?
  • Rutger
    Rutger over 10 years
    Works like a charm, +1
  • Jose_GD
    Jose_GD over 9 years
    @hadi you should take a look at this answer: stackoverflow.com/a/11568271/858626
  • Pang
    Pang almost 9 years
    Link to "Google I/O session" in answer is dead.
  • Rakesh Patil
    Rakesh Patil about 8 years
    seems awesome! I'm gonna try this today
  • QuinnChen
    QuinnChen almost 8 years
    maybe sometimes you need to use "list.getAdapter().getView(i - list.getHeaderviewCount, view, list);" instead of "list.getAdapter().getView(i, view, list);"
  • Thizzer
    Thizzer over 7 years
    The question was how to refresh a single row in the ListView at any given moment. onItemClick is only called when the users click an item and so does not suffice
  • Harsha
    Harsha over 7 years
    i will send that updated values to detailview another page if user do changes on that details page how to update back to list please
  • Someone Somewhere
    Someone Somewhere almost 6 years
    in this example sysvar_index means the position of the row you want to update