Android: Spinners, SimpleAdapter, and (maybe) ViewBinder
The question came up on StackOverflow yesterday: Can a Spinner be configured to use a SimpleAdapter (and if so, how?) The user who asked the question, Chromium, ran into a couple of problems; the last of these was an IllegalStateException after clicking on the Spinner to make a selection.
A bit of searching turned up this issue about using a Spinner in combination with a SimpleAdapter to display a CheckedTextView; it seems that a ViewBinder must be set on the SimpleAdapter for that combination to work. Although it’s not the same problem, it still made me wonder if the SimpleAdapter / Spinner combination might need a ViewBinder for displaying TextView, too.
I wasn’t able to reproduce the IllegalStateException when I ran a sample program on AndroidOS 2.2, but I thought I’d try it on 1.5 in the emulator. Sure enough, I hit the same problem that Chromium reported. Apparently, whatever issue this is has been fixed by 2.2, and perhaps in earlier releases. (I haven’t tried other releases besides 2.2 and 1.5.)
And I also found that ViewBinder once again comes to the rescue, preventing the IllegalStateException from being thrown.
Like some other interfaces in Android (I’m looking at you, CursorToStringConverter. You too, FilterQueryProvider), ViewBinder’s bark is worse than its bite. In other words, the name may be a bit intimidating, but the implementation is a breeze.
View view, Object data, String textRepresentation
. But the documentation doesn’t tell us what these parameters really mean in a given context. I started by creating an empty setViewValue method, and set a breakpoint there. Inside the breakpoint, I verified that view
is the TextView for displaying a single row in the Spinner; data
is the String value in the Map for this row in the Spinner (“Red”, “Orange”, etc.); and textRepresentation is, well, the text representation of data
, which in this case is that same String.Here’s a sample program, adapted from Chromium’s example, that adds a ViewBinder to a SimpleAdapter. This works on AndroidOS 1.5 and 2.2 (and, presumably, intermediate versions as well.)
package org.oowb.HelloSpinner; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.SimpleAdapter; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import android.widget.AdapterView.OnItemSelectedListener; /** * Displays a list of colors in a Spinner. */ public class HelloSpinner extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // The key to use for reading the color from the Map final String[] from = new String[] { "color" }; // The type of View to use for displaying the color name. // android.R.id.text1 is a standard resource for displaying text. final int[] to = new int[] { android.R.id.text1 }; // Create the List of strings for the spinner to display. Each string // is embedded within a Map, using "color" as the key. final List<Map<String, String>> data = new ArrayList<Map<String, String>>(); final String[] colors = getResources().getStringArray(R.array.colors); for (int i = 0; i < colors.length; i++) { data.add(addData(colors[i])); } final SimpleAdapter simpleAdapter = new SimpleAdapter(this, data, android.R.layout.simple_spinner_item, from, to); simpleAdapter.setDropDownViewResource( android.R.layout.simple_spinner_dropdown_item); // Add a ViewBinder to display a color name in a TextView within the // Spinner. (This isn't needed in AndroidOS 2.2. In earlier releases, // when we're displaying text data within a Spinner, and no ViewBinder // is set in the SimpleAdapter, an IllegalStateException is thrown.) SimpleAdapter.ViewBinder viewBinder = new SimpleAdapter.ViewBinder() { public boolean setViewValue(View view, Object data, String textRepresentation) { // We configured the SimpleAdapter to create TextViews (see // the 'to' array, above), so this cast should be safe: TextView textView = (TextView) view; textView.setText(textRepresentation); return true; } }; simpleAdapter.setViewBinder(viewBinder); final Spinner spinner = (Spinner) findViewById(R.id.spinner); spinner.setAdapter(simpleAdapter); // Add an OnItemSelectedListener to display the selected Color spinner.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { // Get the color name out of the Map final Map<String, String> data = (Map<String, String>) parent.getItemAtPosition(position); final String text = "Selected Color:- " + data.get("color"); Toast.makeText(parent.getContext(), text, Toast.LENGTH_LONG).show(); } @Override public void onNothingSelected(AdapterView<?> arg0) { // Do nothing } }); } /** * Convert the String that's passed in into a Map, with * "color" as the key, and the String as the value. * @param colorName The color to be inserted into a new Map * @return the new Map */ private Map<String, String> addData(String colorName) { Map<String, String> map = new HashMap<String, String>(); map.put("color", colorName); return map; } }
To complete the example, here are the layout (main.xml
) and values files (arrays.xml
and strings.xml
):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:padding="10dip" android:layout_width="fill_parent" android:layout_height="wrap_content"> <Spinner android:id="@+id/spinner" android:layout_width="fill_parent" android:layout_height="wrap_content" android:drawSelectorOnTop="true" android:prompt="@string/prompt" /> </LinearLayout>
<?xml version="1.0" encoding="UTF-8"?> <resources> <string-array name="colors"> <item>Red</item> <item>Orange</item> <item>Yellow</item> <item>Green</item> <item>Blue</item> <item>Purple</item> <item>Violet</item> <item>Octarine</item> </string-array> </resources>
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Color Chooser</string> <string name="prompt">Choose a color:</string> </resources>Android, SimpleAdapter, Spinner, ViewBinder
thank you for very useful and great article…. it helps me lot…… thanks buddy
Hi, I was wondering whether would it be possible to use ViewBinder with ImageLoader class from this website: http://code.google.com/p/android-imagedownloader/source/browse/trunk/src/com/example/android/imagedownloader/ImageDownloader.java?r=4
Hi, Joel,
I’m not sure what you have in mind. Perhaps you’d use the ImageLoader within a ViewBinder to load images into a ListView or Spinner?
I don’t see why not, although it might be slow (especially on the first view of each image.) Since the ImageLoader caches the images, you might want to pre-load it before attaching it to the ViewBinder. (This assumes you’re not using a large number of images!)
Well.. I will be loading it into a listview and the number of images is very large. Or should I not use this class? If not, then can you recommend me a good way or website to do this loading of images? I have been stuck at this for a few days now and frankly its driving me up the wall.