Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 776
zkwentz Avatar asked Nov 22 '10 21:11

zkwentz


2 Answers

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.

like image 52
Ollie C Avatar answered Oct 18 '22 16:10

Ollie C


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; 
    }
like image 8
AlexKots Avatar answered Oct 18 '22 17:10

AlexKots