ListView: setItemChecked only works with standard ArrayAdapter - does NOT work when using customized ArrayAdapter?
Asked Answered
M

4

39

This is really weird.

When I use the standard ArrayAdapter for a ListView calling setItemChecked works OK

But when using a custom made ArrayAdapter it does not.

What would be the reason? Is this a bug? Or am I missing something?

public class Test_Activity extends Activity {

    /** Called when the activity is first created. */
    private List<Model> list;
    private ListView lView;

    public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    // Create an array of Strings, that will be put to our ListActivity
    setContentView(R.layout.main);
    lView = (ListView) findViewById(R.id.ListView01);
    lView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

    list = getModel();

    //with this adapter setItemChecked works OK
    lView.setAdapter(new ArrayAdapter<Model>(this,
        android.R.layout.simple_list_item_multiple_choice, list));


  //**************************
    //PROBLEM: with this adapter it does not check any items on the screen
    // ArrayAdapter<Model> adapter = new Test_Class1(this, list);
    // lView.setAdapter(adapter);



    }

    private List<Model> getModel() {
       List<Model> list = new ArrayList<Model>();
       list.add(get("0"));
       list.add(get("1"));
       list.add(get("2"));
       list.get(1).setSelected(true);
       Model m = list.get(1);
       list.add(get("3"));
       list.add(get("4"));
       list.add(get("5"));
       list.add(get("6"));
       list.add(get("7"));
       // Initially select one of the items
       return list;
    }

    private Model get(String s) {
       return new Model(s);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
       MenuInflater inflater = getMenuInflater();
       inflater.inflate(R.menu.results_screen_option_menu, menu);
       return true;
    }

    /**
     * @category OptionsMenu
     */
    @Override
 public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    case R.id.select_all: {

        int size = lView.getAdapter().getCount();

        for (int i = 0; i <= size; i++) {

            //************************** PROBLEM
        lView.setItemChecked(i, true); // selects the item only for standard ArrayAdapter

    Log.i("xxx", "looping " + i);
        }
    }
        return true;
    case R.id.select_none:
        return true;
    }
    return false;
    }
}

//------------------------------------------------------------

public class Test_Class1 extends ArrayAdapter<Model> {

    private final List<Model> list;
    private final Activity context;

 public Test_Class1(Activity context, List<Model> list) {
    super(context, R.layout.rowbuttonlayout2, list);
    this.context = context;
    this.list = list;
    }

  static class ViewHolder {
    protected TextView text;
    protected CheckBox checkbox;
    }

    @Override
 public View getView(int position, View convertView, ViewGroup parent) {
    View view = null;
    Log.i("xxx", "-> getView " + position);
    if (convertView == null) {
        LayoutInflater inflator = context.getLayoutInflater();
        view = inflator.inflate(R.layout.rowbuttonlayout, null);
        final ViewHolder viewHolder = new ViewHolder();
        viewHolder.text = (TextView) view.findViewById(R.id.label);
        viewHolder.checkbox = (CheckBox) view.findViewById(R.id.check);
        viewHolder.checkbox
            .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton buttonView,
                boolean isChecked) {
                Model element = (Model) viewHolder.checkbox
                    .getTag();
                Log.i("xxx", "-> onCheckedChanged");
                element.setSelected(buttonView.isChecked());
                Log.i("xxx", "<- onCheckedChanged");

            }
            });
        view.setTag(viewHolder);
        viewHolder.checkbox.setTag(list.get(position));
    } else {
        view = convertView;
        ((ViewHolder) view.getTag()).checkbox.setTag(list.get(position));
    }
    ViewHolder holder = (ViewHolder) view.getTag();
    holder.text.setText(list.get(position).getName());
    Log.i("xxx", "holder.checkbox.setChecked: " + position);

    holder.checkbox.setChecked(list.get(position).isSelected());
    Log.i("xxx", "<-  getView " + position);

    return view;
    }

}
Mantelet answered 3/12, 2011 at 17:49 Comment(0)
I
47

Your row layout needs to be Checkable for setItemChecked() to work, in which case Android will manage calling setChecked() on your Checkable as the user clicks on the row. You would not need to be setting up your own OnCheckedChangeListener.

For more, see:

Inevitable answered 3/12, 2011 at 18:32 Comment(5)
Unbelievable - why does this has to be so complicated and difficult to find out ? I also do iPhone programming, while up to now I really prefered Android, this is a real set-back. Thank's very much !Mantelet
I looked at the sample code in detail now and decided not to follow this solution. Instead I build an array of the CheckBoxes in my ArrayAdapter. With this at hand I can then easily do the kind of stuff I need. Thanks again very much for pointing me in the direction!Mantelet
updated link to marvinlabs tutorial blog.marvinlabs.com/2010/10/29/…Vestryman
Absolutely right. I have been working on Android apps for a couple of years and this is the first time I used CheckedTextView.Withhold
@Mantelet Yes, android documentation sucks big time. Plus that some things are implemented in a most complacated way. This thing has yet to mature.Giggle
E
12

For sure you have to set the choice mode for your ListView, for example:

list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

But if you're also using a custom layout for your list item (R.layout.drawing_list_item), then you have to make sure your layout implements the Checkable interface:

public class CheckableRelativeLayout extends RelativeLayout implements Checkable {
    private boolean isChecked = false;

    public CheckableRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public boolean isChecked() {
        return isChecked;
    }

    public void setChecked(boolean isChecked) {
        this.isChecked = isChecked;
        changeColor(isChecked);
    }

    public void toggle() {
        this.isChecked = !this.isChecked;
        changeColor(this.isChecked);
    }

    private void changeColor(boolean isChecked){
        if (isChecked) {
            setBackgroundColor(getResources().getColor(android.R.color.holo_blue_light));
        } else {
            setBackgroundColor(getResources().getColor(android.R.color.transparent));
        }
    }
}

Then your checkable layout will be defined as the following example:

<?xml version="1.0" encoding="utf-8"?>
<it.moondroid.banking.CheckableRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_item_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp" >

    <ImageView
        android:id="@+id/agenzie_item_icon"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        android:background="@android:color/darker_gray"
        android:focusable="false" />

    <TextView
        android:id="@+id/agenzie_item_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_toRightOf="@+id/agenzie_item_icon"
        android:focusable="false"
        android:paddingLeft="10dp"
        android:text="item"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:textColor="#FFFFFF" />

</it.moondroid.banking.CheckableRelativeLayout>
Embank answered 15/11, 2013 at 9:27 Comment(0)
A
1

Checkable is a learning curve I prefer not to take right now. You can set and unset the CheckBox manually in the Activities' OnItemClickListener. Maintain a boolean isChecked transient variable in the MyObject list in the ArrayAdapter<MyObject>.

public void onItemClick(AdapterView<?> av, View v, int position, long arg3) {
    final ListView listView = (ListView) findViewById(android.R.id.list);
    MyObject item = (MyObject) listView.getItemAtPosition(position);
    item.isChecked = !item.isChecked;
    if(item.isChecked)
        addItem(item);
    else
        removeItem(item);
}  

To account for users clicking the CheckBox itself; use the same addItem(item)/removeItem(item) logic in your custom array adapter's getView implementation, in the CheckBox's OnClickListener.

public View getView(int position, View convertView, ViewGroup viewGroup) {
        CheckBox cb = null;
        if (convertView == null) {
            if(inflater == null) //cache to avoid reconstructing for each view
            inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView =inflater.inflate(R.layout.list_item_text_right_checkmark, null);

            CheckBox cb = (CheckBox) convertView.findViewById(R.id.check);
            cb.setChecked((list.get(position).isChecked));
            cb.setTag(list.get(position);
            public void onClick(View v) {
                if(v instanceof CheckBox) {
                    CheckBox cb = (CheckBox) v;
                    if(cb.isChecked()) //verify boolean logic here!!!
                        activityRef.get().addItem(cb.getTag()); //WeakReference to activity
                    else
                        activityRef.get().removeItem(cb.getTag());

                }
            });
    } else cb = (CheckBox) convertView.findViewById(R.id.check);
    cb.setChecked((list.get(position).isChecked));
...
}

Calling CheckBox::setChecked() is the key here.
This seems to do the trick as long as your array adapter can assume the same activity, and your array adapter can assume the same list item xml definition in res/layout/.

checkable list item XML:

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

  <TextView
      android:id="@+id/text"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_toLeftOf="@+id/check"
      android:gravity="center_vertical"
      android:paddingLeft="8dp"
      android:textAppearance="?android:attr/textAppearanceMedium" />

  <CheckBox
    android:id="@+id/check"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentRight="true"
    android:focusable="false"/>
</RelativeLayout>

If you need to check some of your CheckBoxs initially, just set your isChecked members in your MyObject prior to inflating.

Adapt answered 11/2, 2013 at 19:12 Comment(0)
S
0

None of the responses worked for me. My problem was that only the initial call to ExpandableListView.setItemChecked from OnCreate/OnStart/OnPause did not work as if the view was not fully initialized yet. In order to solve this, I had to do the following:

_expandableListView.Post(() =>
{
    _expandableListView.SetItemChecked(fragmentPosition, true);
});
Suspender answered 3/8, 2017 at 18:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.