Q:

Dynamic views in listview

I'm creating kind of music player which has a listview with songs (having progress bar near currently playing song)
What is important is that I have an Item with views which can be changed from outside (in some handler)
 public View getView(int i, View convertView, ViewGroup viewGroup) {
   RelativeLayout result;

        if (convertView == null) {
            result = (RelativeLayout) inflater.inflate(R.layout.list_item, viewGroup, false);
        } else {
            result = (RelativeLayout) convertView;
        }

        ...
        ProgressBar progressBar = result.findViewById(R.id.progressBar)
        ...

        if (i == currentSong) {
           // saving to instance variable
           this.currentlyPlayingProgressBar = progressBar;
        } else {
           progressBar.setVisibility(View.GONE);
        }
        ...       
        return result;
}

(Code was changed to focus on my problem)
Btw currentSong can be changed from outside, adapter.notifyDataSetChanged() is being called in this case.
It seems like I'm using listView incorrectly, but I don't know the better way.
The main problem is that I need to have links to views to change them.
And the only way where I can get them is in getView method which reuses those view in a way only google developers can explain=(

First problem
This is all happening in Fragment which is just a part of a viewPager. when user scrolls of this fragment and then scrolls back then getView method is being called with some strange objects inside.. And I override currentlyPlayingProgressBar with this invalid value. Which causes the freeze of my statusbar. (it starts updating wrong view)
And I have no idea which view is it..

Second problem
I am reusing list items and this means that when user scrolls list view down - then sometimes he gets actually the same list item with the same progressBar.
This progressBar must be invisible but it's not (I think it's all because of my usage of currentlyPlayingProgressBar from outside)
Thanks in advance.

A:

You can do this in two ways:
1) notifyDataSetChanged, which just resets entire ListView and assigns all visible list items again to views. notifyDataSetChanged is very expensive, since it makes entire list and view hierarchy to be recreated, layouts are measured, etc, which is slow. For frequent update of single list item, such as progress bar change, this is overkill.
2) Find view of particular listview item, and update only that one. We'll focus on this approach.
First you need to somehow identify which list view contains which list item. Common approach is to use View.setTag in your Adapter.getView, where setTag parameter is Object of your choice, may be same item as you return for Adapter.getItem.
So later you know which ListView child view has which item assigned.
Assuming you want to update particular item displayed in ListView, you have to iterate through ListView child views to find which view displays your item:
Object myItem = ...;
for(int i = list.getChildCount(); --i>=0; ){
     View v = list.getChildAt(i);
     Object tag = v.getTag();
     if(tag==myItem) {
        // found, rebind this item
        bindItemToView(myItem, v);
        break;
     }
  }

You must expect that ListView currently may not display your list item (is scrolled away).
From code above you see that it calls bindItemToView, which is your function to bind item to list view. You'd probably call same function to setup the item view in Adapter.getView.
You may also optimize it further, assuming you want to update only ProgressBar, then don't call bindItemToView, but update only ProgressBar in your listitem view (findViewById, setup values).
Hint: you can make it even more optimal by using ViewHolder approach. Then setTag would not contain your item object, but your ViewHolder object.

