Question

I have a ListView with rows created with using specific layout. Each row have ProgressBar that is View.GONE by default. I need to make ProgressBar in ListView item visible when this element is clicked. The problem is that when i click the element ProgressBars became visible not only in the row i clicked but in other random rows too. Here is the code:

Creating ListView and setting OnItemClickListener:

private void buildTracklist(ArrayList<Audio> differences, ArrayList<Audio> vkAudioList) {
        ArrayList<Map<String, Object>> data = new ArrayList<Map<String, Object>>(vkAudioList.size());
        Map<String, Object> m;
        int i;
        for (i=0;i<vkAudioList.size();i++) {
            m = new HashMap<String, Object>();
            m.put(AUDIO_ARTIST, vkAudioList.get(i).artist);
            m.put(AUDIO_TITLE, vkAudioList.get(i).title);
            if (arrayListContainsAudio(differences, vkAudioList.get(i))) {
                m.put(AUDIO_DOWNLOADED, R.drawable.image_blank);
            } else {
                m.put(AUDIO_DOWNLOADED, R.drawable.image_downloaded);
            }
            data.add(m);
        }
        String[] from = {AUDIO_TITLE,AUDIO_ARTIST,AUDIO_DOWNLOADED};
        int[] to = {R.id.audioTitleTextView,R.id.audioArtistTextView,R.id.isDownloadedImageView};
        sAdapter = new SimpleAdapter(this, data, R.layout.tracklist_item, from, to);
        audioListView = (ListView) findViewById(R.id.audioListView);
        audioListView.setAdapter(sAdapter);

        audioListView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Log.i(TAG,"position="+position+" id="+id);

                view.findViewById(R.id.audioItemProgressBar).setVisibility(View.VISIBLE);
                //Here I'm passing progressBar further
            }
        });
    }

tracklistItem layout:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:id="@+id/audioTitleTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="2dp"
            android:gravity="top|left"
            android:text="Title"
            android:textSize="24dp" />

        <TextView
            android:id="@+id/audioArtistTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/audioTitleTextView"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="2dp"
            android:text="Artist"
            android:textSize="16sp" />

        <ProgressBar
            android:id="@+id/audioItemProgressBar"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_below="@+id/audioArtistTextView"
            android:visibility="gone" />

    </RelativeLayout>

    <ImageView
        android:id="@+id/isDownloadedImageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right"
        android:src="@drawable/downloaded_backgroundv10"
        android:visibility="visible" />

</FrameLayout>

I tried to get the listview item view with getChildAt() of getView() but the same problem occured. Thanks for help and sorry for my bad english.

Update: i found that progressBar i'm using changes parent item when i scroll ListView but i need it to be in one row no matter what part of ListView is currently on screen.

Was it helpful?

Solution

ListView, like all Adapter views, uses views recycling.

The problem is that when i click the element ProgressBars became visible not only in the row i clicked but in other random rows too.

I bet it happens when you scroll the view. It works like this: you show the progress bar, then scroll the list. The item with the progress bar scrolls outside of the visible area of the screen. This is the moment the view gets recycled. The system detaches it from listview and attaches again to show another row of your list. This is why you see the progress bar again.

What to do?

You should turn the visibility of the progress bar inside the getView() method of your custom adapter (which can be based on BaseAdapter). Whether it's turned on or not should depend on some flag.

Read this to find out more about views recycling in adapter views.


i need to change visibility of progressbar and it's progress when i click on the element, not when i create listview.

Just define a method in your adapter which will change the value of the flag. Then you can call notifyDataSetChanged.

The important thing to remember when dealing with adapter views is that you change the underlying data, not the view, because you may have several thousand items in your lists, but there are only a few views to deal with the visible rows.

OTHER TIPS

You code seem pretty good, but I guess you should use baseAdapter instead of SimpleAdapter. If you are going to visible or hide the view on each of your row, it is recommend you to use baseAdapter.

Here's sample of baseAdapter. First, create a new class called MyBaseAdapter.

public class MyBaseAdapter  extends BaseAdapter{

    private Activity mContext;
    private ArrayList<Map<String, Object>> mList;

    MyBaseAdapter(Activity context, ArrayList<Map<String, Object>> list){
        this.mContext = context;    
        mList = list;
    }

    @Override
    public int getCount() {
        return mList.size();        
    }

    @Override
    public Map<String, Object> getItem(int position) {      
        return mList.get(position);
    }

    @Override
    public long getItemId(int position) {       
        return position;
    }


    private static class ViewHolder {
        public final TextView audioTitleTextView, autioArtistTextView;
        public final ImageView isDownloadedImageView;
        public final FrameLayout fl;


        public ViewHolder(TextView audioArtist, TextView audioTitle,ImageView isDownloaded, FrameLayout fl){
            this.autioArtistTextView = audioArtist;
            this.audioTitleTextView = audioTitle;
            this.isDownloadedImageView = isDownloaded;
            this.fl = fl;
        }
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent){


        TextView audioTitleTextView,autioArtistTextView;
        FrameLayout fl;

        ImageView isDownloadedImageView;
        /*
         * If convertView is not null, tried reuse it
         * else create LayoutInflater to pack up the row view
         */
        if(convertView == null){            
            convertView = LayoutInflater.from(mContext).inflate(R.layout.tracklistItem , parent,false);
            audioTitleTextView = (TextView)convertView.findViewById(R.id.audioTitleTextView);
            autioArtistTextView = (TextView)convertView.findViewById(R.id.audioArtistTextView);     
            isDownloadedImageView = (ImageView)convertView.findViewById(R.id.isDownloadedImageView);
            fl = (FrameLayout)convertView.findViewById(R.id.parentFrameLayout);
            convertView.setTag(new ViewHolder(autioArtistTextView,audioTitleTextView,isDownloadedImageView));


        } else{
            ViewHolder holder = (ViewHolder)convertView.getTag();
            audioTitleTextView = holder.audioTitleTextView;
            autioArtistTextView = holder.autioArtistTextView;
            isDownloadedImageView = holder.isDownloadedImageView;
            fl = holder.fl;
        }


        @SuppressWarnings("unchecked")
        Map<String, Object> rowItem = getItem(position);


        audioTitleTextView.setText(rowItem.get(AUDIO_TITLE));   
        autioArtistTextView.setText(rowItem.get(AUDIO_ARTIST));

        // Change setImageResource to your own image resources
        isDownloadedImageView.setImageResource(rowItem.get(AUDIO_DOWNLOADED));

        fl.setOnClickListener( new View.OnClickListener(){
             @Override
             public void onClick(View view){
                  // hide your progress bar here
             }
        });

        return convertView;
    }   

Then, change your SimpleAdapter to following

MyBaseAdapter sAdapter = new MyBaseAdapter(this, data);
audioListView = (ListView) findViewById(R.id.audioListView);
audioListView.setAdapter(sAdapter);

Edit: Add FrameLayout to viewHolder and set FrameLayout OnClickListener

To understand why this issue is happening you must first understand how ListView works. There is lot available on internet about it so I will be brief here. If there are 50 items in your adapter then it doesn't mean that ListView will create 50 rows for you. Instead it will create 5 may be 10 may be more depending on the size of your screen but not 50 ! and then when you will scroll ListView, this is what will happen. The row that scrolled out of the screen will be reused. getView() will be called and you will have to set each property again otherwise the old one will be re used. In your case visibility value for that row is re used.

Here is how to solve it:

Store visibility data in your data ArrayList and override getView() method of your adapter and then handle both cases like this:

if(data.isProressVisible){
   audioItemProgressBarView.setVisibility(View.VISIBLE);
}else{
   audioItemProgressBarView.setVisibility(View.INVISIBLE);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top