I've got a ListView, each of item of which contains a ToggleButton. After I toggle it and then scroll up or down, the ListView is recycling the Views and so some of the others are mirroring the checked state of the ToggleButton. I don't want this. How can I prevent it?

Solution 1

Add this two methods to your Adapter.

@Override

public int getViewTypeCount() {                 

    return getCount();
}

@Override
public int getItemViewType(int position) {

    return position;
}

Solution 2

Android recycles list items for performance purposes. It is highly recommended to reuse them if you want your ListView to scroll smoothly.

For each list item the getView function of your adapter is called. There, is where you have to assign the values for the item the ListView is asking for.

Have a look at this example:

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
    ViewHolder holder = null;

    if ( convertView == null )
    {
        /* There is no view at this position, we create a new one. 
           In this case by inflating an xml layout */
        convertView = mInflater.inflate(R.layout.listview_item, null);  
        holder = new ViewHolder();
        holder.toggleOk = (ToggleButton) convertView.findViewById( R.id.togOk );
        convertView.setTag (holder);
    }
    else
    {
        /* We recycle a View that already exists */
        holder = (ViewHolder) convertView.getTag ();
    }

    // Once we have a reference to the View we are returning, we set its values.

    // Here is where you should set the ToggleButton value for this item!!!

    holder.toggleOk.setChecked( mToggles.get( position ) );

    return convertView;
}

Notice that ViewHolder is a static class we use to recycle that view. Its properties are the views your list item has. It is declared in your adapter.

static class ViewHolder{
    ToggleButton toggleOk;
}

mToggles is declared as a private property in your adapter and set with a public method like this:

public void setToggleList( ArrayList<Boolean> list ){
        this.mToggles = list;
        notifyDataSetChanged();
    }

Have a look at other custom ListView examples for more information.

Hope it helps.

Solution 3

You could use a HashMap to save your buttons state:

 private Map<Integer,Boolean> listMapBoolean = new HashMap<Integer,Boolean>();


 toggleButton.setOnCheckedChangeListener(new OnCheckedChangeListener() {
                    @Override
                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                        if (isChecked) {
                            listMapBoolean.put(position, true);
                        } else {
                            listMapBoolean.put(position, false);
                        }
                    }
                });

and after inflating the view you read the HashMap to see if it was checked or not:

 for (Entry<Integer, Boolean> entry : listMapBoolean.entrySet()) {
                if (entry.getKey().equals(i)) {
                    if(entry.getValue()) {
                        System.out.println("ToggleButton is checked!");
                    } else {
                        System.out.println("ToggleButton is not checked!");
                    }
                }
            }

Not sure if it helps in your way. I had also problems with recycling my EditText in my ListView.

Solution 4

This would make it so slow for large lists. But inside getView(), you can use:

if (listItemView == null || ((int)listItemView.getTag()!=position)) {
            listItemView = LayoutInflater.from(context).inflate(R.layout.edit_text_list_item,
                    parent, false);
}

listItemView.setTag(position);
// set inner Views data from ArrayList
...

The tag is an Object that is associated with the View. And you check whenever you recycle it if you can recycle it or not. This makes each list item be inflated and nothing will be recycled.

This also should prevent deleting text from EditText inside the ListView and also prevent images from being reordered or messed up if your ListView has images in it.

Solution 5

May be you should try creating your own list view with scroll view and a container that holds the children that are added to the container programatically. set the tag for identifying the child or you could use the order of the child for that