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

7

30

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. :)

Anemone answered 22/11, 2010 at 21:58 Comment(0)
L
9

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.

Lierne answered 22/11, 2010 at 22:28 Comment(6)
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.Anemone
These kinds of quirks in Android do make it a steep learning curve, but once understood we do get the performance benefits of recycling.Lierne
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.Anemone
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.Anemone
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.Lierne
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).Lierne
V
8

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; 
    }
Vagrom answered 10/8, 2012 at 0:55 Comment(2)
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?Interdisciplinary
Did anyone solved the issue with long lists of items?Ewart
E
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>
Ethelinda answered 9/5, 2012 at 12:40 Comment(1)
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.Batchelder
D
2

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();
}                
Dumortierite answered 9/8, 2011 at 9:55 Comment(0)
C
2

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.

Cheloid answered 26/10, 2015 at 11:43 Comment(0)
M
0

Inside the adapter

 viewHolder.radioBtn.setOnClickListener(new OnClickListener() {

               @Override
               public void onClick(View v) {
                   Log.e("called", "called");
                   if(position != mSelectedPosition && mSelectedRB != null){
                       mSelectedRB.setChecked(false);
                   }



                   mSelectedPosition = position;
                   mSelectedRB = (RadioButton)v;
               }
           });

           viewHolder.radioBtn.setText(mList[position]);  
           if(mSelectedPosition != position){
               viewHolder.radioBtn.setChecked(false);

           }else{
               viewHolder.radioBtn.setChecked(true);

               if(mSelectedRB != null && viewHolder.radioBtn != mSelectedRB){
                   mSelectedRB = viewHolder.radioBtn;
               }
           }

add style to the radio button

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

    <item android:drawable="@drawable/tick" android:state_checked="true"/>
    <item android:drawable="@drawable/untick" android:state_checked="false"/>

 </selector>

and in the xml use the radio button style

<?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" >

    <RadioButton
        android:id="@+id/radioBtn"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:button="@null"
        android:checked="true"
        android:drawablePadding="30dp"
        android:drawableRight="@drawable/checkmark"
        android:text="first" />

</RelativeLayout>
Mcbee answered 14/12, 2015 at 10:56 Comment(0)
D
-1

On your adapter:

public void setChecked(View v, int position) {

    ViewGroup parent = ((ViewGroup) v.getParent()); // linear layout

    for (int x = 0; x < parent.getChildCount() ; x++) {

        View cv = parent.getChildAt(x);
        ((RadioButton)cv.findViewById(R.id.checkbox)).setChecked(false); // your checkbox/radiobtt id
    }

    RadioButton radio = (RadioButton)v.findViewById(R.id.checkbox);
    radio.setChecked(true);
}

On your activity/fragment:

   listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            ((ModelNameAdapter)adapter).setChecked(view, position);

        }
    });
Decani answered 7/4, 2015 at 18:20 Comment(2)
position is never used in your set checked method.Viscounty
For this to work you must not recycle your views. Dont do: if convertView == null { convertView = inflate}, always inflate a new one.Decani

© 2022 - 2024 — McMap. All rights reserved.