Question Comment faire un Spinner Android avec le texte initial "Select One"


Dans Android, je veux utiliser un Spinner qui initialement (lorsque l'utilisateur n'a pas encore fait de sélection) affiche le texte "Select One". Lorsque l'utilisateur clique sur le bouton rotatif, la liste des éléments s'affiche et l'utilisateur sélectionne l'une des options. Après que l'utilisateur a fait une sélection, l'élément sélectionné est affiché dans le Spinner au lieu de "Select One".

J'ai le code suivant pour créer 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);

Avec ce code, l'élément "One" est initialement affiché. Je pourrais juste ajouter un nouvel article "Select One" aux articles, mais alors "Select One" serait également affiché dans la liste déroulante en tant que premier article, ce qui n'est pas ce que je veux.

Comment puis-je résoudre ce problème?


483
2018-05-15 08:09


origine


Réponses:


Voici une solution générale qui remplace le Spinner vue. Il remplace setAdapter() pour définir la position initiale sur -1, et transmet le proxy fourni SpinnerAdapter pour afficher la chaîne d'invite pour la position inférieure à 0.

Cela a été testé sur Android 1.5 à 4.2, mais attention aux acheteurs! Parce que cette solution repose sur la réflexion pour appeler le privé AdapterView.setNextSelectedPositionInt() et AdapterView.setSelectedPositionInt(), il n'est pas garanti de fonctionner dans les futures mises à jour du système d'exploitation. Cela semble probable, mais ce n'est pas garanti.

Normalement, je ne tolérerais pas quelque chose comme ça, mais cette question a été posée assez souvent et cela semble être une demande assez raisonnable que je pensais que je posterais ma solution.

/**
 * 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);
        }
    }
}

239
2017-08-06 19:25



Ce que vous pouvez faire est de décorer votre SpinnerAdapter avec un qui présente une option 'Select Option ...' Voir initialement pour le Spinner à afficher avec rien sélectionné.

Voici un exemple de travail testé pour Android 2.3 et 4.0 (il n'utilise rien dans la bibliothèque de compatibilité, donc ça devrait aller pour un certain temps) Puisqu'il s'agit d'un décorateur, il devrait être facile de modifier le code existant et cela fonctionne bien avec CursorLoaders aussi. (Permuter le curseur sur l'enveloppe cursorAdapter bien sûr...)

Il y a un bogue Android qui rend la réutilisation des vues un peu plus difficile. (Donc, vous devez utiliser le setTag ou quelque chose d'autre pour assurer votre convertView est correct.) Spinner ne prend pas en charge plusieurs types de vue

Notes de code: 2 constructeurs

Cela vous permet d'utiliser une invite standard ou de définir votre propre «rien sélectionné» comme première ligne, ou les deux, ou aucune. (Remarque: certains thèmes affichent un DropDown pour un Spinner à la place d'une boîte de dialogue.) La liste déroulante n'affiche normalement pas l'invite)

Vous définissez une mise en page pour qu'elle ressemble à une invite, par exemple grisée ...

Initial nothing selected

En utilisant une invite standard (notez que rien n'est sélectionné):

With a standard prompt

Ou avec une invite et quelque chose de dynamique (pourrait avoir eu aucune invite aussi):

Prompt and nothing selected row

Utilisation dans l'exemple ci-dessus

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...]" />

NothingSelectedSpinnerAdapter.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) {
        // Android BUG! 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) {
        return 0;
    }

    @Override
    public int getViewTypeCount() {
        return 1;
    }

    @Override
    public long getItemId(int position) {
        return position >= EXTRA ? adapter.getItemId(position - EXTRA) : 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; // Don't allow the 'nothing selected'
                                             // item to be picked.
    }

}

265
2017-08-31 19:14



J'ai fini par utiliser un Button au lieu. Alors qu'un Button n'est pas un Spinner, le comportement est facile à personnaliser.

Commencez par créer l'adaptateur comme d'habitude:

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

Notez que j'utilise le simple_spinner_dropdown_item comme l'ID de mise en page. Cela aidera à créer un meilleur aperçu lors de la création de la boîte de dialogue d'alerte.

Dans le gestionnaire onClick pour mon bouton, j'ai:

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();
}

Et c'est tout!


123
2018-04-26 12:55



Je sais que cette question a beaucoup de réponses, mais j'ai trouvé le moyen le plus simple et le plus simple de le faire.

Cette solution est indépendant du niveau de l'API, il fonctionnera pour tous les niveaux d'API.

L'idée est de définir le dernier élément de spinner par défaut un.

spinner.setSelection(lastIndex);//index starts from 0.so if spinner has 5 item the lastIndex is 4

L'article au dernier index devrait être votre titre de spinner comme "Select Country"

Et pendant que vous remplissez le spinner, réduisez le nombre d'objets par un. // Le nombre commence de 1 à l'élément total.

    @Override
public int getCount() {
// don't display last item. It is used as hint.
int count = super.getCount();
return count > 0 ? count - 1 : count;
}

Donc, votre flux de code sera comme ça

List<String> objects = new ArrayList<String>();
objects.add("India");
objects.add("Pakistan");
objects.add("China");
// add hint as last item
objects.add("Select Country");

HintAdapter adapter = new HintAdapter(context, objects, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

 Spinner spinnerFilmType = (Spinner) findViewById(R.id.spinner);
spinner.setAdapter(adapter);

// show hint
spinner.setSelection(adapter.getCount());

HintAdapter.java

 public class HintAdapter   extends ArrayAdapter<Objects> {



public HintAdapter(Context theContext, List<Object> objects, int theLayoutResId) {
    super(theContext, theLayoutResId, objects);
}

@Override
public int getCount() {
    // don't display last item. It is used as hint.
    int count = super.getCount();
    return count > 0 ? count - 1 : count;
}
}

Titre de Spinner Spinner Title Spinner Spinner Items

EDIT: Typo


94
2018-04-11 06:46



Tout d'abord, vous pourriez être intéressé par le prompt attribut de la Spinner classe. Voir l'image ci-dessous, "Choisir une planète" est l'invite qui peut être définie dans le XML avec android:prompt="".

enter image description here

J'allais suggérer un sous-classement Spinner, où vous pouvez maintenir deux adaptateurs en interne. Un adaptateur avec l'option "Select One" et l'autre réal adaptateur (avec les options réelles), puis en utilisant le OnClickListener pour changer les adaptateurs avant que la boîte de dialogue des choix ne soit affichée. Cependant, après avoir essayé de mettre en œuvre cette idée, je suis arrivé à la conclusion que vous ne pouvez pas recevoir OnClick événements pour le widget lui-même.

Vous pouvez envelopper le fileur dans une vue différente, intercepter les clics sur la vue, puis dire à votre CustomSpinner pour changer l'adaptateur, mais semble être un hack horrible.

Avez-vous vraiment besoin de montrer "Select One"?


65
2018-05-15 22:42



Ce code a été testé et fonctionne sur Android 4.4

enter image description here

Spinner spinner = (Spinner) activity.findViewById(R.id.spinner);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity, android.R.layout.simple_spinner_dropdown_item) {

            @Override
            public View getView(int position, View convertView, ViewGroup parent) {

                View v = super.getView(position, convertView, parent);
                if (position == getCount()) {
                    ((TextView)v.findViewById(android.R.id.text1)).setText("");
                    ((TextView)v.findViewById(android.R.id.text1)).setHint(getItem(getCount())); //"Hint to be displayed"
                }

                return v;
            }       

            @Override
            public int getCount() {
                return super.getCount()-1; // you dont display last item. It is used as hint.
            }

        };

        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        adapter.add("Daily");
        adapter.add("Two Days");
        adapter.add("Weekly");
        adapter.add("Monthly");
        adapter.add("Three Months");
        adapter.add("HINT_TEXT_HERE"); //This is the text that will be displayed as hint.


        spinner.setAdapter(adapter);
        spinner.setSelection(adapter.getCount()); //set the hint the default selection so it appears on launch.
        spinner.setOnItemSelectedListener(this);

52
2017-10-20 22:04



J'ai trouvé cette solution:

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) {
    }
});

Changez simplement le tableau [0] avec "Select One", puis dans le onItemSelected, renommez-le en "One".

Pas une solution chic, mais ça marche: D


30
2017-07-20 15:54



Il n'y a pas d'API par défaut pour définir l'indicateur sur Spinner. Pour l'ajouter, nous avons besoin d'une petite solution de contournement, sans pour autant mettre en œuvre la réflexion sur la sécurité.

List<Object> objects = new ArrayList<Object>();
objects.add(firstItem);
objects.add(secondItem);
// add hint as last item
objects.add(hint);

HintAdapter adapter = new HintAdapter(context, objects, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

Spinner spinnerFilmType = (Spinner) findViewById(R.id.spinner);
spinner.setAdapter(adapter);

// show hint
spinner.setSelection(adapter.getCount());

Source de l'adaptateur:

public class HintAdapter
        extends ArrayAdapter<Objects> {

    public HintAdapter(Context theContext, List<Object> objects) {
        super(theContext, android.R.id.text1, android.R.id.text1, objects);
    }

    public HintAdapter(Context theContext, List<Object> objects, int theLayoutResId) {
        super(theContext, theLayoutResId, android.R.id.text1, objects);
    }

    @Override
    public int getCount() {
        // don't display last item. It is used as hint.
        int count = super.getCount();
        return count > 0 ? count - 1 : count;
    }
}

Source primaire


18
2018-03-31 23:44



Beaucoup de réponses ici mais je suis surpris personne n'a suggéré une solution simple: Placez un TextView sur le Spinner. Définissez un écouteur de clic sur le TextView qui cache le TextView affiche le Spinner et appelle spinner.performClick ().


10
2017-09-29 23:18



J'ai eu le même problème pour spinner, avec une sélection vide, et j'ai trouvé une meilleure solution. Jetez un oeil à ce code simple.

Spinner lCreditOrDebit = (Spinner)lCardPayView.findViewById(R.id.CARD_TYPE);
spinneradapter lAdapter = 
  new spinneradapter(
    BillPayScreen.this, 
    ndroid.R.layout.simple_spinner_item,getResources().getStringArray(R.array.creditordebit));
lAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
lCreditOrDebit.setAdapter(lAdapter);

Ici, spinneradapter est une petite personnalisation pour arrayadapter. Cela ressemble à ceci:

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;

public class spinneradapter extends ArrayAdapter<String>{
    private Context m_cContext;
    public spinneradapter(Context context,int textViewResourceId, String[] objects) {
        super(context, textViewResourceId, objects);
        this.m_cContext = context;
    }

    boolean firsttime = true;
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if(firsttime){
            firsttime = false;
            //Just return some empty view
            return new ImageView(m_cContext);
        }
        //Let the array adapter take care of it this time.
        return super.getView(position, convertView, parent);
    }
}

9
2017-10-02 08:03