575 votos

Cómo hacer un Girador Androide con el texto inicial "Seleccione uno"

En Android, quiero usar un Spinner que inicialmente (cuando el usuario aún no ha hecho una selección) muestre el texto "Select One". Cuando el usuario hace clic en el spinner, se muestra la lista de elementos y el usuario selecciona una de las opciones. Una vez que el usuario ha hecho una selección, el elemento seleccionado se muestra en el Girador en lugar de "Seleccione uno".

Tengo el siguiente código para crear un Spinner:

String[] items = new String[] {"One", "Two", "Three"};
Spinner spinner = (Spinner) findViewById(R.id.mySpinner);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
            android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);

Con este código, inicialmente se muestra el elemento "Uno". Podría añadir un nuevo elemento "Seleccione uno" a los elementos, pero entonces "Seleccione uno" también se mostraría en la lista desplegable como primer elemento, que no es lo que quiero.

¿Cómo puedo solucionar este problema?

295voto

aaronvargas Puntos 1881

Lo que puedes hacer es decorar tu SpinnerAdapter con uno que presente una "Opción de selección"... Ver inicialmente para que el Spinner se muestre sin nada seleccionado.

Aquí hay un ejemplo de trabajo probado para Android 2.3, y 4.0 (no usa nada en la biblioteca de compatibilidad, así que debería estar bien por un tiempo) Como es un decorador, debería ser fácil actualizar el código existente y funciona bien con CursorLoaders también. (Intercambiar el cursor en el adaptador del cursor envuelto, por supuesto...)

Hay un bicho androide que hace que sea un poco más difícil reutilizar las vistas. (Así que tienes que usar el setTag o alguna otra cosa para asegurarte de que tu convertView es correcta). Spinner no soporta múltiples tipos de vista

Notas de código: 2 constructores

Esto te permite usar un aviso estándar o definir tu propio "nada seleccionado" como la primera fila, o ambos, o ninguno. (Nota: Algunos temas muestran un DropDown para un Spinner en lugar de un diálogo. El menú desplegable normalmente no muestra el aviso)

Se define un diseño para que "se vea" como un aviso, por ejemplo, en gris...

Initial nothing selected

Usando un aviso estándar (note que no se selecciona nada):

With a standard prompt

O con una puntualidad y algo dinámico (podría no haber tenido también una puntualidad):

Prompt and nothing selected row

Uso en el ejemplo anterior

    Spinner spinner = (Spinner) findViewById(R.id.spinner);
    ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.planets_array, android.R.layout.simple_spinner_item);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setPrompt("Select your favorite Planet!");

    spinner.setAdapter(
      new NothingSelectedSpinnerAdapter(
            adapter,
            R.layout.contact_spinner_row_nothing_selected,
            // R.layout.contact_spinner_nothing_selected_dropdown, // Optional
            this));

contact_spinner_row_nothing_selected.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    style="?android:attr/spinnerItemStyle"
    android:singleLine="true"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:ellipsize="marquee"
    android:textSize="18sp"
    android:textColor="#808080"
    android:text="[Select a Planet...]" />

NadaSeleccionadoAdaptador de espinas.java

import android.content.Context;
import android.database.DataSetObserver;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;
import android.widget.SpinnerAdapter;

/**
 * Decorator Adapter to allow a Spinner to show a 'Nothing Selected...' initially
 * displayed instead of the first choice in the Adapter.
 */
public class NothingSelectedSpinnerAdapter implements SpinnerAdapter, ListAdapter {

    protected static final int EXTRA = 1;
    protected SpinnerAdapter adapter;
    protected Context context;
    protected int nothingSelectedLayout;
    protected int nothingSelectedDropdownLayout;
    protected LayoutInflater layoutInflater;

    /**
     * Use this constructor to have NO 'Select One...' item, instead use
     * the standard prompt or nothing at all.
     * @param spinnerAdapter wrapped Adapter.
     * @param nothingSelectedLayout layout for nothing selected, perhaps
     * you want text grayed out like a prompt...
     * @param context
     */
    public NothingSelectedSpinnerAdapter(
      SpinnerAdapter spinnerAdapter,
      int nothingSelectedLayout, Context context) {

        this(spinnerAdapter, nothingSelectedLayout, -1, context);
    }

    /**
     * Use this constructor to Define your 'Select One...' layout as the first
     * row in the returned choices.
     * If you do this, you probably don't want a prompt on your spinner or it'll
     * have two 'Select' rows.
     * @param spinnerAdapter wrapped Adapter. Should probably return false for isEnabled(0)
     * @param nothingSelectedLayout layout for nothing selected, perhaps you want
     * text grayed out like a prompt...
     * @param nothingSelectedDropdownLayout layout for your 'Select an Item...' in
     * the dropdown.
     * @param context
     */
    public NothingSelectedSpinnerAdapter(SpinnerAdapter spinnerAdapter,
            int nothingSelectedLayout, int nothingSelectedDropdownLayout, Context context) {
        this.adapter = spinnerAdapter;
        this.context = context;
        this.nothingSelectedLayout = nothingSelectedLayout;
        this.nothingSelectedDropdownLayout = nothingSelectedDropdownLayout;
        layoutInflater = LayoutInflater.from(context);
    }

    @Override
    public final View getView(int position, View convertView, ViewGroup parent) {
        // This provides the View for the Selected Item in the Spinner, not
        // the dropdown (unless dropdownView is not set).
        if (position == 0) {
            return getNothingSelectedView(parent);
        }
        return adapter.getView(position - EXTRA, null, parent); // Could re-use
                                                 // the convertView if possible.
    }

    /**
     * View to show in Spinner with Nothing Selected
     * Override this to do something dynamic... e.g. "37 Options Found"
     * @param parent
     * @return
     */
    protected View getNothingSelectedView(ViewGroup parent) {
        return layoutInflater.inflate(nothingSelectedLayout, parent, false);
    }

    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        // BUG! Vote to fix!! http://code.google.com/p/android/issues/detail?id=17128 -
        // Spinner does not support multiple view types
        if (position == 0) {
            return nothingSelectedDropdownLayout == -1 ?
              new View(context) :
              getNothingSelectedDropdownView(parent);
        }

        // Could re-use the convertView if possible, use setTag...
        return adapter.getDropDownView(position - EXTRA, null, parent);
    }

    /**
     * Override this to do something dynamic... For example, "Pick your favorite
     * of these 37".
     * @param parent
     * @return
     */
    protected View getNothingSelectedDropdownView(ViewGroup parent) {
        return layoutInflater.inflate(nothingSelectedDropdownLayout, parent, false);
    }

    @Override
    public int getCount() {
        int count = adapter.getCount();
        return count == 0 ? 0 : count + EXTRA;
    }

    @Override
    public Object getItem(int position) {
        return position == 0 ? null : adapter.getItem(position - EXTRA);
    }

    @Override
    public int getItemViewType(int position) {
        // Doesn't work!! Vote to Fix! http://code.google.com/p/android/issues/detail?id=17128 -
        // Spinner does not support multiple view types
        // This method determines what is the convertView, this should
        // return 1 for pos 0 or return 0 otherwise.
        return position == 0 ?
               getViewTypeCount() - EXTRA :
               adapter.getItemViewType(position - EXTRA);
    }

    @Override
    public int getViewTypeCount() {
        return adapter.getViewTypeCount() + EXTRA;
    }

    @Override
    public long getItemId(int position) {
        return adapter.getItemId(position - EXTRA);
    }

    @Override
    public boolean hasStableIds() {
        return adapter.hasStableIds();
    }

    @Override
    public boolean isEmpty() {
        return adapter.isEmpty();
    }

    @Override
    public void registerDataSetObserver(DataSetObserver observer) {
        adapter.registerDataSetObserver(observer);
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer) {
        adapter.unregisterDataSetObserver(observer);
    }

    @Override
    public boolean areAllItemsEnabled() {
        return false;
    }

    @Override
    public boolean isEnabled(int position) {
        return position == 0 ? false : true; // Don't allow the 'nothing selected'
                                             // item to be picked.
    }

}

256voto

emmby Puntos 35359

Aquí hay una solución general que anula la vista de Spinner. Anula el setAdapter() para establecer la posición inicial en -1, y representa el SpinnerAdapter suministrado para mostrar la cadena de indicaciones para la posición inferior a 0.

Esto ha sido probado en Android 1.5 a 4.2, pero cuidado con el comprador! Debido a que esta solución se basa en la reflexión para llamar a los privados AdapterView.setNextSelectedPositionInt() y AdapterView.setSelectedPositionInt(), no se garantiza que funcione en futuras actualizaciones del sistema operativo. Parece probable que lo haga, pero no está de ninguna manera garantizado.

Normalmente no aprobaría algo así, pero esta pregunta ya se ha hecho suficientes veces y parece una petición bastante razonable que pensé en publicar mi solución.

/**
 * A modified Spinner that doesn't automatically select the first entry in the list.
 *
 * Shows the prompt if nothing is selected.
 *
 * Limitations: does not display prompt if the entry list is empty.
 */
public class NoDefaultSpinner extends Spinner {

    public NoDefaultSpinner(Context context) {
        super(context);
    }

    public NoDefaultSpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public NoDefaultSpinner(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void setAdapter(SpinnerAdapter orig ) {
        final SpinnerAdapter adapter = newProxy(orig);

        super.setAdapter(adapter);

        try {
            final Method m = AdapterView.class.getDeclaredMethod(
                               "setNextSelectedPositionInt",int.class);
            m.setAccessible(true);
            m.invoke(this,-1);

            final Method n = AdapterView.class.getDeclaredMethod(
                               "setSelectedPositionInt",int.class);
            n.setAccessible(true);
            n.invoke(this,-1);
        } 
        catch( Exception e ) {
            throw new RuntimeException(e);
        }
    }

    protected SpinnerAdapter newProxy(SpinnerAdapter obj) {
        return (SpinnerAdapter) java.lang.reflect.Proxy.newProxyInstance(
                obj.getClass().getClassLoader(),
                new Class[]{SpinnerAdapter.class},
                new SpinnerAdapterProxy(obj));
    }

    /**
     * Intercepts getView() to display the prompt if position < 0
     */
    protected class SpinnerAdapterProxy implements InvocationHandler {

        protected SpinnerAdapter obj;
        protected Method getView;

        protected SpinnerAdapterProxy(SpinnerAdapter obj) {
            this.obj = obj;
            try {
                this.getView = SpinnerAdapter.class.getMethod(
                                 "getView",int.class,View.class,ViewGroup.class);
            } 
            catch( Exception e ) {
                throw new RuntimeException(e);
            }
        }

        public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
            try {
                return m.equals(getView) && 
                       (Integer)(args[0])<0 ? 
                         getView((Integer)args[0],(View)args[1],(ViewGroup)args[2]) : 
                         m.invoke(obj, args);
            } 
            catch (InvocationTargetException e) {
                throw e.getTargetException();
            } 
            catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        protected View getView(int position, View convertView, ViewGroup parent) 
          throws IllegalAccessException {

            if( position<0 ) {
                final TextView v = 
                  (TextView) ((LayoutInflater)getContext().getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE)).inflate(
                      android.R.layout.simple_spinner_item,parent,false);
                v.setText(getPrompt());
                return v;
            }
            return obj.getView(position,convertView,parent);
        }
    }
}

131voto

HRJ Puntos 4750

Terminé usando un Button en su lugar. Mientras que un Button no es un Spinner el comportamiento es fácil de personalizar.

Primero crea el adaptador como de costumbre:

String[] items = new String[] {"One", "Two", "Three"};
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
        android.R.layout.simple_spinner_dropdown_item, items);

Tenga en cuenta que estoy usando el simple_spinner_dropdown_item como la identificación de la disposición. Esto ayudará a crear un mejor aspecto al crear el diálogo de alerta.

En el manejador de onClick para mi botón tengo:

public void onClick(View w) {
  new AlertDialog.Builder(this)
  .setTitle("the prompt")
  .setAdapter(adapter, new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {

      // TODO: user specific action

      dialog.dismiss();
    }
  }).create().show();
}

¡Y eso es todo!

67voto

Casey Puntos 2020

Primero, puede que te interese el atributo "prompt" de la clase Spinner. Mira la imagen de abajo, "Elige un Planeta" es el prompt que se puede establecer en el xml con Android:prompt=""

Iba a sugerir la subclasificación de Spinner, donde podrías mantener dos adaptadores internamente. Un adaptador que tiene la opción "Seleccionar uno", y el otro real adaptador (con las opciones reales), luego usando el OnClickListener para cambiar los adaptadores antes de que se muestre el diálogo de opciones sin embargo, después de intentar implementar esa idea he llegado a la conclusión de que no se pueden recibir eventos OnClick para el propio widget.

Podrías envolver el spinner en una vista diferente, interceptar los clics en la vista, y luego decirle a tu CustomSpinner que cambie el adaptador, pero parece un hack horrible.

¿Realmente necesitas mostrar "Selecciona uno"?

prompt example

31voto

Encontré esta solución:

String[] items = new String[] {"Select One", "Two", "Three"};
Spinner spinner = (Spinner) findViewById(R.id.mySpinner);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
            android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);

spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> arg0, View arg1, int position, long id) {
        items[0] = "One";
        selectedItem = items[position];
    }

    @Override
    public void onNothingSelected(AdapterView<?> arg0) {
    }
});

Sólo cambia el Array[0] con "Select One" y luego en el onItemSelected, renómbralo a "One".

No es una solución con clase, pero funciona.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X