Android Spinner with different layouts for "drop down state" and "closed state"?
I have an Android Spinner view in my layout. I would like that spinner to show only a single text item when closed, but when the user clicks on it (i.e. opens the spinner dialog) I would like to show more extensive inf开发者_如何学Goormation for each item, including an icon and an additional description text view. As it is now the spinner shows the very same layout (icon, title + description) in both states.
If I attach an ArrayAdapter to the spinner, then I get access to something called "setDropDownViewResource" but that's not necessarily what I need, since my spinner data is fetched from a Cursor not from an Array of any kind (I have, as of now, created my own adapter, extending BaseAdapter).
Anyone who can help me?
You have to create a custom Adapter
class for the Spinner and overwrite the two methods getView()
for the normal closed view and getDropDownView()
for the drop down list view. Both methods must return a View
object for a single element.
Have a look at this tutorial it might help you getting started.
I was having problem too. Rather than overriding the class, I hava an easier way to do this.
But first you need to understand the difference between the resource id in the adapter constructor, and the other one in setDropDownViewResource(...)
. For example,
SimpleAdapter adapter =
new SimpleAdapter(ab.getThemedContext(), data, R.layout.actionbar_dropdown, new String[] { "EventID", "Icon" },
new int[] { R.id.event_id, R.id.icon });
adapter.setDropDownViewResource(R.layout.actionbar_list_item);
R.layout.actionbar_dropdown
is the style for spinner, and R.layout.actionbar_list_item
for every single list item.
I used SimpleAdapter here, since if I use ArrayAdapter, the xml can only be a single TextView.
R.layout.actionbar_list_item
contains a TextView whose id is event_id
and an ImageView whose id is icon
.
R.layout.actionbar_dropdown
is almost exactly the same as actionbar_list_item
, but the visibility of ImageView of the latter is set to GONE.
In this way every list item has a textview and an imageview, but you will only see a textview on the spinner.
Using the code of the tutorial linked by Flo, I created the following CustomSpinnerAdapter in order to show two different sets of strings, one when the items are displayed, and one when not. I hope it helps someone.
public class CustomSpinnerAdapter extends ArrayAdapter<String> {
Context mContext;
int mTextViewResourceId;
String[] mObjects;
String[] mShortNameObjects;
public CustomSpinnerAdapter(Context context, int textViewResourceId,
String[] objects, String[] shortNameObjects) {
super(context, textViewResourceId, objects);
mContext = context;
mTextViewResourceId = textViewResourceId;
mObjects = objects;
mShortNameObjects = shortNameObjects;
}
@Override
public View getDropDownView(int position, View convertView,
ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
TextView row = (TextView) inflater.inflate(mTextViewResourceId, parent, false);
row.setText(mObjects[position]);
return row;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return getCustomView(position, convertView, parent);
}
public View getCustomView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
TextView row = (TextView) inflater.inflate(mTextViewResourceId, parent, false);
row.setText(mShortNameObjects[position]);
return row;
}
}
And the usage inside a Fragment:
CustomSpinnerAdapter mSpinnerAdapter = new CustomSpinnerAdapter(getActivity(), R.layout.spinner_item, getResources().getStringArray(R.array.action_filter), getResources().getStringArray(R.array.action_filter_short_names));
Finally, the layout for the spinner item:
spinner_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="18dip"
android:gravity="left"
android:textColor="@color/navdraw_list_item_background_default"
android:padding="5dip" />
Only sets the dropdown view resource with your alternative layout:
ArrayAdapter<String> genderAdapter = new ArrayAdapter<>(this, R.layout.adapter_spinner_white, Constants.GENDER);
genderAdapter.setDropDownViewResource(R.layout.adapter_spinner_white_dropdown);
view.setAdapter(genderAdapter);
For me, it's only a layout with an extra padding left, because my background of spinner is a rounded drawable and require this extra space.
Just call setUpSpinner()
method after getting reference to spinner
// here is setUpSpinner method
private void setupSpinner() {
// Create adapter for spinner. The list options are from the String array it will use
// the spinner will use the default layout
ArrayAdapter spinnerAdapter = ArrayAdapter.createFromResource(this,
R.array.array_dropdown_options, android.R.layout.simple_spinner_item);
// Specify dropdown layout style - simple list view with 1 item per line
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
// Apply the adapter to the spinner
spinner.setAdapter(spinnerAdapter);
// spinner is referenced spinner by finViewById.
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String selection = (String) parent.getItemAtPosition(position);
if (!TextUtils.isEmpty(selection)) {
if (selection.equals(getString(R.string.item_a))) {
// your code for selected item whose id equals to id to R.string.item_a
} else if (selection.equals(getString(R.string.item_b))) {
// your code
} else {
// your code
}
}
}
// Because AdapterView is an abstract class, onNothingSelected must be defined
@Override
public void onNothingSelected(AdapterView<?> parent) {
// code for nothing selected in dropdown
}
});
}
I made an extension of ArrayAdapter
which preservers as much of its functionality as possible, just sets the long description to the selected item.
First, we need a new model for feeding the adapter. Note that toString()
returns the shortDescription
so that the ArrayAdapter
will show short description in the dropdown:
data class DescriptiveArrayItem(val shortDescription: String, val longDescription: String) {
override fun toString(): String {
return shortDescription
}
}
Second, we create our custom adapter which overrides getView()
and shows the long description when the item is selected (unfortunately, a lot of fields and methods in ArrayAdapter
are private so I had to copy them):
class DescriptiveArrayAdapter : ArrayAdapter<DescriptiveArrayItem> {
private var mResource: Int
private val mFieldId: Int
private val mContext: Context
private val mInflater: LayoutInflater
constructor(context: Context, resource: Int) : super(context, resource) {
mContext = context
mFieldId = 0
mInflater = LayoutInflater.from(context)
mResource = resource
}
constructor(context: Context, resource: Int, textViewResourceId: Int) : super(context, resource, textViewResourceId) {
mContext = context
mFieldId = textViewResourceId
mInflater = LayoutInflater.from(context)
mResource = resource
}
constructor(context: Context, resource: Int, objects: Array<out DescriptiveArrayItem>) : super(context, resource, objects) {
mContext = context
mFieldId = 0
mInflater = LayoutInflater.from(context)
mResource = resource
}
constructor(context: Context, resource: Int, textViewResourceId: Int, objects: Array<out DescriptiveArrayItem>) : super(context, resource, textViewResourceId, objects) {
mContext = context
mFieldId = textViewResourceId
mInflater = LayoutInflater.from(context)
mResource = resource
}
constructor(context: Context, resource: Int, objects: List<DescriptiveArrayItem>) : super(context, resource, objects) {
mContext = context
mFieldId = 0
mInflater = LayoutInflater.from(context)
mResource = resource
}
constructor(context: Context, resource: Int, textViewResourceId: Int, objects: List<DescriptiveArrayItem>) : super(context, resource, textViewResourceId, objects) {
mContext = context
mFieldId = textViewResourceId
mInflater = LayoutInflater.from(context)
mResource = resource
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return createViewFromResource(mInflater, position, convertView, parent, mResource)
}
private fun createViewFromResource(inflater: LayoutInflater, position: Int,
convertView: View?, parent: ViewGroup, resource: Int): View {
val text: TextView?
val view: View = convertView ?: inflater.inflate(resource, parent, false)
try {
if (mFieldId == 0) {
// If no custom field is assigned, assume the whole resource is a TextView
text = view as TextView
} else {
// Otherwise, find the TextView field within the layout
text = view.findViewById(mFieldId)
if (text == null) {
throw RuntimeException("Failed to find view with ID "
+ mContext.resources.getResourceName(mFieldId)
+ " in item layout")
}
}
} catch (e: ClassCastException) {
Log.e("ArrayAdapter", "You must supply a resource ID for a TextView")
throw IllegalStateException(
"ArrayAdapter requires the resource ID to be a TextView", e)
}
val item: DescriptiveArrayItem? = getItem(position)
text.text = item?.longDescription
return view
}
}
精彩评论