Android Spinner: évitez les appels onItemSelected lors de l'initialisation

157

J'ai créé une application Android avec un Spinneret un TextView. Je souhaite afficher l'élément sélectionné dans la liste déroulante du Spinner dans TextView. J'ai implémenté le Spinner dans la onCreateméthode, donc lorsque j'exécute le programme, il affiche une valeur dans le TextView(avant de sélectionner un élément dans la liste déroulante).

Je souhaite afficher la valeur dans TextView uniquement après avoir sélectionné un élément dans la liste déroulante. Comment puis-je faire cela?

Voici mon code:

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;

public class GPACal01Activity extends Activity implements OnItemSelectedListener {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Spinner spinner = (Spinner) findViewById(R.id.noOfSubjects);

        // Create an ArrayAdapter using the string array and a default spinner layout
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,R.array.noofsubjects_array, android.R.layout.simple_spinner_item);
        // Specify the layout to use when the list of choices appears
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        // Apply the adapter to the spinner
        spinner.setAdapter(adapter);
        spinner.setOnItemSelectedListener(this);
    }

    public void onItemSelected(AdapterView<?> parent, View arg1, int pos,long id) {
        TextView textView = (TextView) findViewById(R.id.textView1);
        String str = (String) parent.getItemAtPosition(pos);
        textView.setText(str);
    }

    public void onNothingSelected(AdapterView<?> arg0) {
        // TODO Auto-generated method stub

    }
}
Subvention
la source
les spinners ont toujours une valeur sélectionnée par défaut, si vous voulez avoir un spinner sans valeur sélectionnée par défaut, vous devez créer votre spinner personnalisé ou créer un spinner personnalisé avec une entrée vide, et dans la méthode getView (), modifier la visibilité de la mise en page brute à GONE
Houcine
5
Comment se fait-il qu'un bug aussi ennuyeux existe encore à la fin de 2018 ???
user-123

Réponses:

174
spinner.setOnItemSelectedListener(this); // Will call onItemSelected() Listener.

Donc, la première fois, gérez cela avec une valeur entière

Exemple: prendre initialement int check = 0;

public void onItemSelected(AdapterView<?> parent, View arg1, int pos,long id) {
   if(++check > 1) {
      TextView textView = (TextView) findViewById(R.id.textView1);
      String str = (String) parent.getItemAtPosition(pos);
      textView.setText(str);
   }
}

Vous pouvez le faire avec une valeur booléenne et également en vérifiant les positions actuelles et précédentes. Vois ici

Abhi
la source
2
Un homme génial ... quand j'ai rencontré ce problème pour la première fois, j'ai essayé d'implémenter un spinner personnalisé ... cela n'a toujours pas fonctionné ... mais votre solution a fonctionné comme un charme ... Merci.
Sash_KP
1
Où déclarez-vous le chèque? En dehors de getView ()? Dans le viewHolder? Où? essayé votre solution mais ne fonctionne pas pour moi.
user3718908
10
c'est un patch pas une solution je suppose.
saksham
1
Je suis confronté au même problème. Est-ce une sorte de bug? également si je sélectionne le même élément à plusieurs reprises, l'auditeur ne fonctionne pas après la première fois. Cela ne fonctionne que si l'élément de sélection change. Un commentaire, une aide?
amitava
1
J'ai essayé cette solution avec le même problème, mais cela ne fonctionne qu'un seul, par exemple: lorsque j'ai 2 éléments dans la liste déroulante, cela fonctionne lorsque je sélectionne un élément de spinner la première fois, lorsque j'essaye la deuxième fois, cela ne fonctionne pas correctement
Waseem
117

Mettez simplement cette ligne avant de définir OnItemSelectedListener

spinner.setSelection(0,false)
Dayanand Waghmare
la source
7
Ce serait une meilleure réponse si vous écrivez comment cela aide et pourquoi.
user3533716
1
Ça aide, mais tu veux comment?
AEMLoviji
14
Cela fonctionne car vous définissez d'abord la sélection, puis ajoutez un écouteur, mais l'écouteur ne sera pas appelé car vous avez déjà choisi cette sélection auparavant. Seules les nouvelles sélections appelleront l'auditeur.
développeur android
4
Cela fonctionne car les setSelection(int, boolean)appels en setSelectionInt()interne et vous devez définir l'écouteur après (au lieu d'avant) l'appel. Attention cela setSelection(int)ne fonctionnera pas, car cela appelle en setNextSelectedPositionInt()interne, et c'est ce qui m'a conduit ici.
Hai Zhang
3
Cela ne fonctionne pas s'il est déclaré pendant ou avant onCreateView (). onItemSelected sera appelé.
Arash
62

À partir du niveau d'API 3, vous pouvez utiliser onUserInteraction () sur une activité avec un booléen pour déterminer si l'utilisateur interagit avec l'appareil.

http://developer.android.com/reference/android/app/Activity.html#onUserInteraction ()

@Override
public void onUserInteraction() {
     super.onUserInteraction();
     userIsInteracting = true;
}

En tant que champ de l'activité que j'ai:

 private boolean userIsInteracting;

Enfin, mon spinner:

      mSpinnerView.setOnItemSelectedListener(new OnItemSelectedListener() {

           @Override
           public void onItemSelected(AdapterView<?> arg0, View view, int position, long arg3) {
                spinnerAdapter.setmPreviousSelectedIndex(position);
                if (userIsInteracting) {
                     updateGUI();
                }
           }

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

           }
      });

Au fur et à mesure que vous avancez dans l'activité, la valeur booléenne est réinitialisée à false. Fonctionne comme un charme.

Bill Mote
la source
3
Bon Bill..Je pense que c'est une meilleure solution que celle acceptée comme réponse
Ritesh Gune
où obtenez-vous setmPreviousSelectedIndex?!?!
TheQ
Faute de frappe? :) setPreviousSelectedIndex ()
Bill Mote
4
Cela ne fonctionnera pas en cas d'imbrication de fragments car onUserInteraction est la méthode Activity. Une autre solution?
Chitrang
1
@ErikB nvm, l'a compris. Le définir sur false à l'intérieur de l'auditeur fonctionne très bien.
Sikander
20

Cela a fonctionné pour moi

L'initialisation de Spinner dans Android est problématique, parfois le problème ci-dessus a été résolu par ce modèle.

Spinner.setAdapter();
Spinner.setSelected(false);  // must
Spinner.setSelection(0,true);  //must
Spinner.setonItemSelectedListener(this);

L'adaptateur de configuration doit être la première partie et onItemSelectedListener (this) sera le dernier lors de l'initialisation d'un spinner. Par le modèle ci-dessus, mon OnItemSelected () n'est pas appelé lors de l'initialisation du spinner

Saksham
la source
11

haha ... J'ai la même question. Quand initViews () fait comme ceci: la séquence est la clé, l'auditeur est le dernier. Bonne chance !

spinner.setAdapter(adapter);
spinner.setSelection(position);
spinner.setOnItemSelectedListener(listener);
Treesouth
la source
Bon! Rien n'a fonctionné pour moi quoi que j'ai postulé auparavant, mais celui-ci a fonctionné pour moi comme un charme. Merci @TreeSouth
Deepika Lalra
11
Pour moi travaillé spinner.setSelection (position, false) utilisé de la même manière. Avec la méthode setSelection (position), l'écouteur a été appelé lors de l'initialisation.
Mario Kutlev
2
@HugoGresse Essayez d'appeler spinner.setSelection (0, false); . Le fait est que maintenant il ignorera la sélection de cette position, car elle est déjà sélectionnée
développeur Android
8

Ma solution:

protected boolean inhibit_spinner = true;


@Override
        public void onItemSelected(AdapterView<?> arg0, View arg1,
                int pos, long arg3) {

            if (inhibit_spinner) {
                inhibit_spinner = false;
            }else {

            if (getDataTask != null) getDataTask.cancel(true);
            updateData();
            }

        }
codareee
la source
7

Pour éviter d'appeler spinner.setOnItemSelectedListener () lors de l'initialisation

spinner.setSelection(Adapter.NO_SELECTION, true); //Add this line before setting listener
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {

    }
});
Ketan Ramani
la source
6

Vous pouvez le faire de cette manière:

AdapterView.OnItemSelectedListener listener = new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
            //set the text of TextView
        }

        @Override
        public void onNothingSelected(AdapterView<?> adapterView) {

        }
    });

yourSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
            yourSpinner.setOnItemSelectedListener(listener);
        }

        @Override
        public void onNothingSelected(AdapterView<?> adapterView) {

        }
    });

Au début, je crée un auditeur et attribué à un rappel variable; puis je crée un deuxième auditeur anonyme et quand cela est appelé à un premier moment, cela change l'auditeur =]

Charleston
la source
3

L'indicateur d'interaction de l'utilisateur peut alors être défini sur true dans la méthode onTouch et réinitialisé onItemSelected()une fois que le changement de sélection a été traité. Je préfère cette solution car l'indicateur d'interaction de l'utilisateur est géré exclusivement pour le spinner, et non pour les autres vues de l'activité qui peuvent affecter le comportement souhaité.

Dans du code:

Créez votre auditeur pour le spinner:

public class SpinnerInteractionListener implements AdapterView.OnItemSelectedListener, View.OnTouchListener {

    boolean userSelect = false;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        userSelect = true;
        return false;
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
        if (userSelect) { 
            userSelect = false;
            // Your selection handling code here
        }
    }

}

Ajoutez l'auditeur à la double flèche en tant que an OnItemSelectedListeneret an OnTouchListener:

SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);
Ranjith Kumar
la source
2

Une solution simple similaire qui permet plusieurs spinners consiste à placer AdapterView dans une collection - dans la superclasse Activités - lors de la première exécution de onItemSelected (...) Ensuite, vérifiez si AdapterView est dans la collection avant de l'exécuter. Ceci active un ensemble de méthodes dans la superclasse et prend en charge plusieurs AdapterViews et par conséquent plusieurs fileurs.

Superclasse ...

private Collection<AdapterView> AdapterViewCollection = new ArrayList<AdapterView>();

   protected boolean firstTimeThrough(AdapterView parent) {
    boolean firstTimeThrough = ! AdapterViewCollection.contains(parent);
    if (firstTimeThrough) {
       AdapterViewCollection.add(parent);
     }
    return firstTimeThrough;
   }

Sous-classe ...

public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
      if (! firstTimeThrough(parent)) {
        String value = safeString(parent.getItemAtPosition(pos).toString());
        String extraMessage = EXTRA_MESSAGE;
        Intent sharedPreferencesDisplayIntent = new         Intent(SharedPreferencesSelectionActivity.this,SharedPreferencesDisplayActivity.class);
    sharedPreferencesDisplayIntent.putExtra(extraMessage,value);
    startActivity(sharedPreferencesDisplayIntent);
  }
  // don't execute the above code if its the first time through
  // do to onItemSelected being called during view initialization.

}

Jonathan Cole
la source
2

créer un champ booléen

private boolean inispinner;

à l'intérieur d'une création de l'activité

    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            if (!inispinner) {
                inispinner = true;
                return;
            }
            //do your work here
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    });
Afjalur Rahman Rana
la source
2

Essaye ça

spinner.postDelayed(new Runnable() {
        @Override
        public void run() {
            addListeners();
        }
    }, 1000);.o
Ubirajara Erthal
la source
1

Vous pouvez y parvenir par setOnTouchListener d'abord puis setOnItemSelectedListener dans onTouch

@Override
public boolean onTouch(final View view, final MotionEvent event) {
 view.setOnItemSelectedListener(this)
 return false;
}
vviieett
la source
J'aime cela. Même si cela crée un nouvel écouteur chaque fois qu'un utilisateur touche la vue. Je préfère donc mettre en cache le premier auditeur créé et le réutiliser.
Vlad
1

Cela a fonctionné pour moi:

    spinner.setSelection(0, false);
    new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                spinner.setOnItemSelectedListener(listener);
            }, 500);
Khushal
la source
1

Basé sur la réponse d'Abhi, j'ai fait cet auditeur simple

class SpinnerListener constructor(private val onItemSelected: (position: Int) -> Unit) : AdapterView.OnItemSelectedListener {

    private var selectionCount = 0

    override fun onNothingSelected(parent: AdapterView<*>?) {
        //no op
    }

    override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        if (selectionCount++ > 1) {
           onItemSelected(position)
        }
    }
}
AdrianoCelentano
la source
0

J'ai eu le même problème et cela fonctionne pour moi:

J'ai 2 spinners et je les mets à jour pendant l'initialisation et pendant les interactions avec d'autres contrôles ou après avoir obtenu des données du serveur.

Voici mon modèle:

public class MyClass extends <Activity/Fragment/Whatever> implements Spinner.OnItemSelectedListener {

    private void removeSpinnersListeners() {
        spn1.setOnItemSelectedListener(null);
        spn2.setOnItemSelectedListener(null);
    }

    private void setSpinnersListeners() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                spn1.setOnItemSelectedListener(MyClass.this);
                spn2.setOnItemSelectedListener(MyClass.this);
            }
        }, 1);
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        // Your code here
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {

    }
}

Lorsque la classe démarre, utilisez setSpinnersListeners () au lieu de définir directement l'écouteur.

Le Runnable empêchera le spinner de déclencher onItemSelected juste après que vous ayez défini leurs valeurs.

Si vous avez besoin de mettre à jour le spinner (après un appel au serveur, etc.), utilisez removeSpinnersListeners () juste avant vos lignes de mise à jour et setSpinnersListeners () juste après les lignes de mise à jour. Cela empêchera onItemSelected de se déclencher après la mise à jour.

RoyBS
la source
0

Code

spinner.setOnTouchListener(new View.OnTouchListener() { 
@Override public boolean onTouch(View view, MotionEvent motionEvent) { isSpinnerTouch=true; return false; }});

holder.spinner_from.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int slot_position, long l) {
                if(isSpinnerTouch)
                {
                    Log.d("spinner_from", "spinner_from");
                    spinnerItemClickListener.onSpinnerItemClickListener(position, slot_position, AppConstant.FROM_SLOT_ONCLICK_CODE);
                }
                else {

                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });
Messi
la source
0

Pour moi, la solution d'Abhi fonctionne très bien jusqu'au niveau Api 27.

Mais il semble qu'à partir du niveau 28 d'API, onItemSelected () ne soit pas appelé lorsque l'écouteur est défini, ce qui signifie que onItemSelected () n'est jamais appelé.

Par conséquent, j'ai ajouté une courte instruction if pour vérifier le niveau de l'API:

public void onItemSelected(AdapterView<?> parent, View arg1, int pos,long id) {

            if(Build.VERSION.SDK_INT >= 28){ //onItemSelected() doesn't seem to be called when listener is set on Api 28+
                check = 1;
            }

            if(++check > 1) {
                //Do your action here
            }
        }

Je pense que c'est assez étrange et je ne suis pas sûr que d'autres aient également ce problème, mais dans mon cas, cela a bien fonctionné.

Jannik B
la source
0

J'ai placé un TextView au-dessus du Spinner, de la même taille et du même arrière-plan que le Spinner, afin d'avoir plus de contrôle sur ce à quoi il ressemblait avant que l'utilisateur ne clique dessus. Avec le TextView là-bas, je pourrais également utiliser le TextView pour signaler lorsque l'utilisateur a commencé à interagir.

Mon code Kotlin ressemble à ceci:

private var mySpinnerHasBeenTapped = false

private fun initializeMySpinner() {

    my_hint_text_view.setOnClickListener {
        mySpinnerHasBeenTapped = true //turn flag to true
        my_spinner.performClick() //call spinner click
    }

    //Basic spinner setup stuff
    val myList = listOf("Leonardo", "Michelangelo", "Rafael", "Donatello")
    val dataAdapter: ArrayAdapter<String> = ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item, myList)
    my_spinner.adapter = dataAdapter

    my_spinner.onItemSelectedListener = object : OnItemSelectedListener {

        override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {

            if (mySpinnerHasBeenTapped) { //code below will only run after the user has clicked
                my_hint_text_view.visibility = View.GONE //once an item has been selected, hide the textView
                //Perform action here
            }
        }

        override fun onNothingSelected(parent: AdapterView<*>?) {
            //Do nothing
        }
    }
}

Le fichier de mise en page ressemble à ceci, la partie importante étant que le Spinner et TextView partagent la même largeur, hauteur et marges:

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <Spinner
                android:id="@+id/my_spinner"
                android:layout_width="match_parent"
                android:layout_height="35dp"
                android:layout_marginStart="10dp"
                android:layout_marginEnd="10dp"
                android:background="@drawable/bg_for_spinners"

                android:paddingStart="8dp"
                android:paddingEnd="30dp"
                android:singleLine="true" />

            <TextView
                android:id="@+id/my_hint_text_view"
                android:layout_width="match_parent"
                android:layout_height="35dp"                
                android:layout_marginStart="10dp"
                android:layout_marginEnd="10dp"
                android:background="@drawable/bg_for_spinners"

                android:paddingStart="8dp"
                android:paddingEnd="30dp"
                android:singleLine="true"
                android:gravity="center_vertical"
                android:text="*Select A Turtle"
                android:textColor="@color/green_ooze"
                android:textSize="16sp" />

        </FrameLayout>

Je suis sûr que les autres solutions fonctionnent là où vous ignorez le premier appel onItemSelected, mais je n'aime vraiment pas l'idée de supposer qu'il sera toujours appelé.

iOS_Mouse
la source