Android ListView with RadioButton in singleChoice mode and a custom row layout

54,458

Solution 1

Do bear in mind that in the ListView row items are RECYCLED. This is likely to explain why actions on one row are affecting another. Dig around in Mark's book and you'll find coverage of this.

If you're using an adapter with the list, you can use getView() on an adapter to add a click handler to each row as it's created/recycled, and make sure the state is managed correctly as the row items are created and recycled.

My approach is to store the state in my own data structure, and then use getView() to mirror that in the UI as the user scrolls up and down the ListView. There may be a better solution, but that works for me.

Solution 2

Simple solution:

Add this line in the RadioButton layout of your xml file:

<RadioButton 
   ...
   android:onClick="onClickRadioButton"
   ...
/>

Then in the activity class that uses the ListView, add the following:

   private RadioButton listRadioButton = null;
   int listIndex = -1;

   public void onClickRadioButton(View v) {
        View vMain = ((View) v.getParent());
        // getParent() must be added 'n' times, 
        // where 'n' is the number of RadioButtons' nested parents
        // in your case is one.

        // uncheck previous checked button. 
        if (listRadioButton != null) listRadioButton.setChecked(false);
        // assign to the variable the new one
        listRadioButton = (RadioButton) v;
        // find if the new one is checked or not, and set "listIndex"
        if (listRadioButton.isChecked()) {
            listIndex = ((ViewGroup) vMain.getParent()).indexOfChild(vMain); 
        } else {
            listRadioButton = null;
            listIndex = -1;
        }
    }

With this simple code only one RadioButton is checked. If you touch the one that is already checked, then it returns to the unchecked mode.

"listIndex" is tracking the checked item, -1 if none.

If you want to keep always one checked, then code in onClickRadioButton should be:

   public void onClickRadioButton(View v) {
        View vMain = ((View) v.getParent());
        int newIndex = ((ViewGroup) vMain.getParent()).indexOfChild(vMain);
        if (listIndex == newIndex) return;

        if (listRadioButton != null) {
                listRadioButton.setChecked(false);
        }
        listRadioButton = (RadioButton) v;
        listIndex = newIndex; 
    }

Solution 3

My Name Is Gourab Singha. I solved This Issue. I have Written This Code to solve it. I hope this helps you all. Thanks. I require 3 hours for this.

public void add_option_to_list(View v){


    LinearLayout ll = (LinearLayout)v.getParent();
    LinearLayout ll_helper = null; 
    LinearLayout ll2 =  (LinearLayout)ll.getParent();
    LinearLayout ll3 =  (LinearLayout)ll2.getParent();

    ListView ll4 =  (ListView)ll3.getParent();
    //RadioButton rb1= (RadioButton)ll.getChildAt(1);
    Toast.makeText(getApplicationContext(), ", "+ll4.getChildCount(), Toast.LENGTH_LONG).show();    
    //ll.findViewById(R.id.radio_option_button)
    for(int k=0;k<ll4.getChildCount();k++){

        ll3 = (LinearLayout)ll4.getChildAt(k);
        ll2 = (LinearLayout)ll3.getChildAt(0);
        ll_helper = (LinearLayout)ll2.getChildAt(0);
        RadioButton rb1 = (RadioButton)ll_helper.getChildAt(1);
        if(rb1.isChecked())
        rb1.setChecked(false);


    }
    RadioButton rb1 = (RadioButton)ll.getChildAt(1);
    rb1.setChecked(true);
    //Toast.makeText(getApplicationContext(), ""+((TextView)ll.getChildAt(4)).getText().toString(), Toast.LENGTH_LONG).show();


    }

The 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="vertical" >

    <LinearLayout

        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >


        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >

            <TextView
                android:id="@+id/id"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Category Name"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:visibility="gone" />

            <RadioButton
                android:id="@+id/radio_option_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="add_option_to_list"
                android:text="" />

            <TextView
                android:id="@+id/option_name"
                android:layout_width="wrap_content"
                android:layout_height="fill_parent"
                android:layout_marginLeft="3dp"
                android:layout_weight="5.35"
                android:text="Category Name"
                android:textAppearance="?android:attr/textAppearanceMedium" />



            <TextView
                android:id="@+id/option_price"
                android:layout_width="76dp"
                android:layout_height="wrap_content"
                android:layout_marginRight="10dp"
                android:gravity="right"
                android:text="$0.00"
                android:textAppearance="?android:attr/textAppearanceMedium" />

            <TextView
                android:id="@+id/option_unit_price"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginRight="5dp"
                android:gravity="right"
                android:text="$0.00"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:visibility="gone" />
        </LinearLayout>

    </LinearLayout>

</LinearLayout>

Solution 4

I've just had a similar problem. It seemed that it was related to views recycling. But I added Log's everywhere and noticed that the recycling handling was fine. The problem was RadioButtons handling. I was using:

if(answer == DBAdapter.YES){
   rbYes.setChecked(true); 
   rbNo.setChecked(false);                    
}
else if(answer == DBAdapter.NO){
   rbYes.setChecked(false); 
   rbNo.setChecked(true);
}
else{
  rbYes.setChecked(false); 
  rbNo.setChecked(false);
}                

and the correct way of doing it is:

if(answer == DBAdapter.YES){
   rbYes.setChecked(true); 
}
else if(answer == DBAdapter.NO){
   rbNo.setChecked(true);
}
else{
   RadioGroup rg = (RadioGroup)(holder.answerView);
   rg.clearCheck();
}                

Solution 5

You can achieve much more simple, follow this. It was tried in MarshMallow

1)Have a CheckedTextView :

<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:checkMark="?android:attr/listChoiceIndicatorSingle"
    android:padding="10dp"
    android:textAppearance="?android:attr/textAppearanceMedium">

  • ListChoiceIndicatorSingle will display RadioButton.

  • ListChoiceIndicatorMultiple will display CheckBox

Don't forgot to put choiceMode:Single in your ListView.

Share:
54,458
zkwentz
Author by

zkwentz

Updated on January 26, 2021

Comments

  • zkwentz
    zkwentz over 3 years

    I have a ListView, which is in single-choice mode. All I want is to display a RadioButton to the side, that when clicked highlights to say it is selected, and when a different one is clicked that one goes back to unselected and the new one becomes selected. Why is this so hard? This should not be this complicated. I've spent DAYS looking for an appropriate answer to this and I have found nothing, so I'm asking hopefully clearly and concisely.

    My layout for the listview (R.layout.view_orders):

    <?xml version="1.0" encoding="utf-8"?>
    <ListView 
            android:choiceMode="singleChoice"
            android:id="@android:id/list"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:divider="@drawable/list_divider"
            android:dividerHeight="1px"
            android:focusable="false"
            android:focusableInTouchMode="false"
            android:cacheColorHint="#00000000">
    </ListView>
    

    My custom row (R.layout.orders_row):

    <?xml version="1.0" encoding="utf-8"?>
        
    <RelativeLayout
        xmlns:app="http://schemas.android.com/apk/res/com.xxx.xxxxxx"
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="6dip">
        
        <com.xxx.xxxxxx.VerticalLabelView
            app:text="SHORT"
            app:textColor="#666"
            app:textSize="14sp"
            android:id="@+id/state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true" />
        
        <TextView
            android:id="@+id/quantity"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/state" 
            android:layout_centerVertical="true"
            android:gravity="center"
            android:textSize="40sp"
            android:layout_margin="2dip"
            android:minWidth="30dip"
            android:textColor="#555" />
            
            
        <RelativeLayout
            android:layout_toRightOf="@id/quantity"
            android:layout_centerVertical="true"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content">
            
            <TextView
                android:id="@+id/instrument"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:textSize="20sp"
                android:textColor="#333"
                android:layout_marginLeft="2dip"
                android:layout_marginRight="2dip"
                />
                
                
            <TextView
                android:id="@+id/deets"
                android:layout_below="@id/instrument"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:textSize="16sp"
                android:textColor="#888"
                android:layout_marginLeft="2dip"
                android:layout_marginRight="2dip"
                />
            
        </RelativeLayout>
        
            <RadioButton
                android:id="@+id/selector"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                />
    
    </RelativeLayout>
    

    My onCreate() method:

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.view_orders);
        client = new Client(handler);
        ola = new OrdersAdapter(this, R.layout.orders_row, Orders);
        setListAdapter(ola);
        final RelativeLayout loading = (RelativeLayout) findViewById(R.id.loading);
        panel = (PositionsPanel) findViewById(R.id.panel);
        Utility.showProgressBar(loading);
        client.Connect("orders");
    }
    

    Now everything underlying works as expected, you click on a radiobutton, and through its tag, I can appropriately select that item from the list and manipulate it how I want. However, when the first radio button is clicked, the last one will be selected. Click that same radio button again, and it is now selected as well. Click it once more and nothing happens, both the last and the first are selected. Now I click any other one on the list, it gets selected like expected. Click any one of the selected radio buttons and nothing happens, the radio button remains selected.

    I have tried using the following in onCreate():

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.view_orders);
        client = new Client(handler);
        ola = new OrdersAdapter(this, android.R.layout.simple_list_item_single_choice, Orders);
        setListAdapter(ola);
        final RelativeLayout loading = (RelativeLayout) findViewById(R.id.loading);
        panel = (PositionsPanel) findViewById(R.id.panel);
        Utility.showProgressBar(loading);
        client.Connect("orders");
    }
    

    and that just shows no radio buttons at all. AWESOME.

    Now maybe (read: most likely), I'm just dense and can't figure this out, but I've seen this question asked a lot with no real answer. Lots of references to other tutorials or the Commonsware guy's book. However, the comments are old now and his repository has changed so much, that those are no longer correct answers.

    So, does anyone have any idea how to get the expected functionality out of this? Or failing that, just pass me along with the Gmail app's source code. :)

  • zkwentz
    zkwentz over 13 years
    So this is exactly what's happening. I don't know why any of the other answers didn't just state this simple fact. After realizing I was using a convertView and recycling it, it was really obvious. A minute or two of googling and I know how to resolve this. I looked in Mark's book, Chapter 9, "Getting Fancy With Lists" and his RateList example is detailed there, which explains it pretty clearly. Anyone else having this issue check out that chapter of Beginning Android by Mark Murphy.
  • Ollie C
    Ollie C over 13 years
    These kinds of quirks in Android do make it a steep learning curve, but once understood we do get the performance benefits of recycling.
  • zkwentz
    zkwentz over 13 years
    So that didn't end up working, I went through it and while recycling is definitely the reason behind what I'm seeing, Mark's book doesn't detail how to fix the specific problem I'm having.
  • zkwentz
    zkwentz over 13 years
    I'm not real sure. The buttons "know" where they are for sure, but they don't store the correct state. I just changed it to a delete button and was done with it. Too much time wasted. Thanks for the help though, recycling is definitely the underlying issue here.
  • Ollie C
    Ollie C over 13 years
    I keep the state in a separate object, and in the adapter's getView() method I pull the status from the store, and apply it to the checkbox (which may have been recycled). To keep things simple you could strip out the recycling by re-creating a new view every time you enter getView(), it may help debug. Performance may be bad if you don't get it back for release though. ListView is pretty slow at the best of times. I suggest clarifying exactly what the issue is, as you will then be guided to a solution.
  • Ollie C
    Ollie C over 13 years
    If the states still appear random, rule out recycling by either disabling it or forcing the state from your own state-store. You could store the checkbox state in Android preferences (slow but permanent) or in the Activity or in the Application (subclass the Application object).
  • David Gorsline
    David Gorsline almost 12 years
    Welcome to Stack Overflow. I edited your answer to make it easy to read. By the way, you don't need to write your name in your answer; your name is always displayed at the end of your answer.
  • user1118764
    user1118764 over 10 years
    Hi,this works when the list fits in 1 page, but when the list is longer than a page, listIndex gets messed up as it starts counting from the first visible list item rather than from the actual start of the list. How can this be fixeD?
  • Sagar Limbani
    Sagar Limbani almost 9 years
    position is never used in your set checked method.
  • Renato Probst
    Renato Probst almost 9 years
    For this to work you must not recycle your views. Dont do: if convertView == null { convertView = inflate}, always inflate a new one.
  • PayToPwn
    PayToPwn over 3 years
    Did anyone solved the issue with long lists of items?