Android ListActivity and Selected State
Here’s a simple desktop dialog that allows the user to select an item from a list and then click an “Action” button to act on the selected item.
How it works – a user selects an item in the list, that row is highlighted, the user then clicks one of the buttons below the list, the application retrieves the selected index, acts on the selected item and the application continues. Simple right?
Trying to implement this exact same behavior on the Android platform can be frustrating for a new developer. Here’s why. With the best intentions you code an Android version of the above dialog extending ListActivity and using an ArrayAdapter to render your array of strings is the scrolling list. So far so good. But when a user selects an item in the list and then clicks one of the action buttons the previously selected item is “lost”. The list does not maintain selected state because the device must switch between touch mode (such as “flinging” a scrolling list) and traditional mouse/pointer interaction. As soon as the Android enters touch mode any focus and selected state is lost.
The Android team has made it clear this behavior is part of the platform design and discourages developers from coding around it. Romain Guy does a good job explaining why the Android UI behaves this way, see http://android-developers.blogspot.com/2008/12/touch-mode.html.
Think of it this way, selection of an item in the List is intended to be a trigger, it’s not intended to display selected state while the user ponders his next move. It gets a little more confusing because if the trackball is used to scroll the list the rows look like they’re selected – they’re not, just rendered that way so that a user knows where they are when using the trackball.
For our list screen there is a simple solution, make each row in the list a radio button. This way the user can select an item in the list, then click one of the buttons below the list. Use the radio button to retain selected state. One way to do this is to extend ListActivity and implement a custom BaseAdapter where you assign a RadioButton to be the row render. This is also a lot of plumbing for a simple list screen. (For an example that uses a similar approach, see http://bestsiteinthemultiverse.com/2009/12/android-selected-state-listview-example/ )
Another way is to re-use the dialog screen described in this post and use code to dynamically add radio buttons to a RadioGroup inside of a ScrollView.
Here’s the layout xml code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/linlayoutBase"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<ScrollView
android:id="@+id/scrollview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
>
<LinearLayout
android:id="@+id/linearMain"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<RadioGroup
android:id="@+id/radiogroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
>
</RadioGroup>
</LinearLayout>
</ScrollView>
<LinearLayout
android:id="@+id/linlayoutButtons"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
>
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Action 1"
android:layout_weight="1"
>
</Button>
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Action 2"
android:layout_weight="1"
>
</Button>
<Button
android:id="@+id/btn3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Action 3"
android:layout_weight="1"
>
</Button>
</LinearLayout>
</LinearLayout>
Here’s the code for dynamically adding radio buttons to the radio group:
/** add radio buttons to the group */ private void populateList(){ // get reference to radio group in layout RadioGroup radiogroup = (RadioGroup) findViewById(R.id.radiogroup); // layout params to use when adding each radio button LinearLayout.LayoutParams layoutParams = new RadioGroup.LayoutParams( RadioGroup.LayoutParams.WRAP_CONTENT, RadioGroup.LayoutParams.WRAP_CONTENT); // add 20 radio buttons to the group for (int i = 0; i < 20; i++){ RadioButton newRadioButton = new RadioButton(this); String label = "item " + i; newRadioButton.setText(label); newRadioButton.setId(i); radiogroup.addView(newRadioButton, layoutParams); } }
Here's the code for getting the selected radio button:
RadioGroup radiogroup = (RadioGroup) findViewById(R.id.radiogroup);
System.out.println("radio btn: " + radiogroup.getCheckedRadioButtonId());