605 votes

Comment créer un Spinner Android avec le texte initial "Select One" ?

Je veux utiliser un Spinner qui affiche initialement (lorsque l'utilisateur n'a pas encore fait de sélection) le texte "Select One". Lorsque l'utilisateur clique sur la roue, la liste des éléments s'affiche et l'utilisateur sélectionne l'une des options. Une fois que l'utilisateur a effectué une sélection, l'élément sélectionné s'affiche 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" s'affiche initialement. Je pourrais simplement ajouter un nouvel élément "Select One" aux éléments, mais alors "Select One" serait également affiché dans la liste déroulante comme premier élément, ce qui n'est pas ce que je veux.

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

7 votes

La solution parfaite réside dans cette question : stackoverflow.com/questions/9863378/ Remplacez simplement la méthode getDropDownView().

0 votes

Avez-vous essayé de régler le premier élément de votre adaptateur sur "Select One" ?

0 votes

[Voici une autre solution intéressante] [1] [1] : stackoverflow.com/questions/9863378/

313voto

aaronvargas Points 1881

Ce que vous pouvez faire, c'est décorer votre SpinnerAdapter avec une autre qui présente une vue "Sélectionner une option...". pour que le Spinner s'affiche initialement sans être sélectionné.

Voici un exemple fonctionnel testé pour Android 2.3, et 4.0 (il n'utilise rien dans la bibliothèque de compatibilité, donc il devrait fonctionner pendant un certain temps) Comme c'est un décorateur, il devrait être facile de réadapter le code existant et il fonctionne bien avec CursorLoader s également. (Passez le curseur sur l'enveloppe cursorAdapter bien sûr...)

Il existe un bug Android qui rend la réutilisation des vues un peu plus difficile. (Vous devez donc utiliser le setTag ou autre chose pour garantir que votre convertView est correct). Le Spinner ne prend pas en charge les types de vues multiples

Notes de code : 2 constructeurs

Cela vous permet d'utiliser une invite standard ou de définir votre propre "rien de sélectionné" comme première ligne, ou les deux, ou aucune. (Remarque : certains thèmes affichent une liste déroulante pour un Spinner au lieu 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, en grisé...

Initial nothing selected

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

With a standard prompt

Ou avec une invite et quelque chose de dynamique (on aurait pu aussi ne pas avoir d'invite) :

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

RienSelectedSpinnerAdapter.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.
    }

}

57 votes

C'est une solution élégante au problème. Le code fonctionne exactement comme je l'ai copié et collé dans mon projet. +1 pour l'absence de réflexion nécessaire.

0 votes

Existe-t-il un projet de démonstration qui montre l'implémentation de cette classe ? J'ai du mal à la faire fonctionner. Utilisez-vous cette classe comme adaptateur, ou lui passez un adaptateur et l'utilisez ensuite ?

0 votes

@Chris, j'ai ajouté le code pour l'utilisation dans l'exemple ci-dessus, j'espère que cela aidera !

265voto

emmby Points 35359

Voici une solution générale qui remplace l'option Spinner vue. Il remplace setAdapter() pour définir la position initiale à -1, et substitue la valeur SpinnerAdapter pour afficher la chaîne d'invite pour les positions inférieures à 0.

Cette solution a été testée sur Android 1.5 à 4.2, mais attention à l'acheteur ! Parce que cette solution s'appuie sur la réflexion pour appeler la fonction privée AdapterView.setNextSelectedPositionInt() y AdapterView.setSelectedPositionInt() il n'est pas garanti que cela fonctionne dans les futures mises à jour du système d'exploitation. Il est probable qu'il fonctionnera, mais ce n'est en aucun cas garanti.

En temps normal, je n'approuverais pas ce genre de chose, mais cette question a été posée suffisamment de fois et elle semble être une demande suffisamment raisonnable pour que je pense poster 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);
        }
    }
}

0 votes

Votre méthode est très bien, mais j'ai constaté que lorsque je choisis un élément, non seulement le texte de l'élément, mais aussi l'icône de choix sont affichés dans le spinner.

8 votes

@emmby Avez-vous une idée de comment effacer la sélection après que l'utilisateur l'ait définie ? J'ai essayé de refactoriser les deux appels invoke() en une méthode clearSelection(), mais cela ne fonctionne pas vraiment. Bien que la liste popup affiche l'élément précédemment sélectionné comme non sélectionné, le widget spinner l'affiche toujours comme sélectionné, et si l'utilisateur sélectionne à nouveau le même élément, onItemSelected() n'est pas appelé.

5 votes

Quelqu'un pourrait-il expliquer comment utiliser la classe ci-dessus ?

134voto

HRJ Points 4750

J'ai fini par utiliser un Button à la place. Bien 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 identifiant de mise en page. Cela permettra de créer un meilleur aspect lors de la création de la boîte de dialogue d'alerte.

Dans le gestionnaire onClick de 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 !

10 votes

Je suis d'accord avec cette réponse. De plus, un bouton est beaucoup plus facile à coiffer qu'une Spinner.

0 votes

@HRJ J'ai implémenté la méthode que vous avez suggérée, mais l'élément qui est sélectionné avant n'est pas mis en évidence (le bouton radio doit être mis en évidence avec un point vert au milieu) ..... Comment puis-je réaliser cela dans la méthode OnClick() du bouton. Veuillez m'aider HRJ.....

2 votes

Le bouton avec cette disposition c'est parfait <Button Android:id="@+id/city" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:layout_margin="5dp" Android:gravity="left" Android:background="@Android:drawable/btn_dropdown" Android:text="@string/city_prompt" />

113voto

Shakeeb Ayaz Points 2099

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

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

L'idée est de définir le dernier élément du spinner comme élément par défaut, par exemple

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

L'élément du dernier index doit être le titre de votre spinner, par exemple "Select Country".

Et pendant que vous remplissez le spinner, diminuez le nombre d'éléments par un, c'est-à-dire que //Compter à partir de 1 pour le total des éléments.

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

Votre flux de code sera donc le suivant

List<Sting> 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) {
    super(theContext, R.id.text1, R.id.text1, objects);
}

public HintAdapter(Context theContext, List<Object> objects, int theLayoutResId) {
    super(theContext, theLayoutResId, 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;
}
}

Titre du Spinner Spinner Title Objet de la vrille Spinner Items

68voto

Casey Points 2020

Tout d'abord, vous pourriez être intéressé par le prompt de l'attribut Spinner classe. Voir l'image ci-dessous, "Choisissez 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 de sous-classer Spinner où vous pouvez gérer deux adaptateurs en interne. Un adaptateur qui a l'option "Select One", et l'autre qui a l'option "Select One". réel (avec les options réelles), puis en utilisant l'adaptateur OnClickListener pour changer les adaptateurs avant que la boîte de dialogue des choix ne s'affiche. Cependant, après avoir essayé de mettre en œuvre cette idée, j'en suis arrivé à la conclusion que vous ne pouvez pas recevoir OnClick pour le widget lui-même.

Vous pourriez intégrer le compteur rotatif dans une autre vue, intercepter les clics sur la vue, puis indiquer à l'outil CustomSpinner pour changer l'adaptateur, mais ça semble être un horrible hack.

Avez-vous vraiment besoin d'afficher "Select One" ?

37 votes

Il ne s'agit pas seulement de la nécessité d'afficher "Select One", mais aussi du cas où la valeur du spinner peut être laissée vide.

5 votes

De plus, avec cette option, la Terre est affichée comme sélection dans le Spinner avant que quoi que ce soit n'ait été choisi. Pour mon application, je préférerais que l'utilisateur puisse savoir qu'il n'a encore rien choisi.

3 votes

Cela ne répond pas vraiment à la question. Les gens cherchent un moyen pour que le spinner lui-même affiche par défaut "Select One" plutôt que le premier élément de la liste des planètes, dans cet exemple.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X