English 中文(简体)
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 information 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
    }
}




相关问题
Android - ListView fling gesture triggers context menu

I m relatively new to Android development. I m developing an app with a ListView. I ve followed the info in #1338475 and have my app recognizing the fling gesture, but after the gesture is complete, ...

AsyncTask and error handling on Android

I m converting my code from using Handler to AsyncTask. The latter is great at what it does - asynchronous updates and handling of results in the main UI thread. What s unclear to me is how to handle ...

Android intent filter for a particular file extension?

I want to be able to download a file with a particular extension from the net, and have it passed to my application to deal with it, but I haven t been able to figure out the intent filter. The ...

Android & Web: What is the equivalent style for the web?

I am quite impressed by the workflow I follow when developing Android applications: Define a layout in an xml file and then write all the code in a code-behind style. Is there an equivalent style for ...

TiledLayer equivalent in Android [duplicate]

To draw landscapes, backgrounds with patterns etc, we used TiledLayer in J2ME. Is there an android counterpart for that. Does android provide an option to set such tiled patterns in the layout XML?

Using Repo with Msysgit

When following the Android Open Source Project instructions on installing repo for use with Git, after running the repo init command, I run into this error: /c/Users/Andrew Rabon/bin/repo: line ...

Android "single top" launch mode and onNewIntent method

I read in the Android documentation that by setting my Activity s launchMode property to singleTop OR by adding the FLAG_ACTIVITY_SINGLE_TOP flag to my Intent, that calling startActivity(intent) would ...

From Web Development to Android Development

I have pretty good skills in PHP , Mysql and Javascript for a junior developer. If I wanted to try my hand as Android Development do you think I might find it tough ? Also what new languages would I ...

热门标签