0

I am a new android learner. I am working on a project which needs custom List View with 50 or more rows. Each row contains a Text View indicating row numbers and a Radio Group which have four Radio Buttons. Problem is when I check any of the radio buttons and then scroll down, I see many radio buttons automatically checked. And when scroll up I see some random radio button are checked!

I am guessing its happening because of that recycle issue of the getView() method said here. Frankly speaking as a novice I couldn't understand the whole lecture.

I have also looked at this thread. Which is similar to my problem but couldn't understand clearly and solve my problem.

So how can I solve this problem and get data from the radio groups. As I am a new learner please explain easily.

Thanks!

[image]Here is my list view with a radio button checked of 1st row.

[image]When I scroll down I see 15th row is auto checked!

[image]When scroll up again I see radio group of the 1st row is in initial state again!

Here is my code:

MainActivity:

public class MainActivity extends Activity {

    /* ListView */
    ListView lv;

    /* Data source for numbering */
    String[] numbering = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50"};

    /* Custom adapter */
    CustomListAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /* initialization */
        adapter = new CustomListAdapter(this, R.layout.list_row_item, numbering);
        lv = (ListView) findViewById(R.id.lvQuestion);

        /* show list view */
        lv.setAdapter(adapter);
    }
}

list_row_item.xml:

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

    <TextView
        android:id="@+id/tvNumbering"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_weight="4"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <RadioGroup
        android:id="@+id/radioGroup"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="horizontal" >

        <RadioButton
            android:id="@+id/answer0"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@drawable/radio_button"
            android:checked="true" />

        <RadioButton
            android:id="@+id/answer1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@drawable/radio_button" />

        <RadioButton
            android:id="@+id/answer2"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@drawable/radio_button" />

        <RadioButton
            android:id="@+id/answer3"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@drawable/radio_button" />
    </RadioGroup>

</LinearLayout>

Custom List Adapter:

public class CustomListAdapter extends ArrayAdapter<String> {
    Context context;
    int layoutResourceId;
    String[] numbers;

    public CustomListAdapter(Context context, int layoutResourceId,
            String[] numbers) {
        super(context, layoutResourceId, numbers);
        this.context = context;
        this.layoutResourceId = layoutResourceId;
        this.numbers = numbers;
    }

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

        ViewHolder holder = null;

        if (convertView == null) {
            /* Add layout inflater */
            LayoutInflater infletar = ((Activity) context).getLayoutInflater();
            convertView = infletar.inflate(layoutResourceId, null);

            /* initialization */
            holder = new ViewHolder();
            holder.tvNumbering = (TextView) convertView
                    .findViewById(R.id.tvNumbering);
            holder.rg = (RadioGroup) convertView.findViewById(R.id.radioGroup);

            /* set tag */
            convertView.setTag(holder);

        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.tvNumbering.setText(numbers[position]);

        return convertView;
    }

    static class ViewHolder {
        TextView tvNumbering;
        RadioGroup rg;
    }
}
Community
  • 1
  • 1
Ashfaqur Rahaman
  • 714
  • 1
  • 5
  • 21

1 Answers1

2

When getView is called you need to initialise the views held in holder using the data for the relevant position.

You are currently doing this for the TextView in the line.

holder.tvNumbering.setText(numbers[position]);

You need to do the same for the RadioGroup.

At the moment your CustomListAdapter is defined as extending an ArrayAdapter of Strings. So the list of items you past to the adapter is an array of strings, and the only information the adapter holds onto for each item is the value of the numbers String for each item.

In your case what you really want is for each item to have a numbers value and a checked radio button value.

Instead of providing a list of Strings to the adapter, you should provide a list of your own custom class which encapsulates both these variables.

public class MyItem {
    public String number;
    public int checkedRadioButtonId;
}

You need to make your adapter handle this class.

public class CustomListAdapter extends ArrayAdapter<MyItem> {
    Context context;
    int layoutResourceId;
    MyItem[] items;

    public CustomListAdapter(Context context, int layoutResourceId,
                             MyItem[] items) {
        super(context, layoutResourceId, items);
        this.context = context;
        this.layoutResourceId = layoutResourceId;
        this.items = items;
    }

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

        View row = convertView;
        ViewHolder holder = null;

        if (row == null) {
            LayoutInflater inflater = ((Activity) context).getLayoutInflater();
            row = inflater.inflate(layoutResourceId, null);
            holder = new ViewHolder(row);
            row.setTag(holder);
            // Set a listener to update the data in your items list when the RadioGroup is clicked
            holder.rowRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
                public void onCheckedChanged(RadioGroup group, int checkedId) {

                    // **get which item this RadioGroup refers to**
                    Integer pos = (Integer) group.getTag();
                    Log.d("onCheckedChanged", "pos = " + pos + ", checkedId = " + checkedId);
                    items[pos].checkedRadioButtonId = checkedId;

                }
            });
        } else {
            holder = (ViewHolder) row.getTag();
        }

        // **Tag each RadioGroup so we know what item it refers to**
        holder.rowRadioGroup.setTag(position);

        // setup both views from the values stored in your items list
        holder.rowTextView.setText(items[position].number);
        holder.rowRadioGroup.check(items[position].checkedRadioButtonId);



        return row;
    }

    static class ViewHolder {
        TextView rowTextView = null;
        RadioGroup rowRadioGroup = null;

        ViewHolder(View row) {
            this.rowTextView = (TextView) row.findViewById(R.id.tvNumbering);
            this.rowRadioGroup = (RadioGroup) row.findViewById(R.id.radioGroup);
        }
    }
}

Your MainActivity would become something like

public class MainActivity extends Activity {

    /* ListView */
    ListView lv;

    /* Data source for the ListView */
    MyItem[] myItems = new MyItems[50];

    /* Custom adapter */
    CustomListAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Create the data for the list
        for (int i = 0; i < myItems.length; i++) {
            final MyItem item = new MyItem();
            // your numbering goes from 1 to 50, but the array indexes go from 0 to 49
            item.number = String.valueOf(i + 1); 
            // set all items to answer0 by default
            item.checkedRadioButtonId = R.id.answer0

            // put the new item in the array
            myItems[i] = item;
        }

        /* initialization */
        adapter = new CustomListAdapter(this, R.layout.list_row_item, myItems);
        lv = (ListView) findViewById(R.id.lvQuestion);

        /* show list view */
        lv.setAdapter(adapter);
    }
}
Sound Conception
  • 5,263
  • 5
  • 30
  • 47
  • Thanks! But how can I do that for RadioGroup? In TextView I am passing an information but there is no such thing for RadioGroup! – Ashfaqur Rahaman Feb 05 '15 at 07:26
  • Of course there is information for a RadioGroup! You get the information of which button is checked with getCheckedRadioButtonId(). – Sound Conception Feb 05 '15 at 08:23
  • Most of the errors are gone except, now if I check a radio button and scroll down(so that checked radio button go off screen), then scroll up I see that the checked radio button has gone to its initial state! Why is this happening? – Ashfaqur Rahaman Feb 05 '15 at 09:22
  • Sorry. See my latest edit. The OnCheckedChangeListener returns the RadioGroup group that was clicked, but we need to know what item that group referred to. By tagging each group with a position number, we can then extract that info from the `group` object in the listener's onCheckChanged method. – Sound Conception Feb 05 '15 at 09:54
  • Hah, one more edit. The tag can be any `object` so when we `getTag()` we need to cast it back to an `Integer` so we can use it. – Sound Conception Feb 05 '15 at 10:08
  • I also noticed that when added those two line. But still it causing that issue. After scroll down and then scroll up, checked radio buttons reset to initial state ! – Ashfaqur Rahaman Feb 05 '15 at 13:14
  • Have updated the CustomListAdapter class. If you look at the source code for [ListView](http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.4_r1/android/widget/ListView.java#ListView), you will see that it caches old item views. It tries to supply the old view back to the adapter as the convertView parameter, so that the adapter can re-use it. Previously we were re-setting the OnCheckChangedListener every time. What we should be doing is only setting it if convertView == null. – Sound Conception Feb 07 '15 at 02:02