40

Is it possible to redraw a single row in a ListView? I have a ListView with rows that are LinearLayouts. I listen to a preference change and sometimes I need to change just one View inside the LinearLayout of a single row. Is there a way to make it redraw that row without calling listview.notifyDatasetChanged()?

I've tried calling view.invalidate() on the view (inside the LinearLayout) but it doesn't redraw the row.

Sufian
  • 6,405
  • 16
  • 66
  • 120
Falmarri
  • 47,727
  • 41
  • 151
  • 191
  • 1
    "Is there a way to make it redraw that row without calling listview.notifydatasetchanged()?" -- just find it as a child of the `ViewGroup` (in this case, `ListView`) and modify it. "I've tried calling view.invalidate() on the view (inside the linearlayout) but it doesn't redraw the row." -- are you sure you have the proper row? You should not even need to call `invalidate()`, AFAIK. – CommonsWare Nov 02 '10 at 08:38
  • 1
    I definitely have the right row, and the right view. I think my problem is my preference listener is being garbage collected. I changed it so I call notifyDataSetChanged() in my activity's onResume(), which solves the problem I was trying to fix. – Falmarri Nov 02 '10 at 09:31

4 Answers4

108

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.

Sufian
  • 6,405
  • 16
  • 66
  • 120
GriffonRL
  • 2,278
  • 2
  • 18
  • 13
  • Could you please add link to the Google I/O session itself? I want to check whole session. – uthark Apr 08 '13 at 20:20
  • +1 thx for this, its interesting - im presuming it works as your passing the current view in as the convertView arg and it will have its view elements reset - very nice – Dori Aug 19 '13 at 10:19
  • Shouldn't here be i – Paweł Brewczynski Nov 19 '14 at 07:59
  • I am facing same issue. But list.getChildAt(position) return child based on indexing of what displaying on scree. I have mention question http://stackoverflow.com/q/29429704/2624806. any suggestion! – CoDe Apr 03 '15 at 09:41
  • Hi, I have used this code ((TextView)view.findViewById(R.id.myid)).setText("some new text"); and it worked.But when I scrolled down the listview and comes back the text will be old one not the updated text. – Ranjith Sep 21 '15 at 11:22
  • Your answer is working perfectly for me. But screen refreshes and moving to top of screen? Can you please tell me how to solve this problem? – Sai's Stack Oct 14 '15 at 11:47
  • 1
    @ Ranjith That's because, when you scrolling up and down, the adapter's `getView()` method gets called, and, if you haven't changed your data for that row, it will redraw with the "old" one. – Simone Leoni Oct 19 '15 at 13:29
6

The view.invalidate() didn't work for me. But this works like a charm:

supose you are updating the position "position" in the row. The if avoid weird redraws stuffs, like text dancing when you are updating a row that are not in the screen.

            if (position >= listView.getFirstVisiblePosition()
                && position <= listView.getLastVisiblePosition()) {

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            listView.invalidateViews();
                        }
                    });
            }
Felipe
  • 16,649
  • 11
  • 68
  • 92
3

Romain Guy answered to this question at "Google IO 2010 The world of ListView" Video at the exact time where this question was asked : http://www.youtube.com/watch?feature=player_detailpage&v=wDBM6wVEO70#t=3149s

So according to Romain Guy, that should work, and I think we can trust him.

The reason why your view is not redrawn is a bit mysterious. Maybe try disabling all cache options available in listView

setAnimationCacheEnabled(false);
setScrollingCacheEnabled(false);
setChildrenDrawingCacheEnabled(false);
setChildrenDrawnWithCacheEnabled(false);
jptsetung
  • 9,064
  • 3
  • 43
  • 55
  • 1
    This is not what he said. He said that the method getView will still be called but when generating the ui the engine can see that the items data has not changed, and is then reusing them. – Morten Holmgaard Jan 22 '14 at 08:45
0

Put some tag on the row that you want to update. When you want to redraw that specific view or row then do

ListView lv = (ListView)findViewById(....)

and then

View v = lv.findViewWithTag(tagobject);

and then you do whatever you want with the view.

Storm
  • 684
  • 9
  • 20
user590849
  • 11,655
  • 27
  • 84
  • 125