Comment empêcher onItemSelected de se déclencher sur un Spinner nouvellement instancié?

419

J'ai pensé à des moyens moins qu'élégants de résoudre ce problème, mais je sais que je dois manquer quelque chose.

Mon onItemSelectedse déclenche immédiatement sans aucune interaction avec l'utilisateur, et c'est un comportement indésirable. Je souhaite que l'interface utilisateur attende que l'utilisateur sélectionne quelque chose avant de faire quoi que ce soit.

J'ai même essayé de configurer l'auditeur dans le onResume(), en espérant que cela aiderait, mais ce n'est pas le cas.

Comment puis-je empêcher cela de se déclencher avant que l'utilisateur puisse toucher la commande?

public class CMSHome extends Activity { 

private Spinner spinner;

@Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // Heres my spinner ///////////////////////////////////////////
    spinner = (Spinner) findViewById(R.id.spinner);
    ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
            this, R.array.pm_list, android.R.layout.simple_spinner_item);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);
    };

public void onResume() {
    super.onResume();
    spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());
}

    public class MyOnItemSelectedListener implements OnItemSelectedListener {

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

     Intent i = new Intent(CMSHome.this, ListProjects.class);
     i.putExtra("bEmpID", parent.getItemAtPosition(pos).toString());
        startActivity(i);

        Toast.makeText(parent.getContext(), "The pm is " +
          parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show();
    }

    public void onNothingSelected(AdapterView parent) {
      // Do nothing.
    }
}
}
FauxReal
la source
2
Vous pouvez regarder cette solution, elle est simple et pratique. stackoverflow.com/a/10102356/621951
Günay Gültekin
1
Une solution simple serait de rendre le premier élément Spinnervide et à l'intérieur, onItemSelectedvous pouvez détecter si la chaîne n'est pas vide alors startActivity!
Muhammad Babar
Ce modèle fonctionne correctement stackoverflow.com/questions/13397933/…
saksham

Réponses:

78

Je m'attendais à ce que votre solution fonctionne - je pensais que l'événement de sélection ne se déclencherait pas si vous définissiez l'adaptateur avant de configurer l'écouteur.

Cela étant dit, un simple drapeau booléen vous permettrait de détecter le premier événement de sélection non autorisé et de l'ignorer.

CommonsWare
la source
15
ouais. C'est ce que je voulais dire par une solution inélégante. Il semble qu'il doit y avoir un meilleur moyen. Merci quand même.
FauxReal
5
Ce fil sur le Dev ml a plus d'informations à ce sujet: groups.google.com/group/android-developers/browse_thread/thread/… - Malheureusement, aucune solution n'est donnée ...
BoD
25
Le processus de présentation des composants déclenche l'auditeur de sélection. Vous devez donc ajouter l'écouteur une fois la mise en page terminée. Je n'ai pas été en mesure de trouver un endroit simple et approprié pour le faire, car la disposition semble se produire à un moment donné onResume()et onPostResume(), donc tous les crochets normaux sont terminés au moment où la disposition se produit.
Dan Dyer
28
Je resterais loin de ce drapeau booléen - comme si le comportement change à l'avenir, cela pourrait provoquer un bug. Une solution plus à l'épreuve des balles consisterait à conserver une variable avec "l'index actuellement sélectionné", initialisée au premier élément sélectionné. Ensuite, lors de l'événement de sélection - vérifiez si elle est égale à la nouvelle position - revenez et ne faites rien. Bien sûr, mettez à jour la variable lors de la sélection.
daniel.gindi
2
Cela ne fonctionne pas. Réponse de @casanova fonctionne. Cela devrait être la réponse acceptée.
Siddharth
379

L'utilisation de Runnables est complètement incorrecte.

Utiliser setSelection(position, false);dans la sélection initiale avantsetOnItemSelectedListener(listener)

De cette façon, vous définissez votre sélection sans animation, ce qui provoque l'appel de l'écouteur sélectionné sur l'élément. Mais l'écouteur est nul, donc rien n'est exécuté. Ensuite, votre auditeur est affecté.

Suivez donc cette séquence exacte:

Spinner s = (Spinner)Util.findViewById(view, R.id.sound, R.id.spinner);
s.setAdapter(adapter);
s.setSelection(position, false);
s.setOnItemSelectedListener(listener);
Brad
la source
48
+1 Gemme cachée! Passer false comme paramètre "animate" n'appelle pas le rappel de l'écouteur. Impressionnant!
pkk
3
+1 Solution bizarre mais élégante :) Heureusement, j'ai déjà dû appeler setSelection quand même ...
Martin T.
35
L'auditeur se déclenchera toujours lorsque l'élément UI Spinner est assemblé, il se déclenchera donc indépendamment de ce qui n'empêche pas le comportement indésirable décrit par l'OP. Cela fonctionne très bien s'il n'est pas déclaré pendant ou avant onCreateView (), mais ce n'est pas ce qu'ils ont demandé.
Rudi Kershaw
6
Utile, mais résout un problème différent de celui présenté par OP. OP fait référence à un événement de sélection qui (malheureusement) se déclenche automatiquement lorsque la vue apparaît pour la première fois, même si le programmeur n'a pas défini setSelection .
ToolmakerSteve
2
Le paramètre de valeur "faux" dans la méthode setSelection (..) était la solution pour moi. ty!
Dani
195

En vous référant à la réponse de Dan Dyer, essayez d'enregistrer le OnSelectListenerdans une post(Runnable)méthode:

spinner.post(new Runnable() {
    public void run() {
        spinner.setOnItemSelectedListener(listener);
    }
});

En faisant cela pour moi, le comportement souhaité s'est finalement produit.

Dans ce cas, cela signifie également que l'auditeur ne se déclenche que sur un élément modifié.

casaflowa
la source
1
Je reçois une erreur disant: La méthode setOnItemSelectedListener (AdapterView.OnItemSelectedListener) dans le type AdapterView <SpinnerAdapter> n'est pas applicable pour les arguments (new Runnable () {}) pourquoi est-ce?
Jakob
N'est-ce pas essentiellement mettre en place une condition de concurrence entre le Runnable et le thread d'interface utilisateur?
kenny_k
6
@theFunkyEngineer - Ce code doit être exécuté à partir de l'une des principales méthodes de thread, par exemple onCreate(), onResume()etc. Dans ce cas, c'est une astuce fantastique, sans danger de condition de concurrence. J'utilise normalement cette astuce onCreate()juste après le code de mise en page.
Richard Le Mesurier
1
C'est une excellente solution et certainement pas un hack! Ce type de fonctionnalité est la façon dont les choses sont faites profondément dans le cadre. C'est dommage que Spinner ne le fasse pas en interne. Cependant, c'est le moyen le plus propre de garantir l'exécution du code après la création de l'activité. Cela fonctionne car l'écouteur n'est pas encore défini sur le Spinner lorsque l'activité essaie de les notifier.
jophde
1
Ceci est une solution acceptable . pas un coup aveugle. d'autres solutions sont plus sujettes à des problèmes de changement de comportement à l'avenir.
Kuldeep Singh Dhaka,
50

J'ai créé une petite méthode utilitaire pour changer de Spinnersélection sans avertir l'utilisateur:

private void setSpinnerSelectionWithoutCallingListener(final Spinner spinner, final int selection) {
    final OnItemSelectedListener l = spinner.getOnItemSelectedListener();
    spinner.setOnItemSelectedListener(null);
    spinner.post(new Runnable() {
        @Override
        public void run() {
            spinner.setSelection(selection);
            spinner.post(new Runnable() {
                @Override
                public void run() {
                    spinner.setOnItemSelectedListener(l);
                }
            });
        }
    });
}

Il désactive l'écouteur, modifie la sélection et réactive l'écouteur par la suite.

L'astuce est que les appels sont asynchrones au thread d'interface utilisateur, vous devez donc le faire dans des publications de gestionnaire consécutives.

karooolek
la source
Impressionnant. J'ai eu plusieurs filateurs et j'ai essayé de mettre tous leurs auditeurs à zéro avant de définir leurs valeurs, puis je les ai tous remis à ce qu'ils étaient censés être, mais pour une raison quelconque, cela n'a pas fonctionné. essayé cette fonction à la place et cela a fonctionné. Je ne sais pas pourquoi le mien n'a pas fonctionné, mais cela fonctionne donc je m'en fous: D
JStephen
4
À noter: si vous appelez setSpinnerSelectionWithoutCallingListenerdeux fois rapidement, de sorte que le deuxième appel soit effectué alors que le premier a déjà défini l'auditeur sur null, votre spinner sera bloqué pour nulltoujours avec un écouteur. Je propose le correctif suivant: ajouter if (listener == null) return;après spinner.setSelection(selection).
Violet Giraffe
34

Malheureusement, il semble que les deux solutions les plus couramment suggérées à ce problème, à savoir le comptage des occurrences de rappel et la publication d'un Runnable pour définir le rappel ultérieurement, peuvent échouer lorsque, par exemple, les options d'accessibilité sont activées. Voici une classe d'assistance qui contourne ces problèmes. Une explication supplémentaire se trouve dans le bloc de commentaires.

import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;

/**
 * Spinner Helper class that works around some common issues 
 * with the stock Android Spinner
 * 
 * A Spinner will normally call it's OnItemSelectedListener
 * when you use setSelection(...) in your initialization code. 
 * This is usually unwanted behavior, and a common work-around 
 * is to use spinner.post(...) with a Runnable to assign the 
 * OnItemSelectedListener after layout.
 * 
 * If you do not call setSelection(...) manually, the callback
 * may be called with the first item in the adapter you have 
 * set. The common work-around for that is to count callbacks.
 * 
 * While these workarounds usually *seem* to work, the callback
 * may still be called repeatedly for other reasons while the 
 * selection hasn't actually changed. This will happen for 
 * example, if the user has accessibility options enabled - 
 * which is more common than you might think as several apps 
 * use this for different purposes, like detecting which 
 * notifications are active.
 * 
 * Ideally, your OnItemSelectedListener callback should be
 * coded defensively so that no problem would occur even
 * if the callback was called repeatedly with the same values
 * without any user interaction, so no workarounds are needed.
 * 
 * This class does that for you. It keeps track of the values
 * you have set with the setSelection(...) methods, and 
 * proxies the OnItemSelectedListener callback so your callback
 * only gets called if the selected item's position differs 
 * from the one you have set by code, or the first item if you
 * did not set it.
 * 
 * This also means that if the user actually clicks the item
 * that was previously selected by code (or the first item
 * if you didn't set a selection by code), the callback will 
 * not fire.
 * 
 * To implement, replace current occurrences of:
 * 
 *     Spinner spinner = 
 *         (Spinner)findViewById(R.id.xxx);
 *     
 * with:
 * 
 *     SpinnerHelper spinner = 
 *         new SpinnerHelper(findViewById(R.id.xxx))
 *         
 * SpinnerHelper proxies the (my) most used calls to Spinner
 * but not all of them. Should a method not be available, use: 
 * 
 *      spinner.getSpinner().someMethod(...)
 *
 * Or just add the proxy method yourself :)
 * 
 * (Quickly) Tested on devices from 2.3.6 through 4.2.2
 * 
 * @author Jorrit "Chainfire" Jongma
 * @license WTFPL (do whatever you want with this, nobody cares)
 */
public class SpinnerHelper implements OnItemSelectedListener {
    private final Spinner spinner;

    private int lastPosition = -1;
    private OnItemSelectedListener proxiedItemSelectedListener = null;  

    public SpinnerHelper(Object spinner) {
         this.spinner = (spinner != null) ? (Spinner)spinner : null;        
    }

    public Spinner getSpinner() {
        return spinner;
    }

    public void setSelection(int position) { 
        lastPosition = Math.max(-1, position);
        spinner.setSelection(position);     
    }

    public void setSelection(int position, boolean animate) {
        lastPosition = Math.max(-1, position);
        spinner.setSelection(position, animate);        
    }

    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
        proxiedItemSelectedListener = listener;
        spinner.setOnItemSelectedListener(listener == null ? null : this);
    }   

    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if (position != lastPosition) {
            lastPosition = position;
            if (proxiedItemSelectedListener != null) {
                proxiedItemSelectedListener.onItemSelected(
                        parent, view, position, id
                );
            }
        }
    }

    public void onNothingSelected(AdapterView<?> parent) {
        if (-1 != lastPosition) {
            lastPosition = -1;
            if (proxiedItemSelectedListener != null) {
                proxiedItemSelectedListener.onNothingSelected(
                        parent
                );
            }
        }
    }

    public void setAdapter(SpinnerAdapter adapter) {
        if (adapter.getCount() > 0) {
            lastPosition = 0;
        }
        spinner.setAdapter(adapter);
    }

    public SpinnerAdapter getAdapter() { return spinner.getAdapter(); } 
    public int getCount() { return spinner.getCount(); }    
    public Object getItemAtPosition(int position) { return spinner.getItemAtPosition(position); }   
    public long getItemIdAtPosition(int position) { return spinner.getItemIdAtPosition(position); }
    public Object getSelectedItem() { return spinner.getSelectedItem(); }
    public long getSelectedItemId() { return spinner.getSelectedItemId(); }
    public int getSelectedItemPosition() { return spinner.getSelectedItemPosition(); }
    public void setEnabled(boolean enabled) { spinner.setEnabled(enabled); }
    public boolean isEnabled() { return spinner.isEnabled(); }
}
Jorrit
la source
3
Ce devrait être la réponse la plus votée. C'est simple mais brillant. Il vous permet de garder toutes vos implémentations actuelles identiques, sauf la seule ligne où vous initialisez. Définitivement rendu les anciens projets plus faciles à installer. En plus de cela, j'ai tué deux oiseaux avec une pierre en implémentant l'interface OnTouchLisener pour fermer le clavier lorsque le spinner s'ouvre. Maintenant, tous mes filateurs se comportent exactement comme je le souhaite.
user3829751
Belle réponse. Il se déclenche toujours au 0ème élément lorsque j'ajoute All () à l'adaptateur mais mon 0ème élément est une ellipse pour un comportement neutre (ne rien faire).
jwehrle
31

J'ai eu BEAUCOUP de problèmes avec le tir de spinner quand je ne le voulais pas, et toutes les réponses ici ne sont pas fiables. Ils fonctionnent - mais seulement parfois. Vous finirez par rencontrer des scénarios où ils échoueront et introduire des bogues dans votre code.

Ce qui a fonctionné pour moi, c'était de stocker le dernier index sélectionné dans une variable et de l'évaluer dans l'écouteur. Si c'est la même chose que le nouvel index sélectionné, ne rien faire et retourner, sinon continuer avec l'écouteur. Faites ceci:

//Declare a int member variable and initialize to 0 (at the top of your class)
private int mLastSpinnerPosition = 0;

//then evaluate it in your listener
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {

  if(mLastSpinnerPosition == i){
        return; //do nothing
  }

  mLastSpinnerPosition = i;
  //do the rest of your code now

}

Faites-moi confiance quand je dis cela, c'est de loin la solution la plus fiable. Un hack, mais ça marche!

Chris
la source
Est-ce que cela fonctionnera même si vous essayez de changer la valeur? Dans mon cas, j'essaie de définir la valeur à quelque chose comme 3 alors qu'elle est en fait 0 sans déclencher les écouteurs de changement. Êtes-vous en train de dire que int i ne renvoie une valeur différente que si l'utilisateur la sélectionne?
JStephen
Salut JStephen, je ne suis pas sûr à 100% de ce que tu veux dire. Mais int i sera la position du spinner chaque fois que onItemSelected est déclenché. Le problème est que onItemSelected est déclenché chaque fois que le spinner est chargé pour la première fois, sans aucune interaction réelle de l'utilisateur, ce qui conduit à un comportement indésirable dans ce cas. int i sera égal à 0 à ce point initial car il s'agit de l'index de démarrage par défaut lorsque le spinner est chargé pour la première fois. Ma solution vérifie donc qu'un élément réellement différent est sélectionné au lieu que l'élément actuellement sélectionné soit resélectionné ... Est-ce que cela répond à votre question?
Chris
Salut Chris, j'ai une page qui extrait des informations de la base de données pour que l'utilisateur puisse les modifier. lorsque la page s'ouvre, je remplis les filateurs, puis je définis leurs positions sur les valeurs qui étaient dans la base de données. Donc, si je règle leur position sur 3 par exemple, cela provoque le déclenchement de onItemSelected avec i défini sur 3, ce qui est différent de l'initiale. Je pensais que vous disiez que i n'est défini que si l'utilisateur l'a modifié lui-même.
JStephen
4
Et si l'utilisateur sélectionne la position 0? Ils seront ignorés.
Yetti99
Je ne pense pas que la dernière position soit une bonne idée. J'initie les filateurs en chargeant la position à partir de SharedPreferences et en utilisant setSelection. Très souvent, les valeurs dans SharedPrefs ne sont pas les mêmes que les valeurs par défaut lorsque les filateurs sont créés, donc onItemSelected sera déclenché lors de l'initiation.
Arthez
26

J'étais dans une situation similaire et j'ai une solution simple qui fonctionne pour moi.

Cela ressemble à des méthodes setSelection(int position)et à setSelected(int position, boolean animate)une implémentation interne différente.

Lorsque vous utilisez la deuxième méthode setSelected(int position, boolean animate)avec un faux drapeau d'animation, vous obtenez la sélection sans déclencher l' onItemSelectedauditeur.

Michal
la source
La meilleure approche consiste à ne pas vous soucier des appels supplémentaires à onItemSelected, mais à vous assurer qu'il affiche la bonne sélection. Donc, appeler spinner.setSelection (selectedIndex) avant d'ajouter l'écouteur l'a fait fonctionner de manière cohérente pour moi.
andude
1
il n'y a pas de méthode setSelected (int position, boolean animate) pour spinner
shift66
4
L'appel réel dont vous avez besoin estsetSelection(int position, boolean animate);
Brad
+1 pour vous. Cela résout un problème plus général lorsque le code modifie plusieurs fois le contenu de Spinner et la sélection en gardant onItemSelected uniquement pour l'interaction avec l'utilisateur
alrama
4
tristement faux drapeau d'animation appelle toujours onItemSelecteddans API23
mcy
23

Juste pour étoffer les conseils d'utilisation de onTouchListener pour distinguer les appels automatiques à setOnItemSelectedListener (qui font partie de l'initialisation de l'activité, etc.) des appels déclenchés par une interaction réelle de l'utilisateur, j'ai fait ce qui suit après avoir essayé d'autres suggestions ici et a constaté qu'il fonctionnait bien avec le moins de lignes de code.

Définissez simplement un champ booléen pour votre activité / fragment comme:

private Boolean spinnerTouched = false;

Ensuite, juste avant de définir setOnItemSelectedListener de votre spinner, définissez un onTouchListener:

    spinner.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            System.out.println("Real touch felt.");
            spinnerTouched = true;
            return false;
        }
    });

    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    ...
         if (spinnerTouched){
         //Do the stuff you only want triggered by real user interaction.
        }
        spinnerTouched = false;
JASON G PETERSON
la source
1
Cela fonctionne très bien et depuis Android 6+, c'est la seule méthode qui fonctionne. MAIS, vous devez également faire de même avec setOnKeyListener (), ou cela ne fonctionne pas lorsque l'utilisateur navigue avec le clavier.
Stéphane
Fonctionne très bien, toutes les autres solutions ont des problèmes avec différents téléphones.
Ziwei Zeng
C'est simple et absolument parfait! Pas de bêtises supplémentaires nécessaires, gardez simplement la logique à l'esprit. Je suis content d'avoir fait défiler jusqu'à ici!
user3833732
Au lieu de setOnKeyListener (), vous pouvez sous-classer spinner et définir flag spinnerTouched = true dans la méthode preformClick () substituée, qui est appelée dans les deux cas (touch / key). Le repos est le même.
Tout
Je voulais juste mentionner que cela semble résoudre le même bogue avec DropDownPreferences que j'ai récemment publié ici: stackoverflow.com/questions/61867118/… Je ne peux pas f * cking le croire tbh: D
Daniel Wilson
13
spinner.setSelection(Adapter.NO_SELECTION, false);
j2emanue
la source
3
Le code peut parler de lui-même, mais une petite explication va un long chemin :)
nhaarman
8

Après avoir tiré mes cheveux pendant longtemps, j'ai créé ma propre classe Spinner. Je lui ai ajouté une méthode qui déconnecte et connecte l'auditeur de manière appropriée.

public class SaneSpinner extends Spinner {
    public SaneSpinner(Context context) {
        super(context);
    }

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

    public SaneSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    // set the ceaseFireOnItemClickEvent argument to true to avoid firing an event
    public void setSelection(int position, boolean animate, boolean ceaseFireOnItemClickEvent) {
        OnItemSelectedListener l = getOnItemSelectedListener();
        if (ceaseFireOnItemClickEvent) {
            setOnItemSelectedListener(null);
        }

        super.setSelection(position, animate);

        if (ceaseFireOnItemClickEvent) {
            setOnItemSelectedListener(l);
        }
    }
}

Utilisez-le dans votre XML comme ceci:

<my.package.name.SaneSpinner
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/mySaneSpinner"
    android:entries="@array/supportedCurrenciesFullName"
    android:layout_weight="2" />

Tout ce que vous avez à faire est de récupérer l'instance de SaneSpinner après l'inflation et la sélection de l'appel comme ceci:

mMySaneSpinner.setSelection(1, true, true);

Avec cela, aucun événement n'est déclenché et l'interaction de l'utilisateur n'est pas interrompue. Cela a beaucoup réduit la complexité de mon code. Cela devrait être inclus dans le stock Android car c'est vraiment un PITA.

fusion44
la source
1
Cela ne fonctionne pas pour moi, il déclenche toujours onItemSelected.
Arthez
Arthez s'il vous plaît revérifiez si vous passez vraiment fidèle au troisième argument. Si oui, quelque chose d'autre ne va pas ici. Si possible, affichez votre code.
fusion44
8

Aucun événement indésirable de la phase de mise en page si vous retardez l'ajout de l'écouteur jusqu'à la fin de la mise en page:

spinner.getViewTreeObserver().addOnGlobalLayoutListener(
    new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            // Ensure you call it only once works for JELLY_BEAN and later
            spinner.getViewTreeObserver().removeOnGlobalLayoutListener(this);

            // add the listener
            spinner.setOnItemSelectedListener(new OnItemSelectedListener() {

                @Override
                public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
                    // check if pos has changed
                    // then do your work
                }

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

            });

        }
    });
redocoder
la source
Cela fonctionne et l'OMI est la solution la plus propre au problème spécifique du PO. Je tiens à noter que vous pouvez supprimer les ViewTreeObserver.OnGlobalLayoutListenerversions sous J en appelant ViewTreeObserver.removeGlobalOnLayoutListener, ce qui est obsolète et porte un nom similaire à la méthode utilisée par cette réponse.
Jack Meister
7

Cela se produira si vous effectuez une sélection dans le code en tant que;

   mSpinner.setSelection(0);

Au lieu de l'instruction ci-dessus, utilisez

   mSpinner.setSelection(0,false);//just simply do not animate it.

Modifier: cette méthode ne fonctionne pas pour Mi Android Version Mi UI.

Uzair
la source
2
Cela a définitivement résolu le problème pour moi. J'ai lu la documentation sur le widget Spinner .. il est absolument difficile de comprendre la différence: setSelection (int position, boolean animate) -> Aller directement à un élément spécifique dans les données de l'adaptateur. setSelection (int position) -> Définit l'élément actuellement sélectionné.
Matt
5

J'ai obtenu une réponse très simple, sûre à 100%:

boolean Touched=false; // this a a global variable

public void changetouchvalue()
{
   Touched=true;
}

// this code is written just before onItemSelectedListener

 spinner.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            System.out.println("Real touch felt.");
            changetouchvalue();
            return false;
        }
    });

//inside your spinner.SetonItemSelectedListener , you have a function named OnItemSelected iside that function write the following code

if(Touched)
{
 // the code u want to do in touch event
}
user6656805
la source
3

J'ai trouvé une solution beaucoup plus élégante à cela. Il s'agit de compter le nombre de fois où ArrayAdapter (dans votre cas, "adaptateur") a été appelé. Disons que vous avez 1 spinner et que vous appelez:

int iCountAdapterCalls = 0;

ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
            this, R.array.pm_list, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);

Déclarez un compteur int après la méthode onCreate puis à l'intérieur de la méthode onItemSelected () mettez une condition "if" pour vérifier combien de fois l'atapter a été appelé. Dans votre cas, vous ne l’avez appelé qu’une seule fois:

if(iCountAdapterCalls < 1)
{
  iCountAdapterCalls++;
  //This section executes in onCreate, during the initialization
}
else
{
  //This section corresponds to user clicks, after the initialization
}
g00dy
la source
2

Ma petite contribution est une variation de certains des éléments ci-dessus qui m'a plu à quelques reprises.

Déclarez une variable entière comme valeur par défaut (ou dernière valeur utilisée enregistrée dans les préférences). Utilisez spinner.setSelection (myDefault) pour définir cette valeur avant que l'écouteur ne soit enregistré. Dans onItemSelected, vérifiez si la nouvelle valeur de spinner est égale à la valeur que vous avez affectée avant d'exécuter un autre code.

Cela présente l'avantage supplémentaire de ne pas exécuter de code si l'utilisateur sélectionne à nouveau la même valeur.

David Walton
la source
1

Après avoir eu le même problème, je suis arrivé à ces solutions en utilisant des balises. L'idée derrière elle est simple: chaque fois que le spinner est changé par programme, assurez-vous que la balise reflète la position sélectionnée. Dans l'auditeur, vous vérifiez si la position sélectionnée est égale à la balise. Si c'est le cas, la sélection de spinner a été modifiée par programme.

Ci-dessous, ma nouvelle classe "spinner proxy":

package com.samplepackage;

import com.samplepackage.R;
import android.widget.Spinner;

public class SpinnerFixed {

    private Spinner mSpinner;

    public SpinnerFixed(View spinner) {
         mSpinner = (Spinner)spinner;
         mSpinner.setTag(R.id.spinner_pos, -2);
    }

    public boolean isUiTriggered() {
         int tag = ((Integer)mSpinner.getTag(R.id.spinner_pos)).intValue();
         int pos = mSpinner.getSelectedItemPosition();
         mSpinner.setTag(R.id.spinner_pos, pos);
         return (tag != -2 && tag != pos);
    }

    public void setSelection(int position) {
        mSpinner.setTag(R.id.spinner_pos, position);
        mSpinner.setSelection(position);
    }

    public void setSelection(int position, boolean animate) {
        mSpinner.setTag(R.id.spinner_pos, position);
        mSpinner.setSelection(position, animate);
    }

    // If you need to proxy more methods, use "Generate Delegate Methods"
    // from the context menu in Eclipse.
}

Vous aurez également besoin d'un fichier XML avec la configuration de balise dans votre Valuesrépertoire. J'ai nommé mon dossier spinner_tag.xml, mais ça dépend de vous. Cela ressemble à ceci:

<resources xmlns:android="http://schemas.android.com/apk/res/android">
  <item name="spinner_pos" type="id" />
</resources>

Remplacez maintenant

Spinner myspinner;
...
myspinner = (Spinner)findViewById(R.id.myspinner);

dans votre code avec

SpinnerFixed myspinner;
...
myspinner = new SpinnerFixed(findViewById(R.id.myspinner));

Et faites ressembler votre gestionnaire à ceci:

myspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        if (myspinner.isUiTriggered()) {
            // Code you want to execute only on UI selects of the spinner
        }
    }

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

La fonction isUiTriggered()renverra true si et seulement si le spinner a été changé par l'utilisateur. Notez que cette fonction a un effet secondaire - elle définira la balise, donc un deuxième appel dans le même appel d'écoute reviendra toujours false.

Ce wrapper gérera également le problème avec l'écouteur appelé lors de la création de la disposition.

Amusez-vous, Jens.

Jens
la source
1

Étant donné que rien n'a fonctionné pour moi et que j'ai plus d'un spinner à mon avis (et à mon humble avis, tenir une carte booléenne est une exagération), j'utilise la balise pour compter les clics:

spinner.setTag(0);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            Integer selections = (Integer) parent.getTag();
            if (selections > 0) {
                // real selection
            }
            parent.setTag(++selections); // (or even just '1')
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
        }
    });
SagiLow
la source
1

Beaucoup de réponses déjà, voici la mienne.

J'étends AppCompatSpinneret j'ajoute une méthode pgmSetSelection(int pos)qui permet le réglage de sélection programmatique sans déclencher un rappel de sélection. J'ai codé cela avec RxJava afin que les événements de sélection soient livrés via un Observable.

package com.controlj.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;

import io.reactivex.Observable;

/**
 * Created by clyde on 22/11/17.
 */

public class FilteredSpinner extends android.support.v7.widget.AppCompatSpinner {
    private int lastSelection = INVALID_POSITION;


    public void pgmSetSelection(int i) {
        lastSelection = i;
        setSelection(i);
    }

    /**
     * Observe item selections within this spinner. Events will not be delivered if they were triggered
     * by a call to setSelection(). Selection of nothing will return an event equal to INVALID_POSITION
     *
     * @return an Observable delivering selection events
     */
    public Observable<Integer> observeSelections() {
        return Observable.create(emitter -> {
            setOnItemSelectedListener(new OnItemSelectedListener() {
                @Override
                public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                    if(i != lastSelection) {
                        lastSelection = i;
                        emitter.onNext(i);
                    }
                }

                @Override
                public void onNothingSelected(AdapterView<?> adapterView) {
                    onItemSelected(adapterView, null, INVALID_POSITION, 0);
                }
            });
        });
    }

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

    public FilteredSpinner(Context context, int mode) {
        super(context, mode);
    }

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

    public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public FilteredSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
        super(context, attrs, defStyleAttr, mode);
    }
}

Un exemple de son utilisation, appelé onCreateView()dans un Fragmentexemple:

    mySpinner = view.findViewById(R.id.history);
    mySpinner.observeSelections()
        .subscribe(this::setSelection);

setSelection()est une méthode dans la vue englobante qui ressemble à ceci, et qui est appelée à la fois à partir des événements de sélection des utilisateurs via le Observableet également ailleurs par programmation, de sorte que la logique de gestion des sélections est commune aux deux méthodes de sélection.

private void setSelection(int position) {
    if(adapter.isEmpty())
        position = INVALID_POSITION;
    else if(position >= adapter.getCount())
        position = adapter.getCount() - 1;
    MyData result = null;
    mySpinner.pgmSetSelection(position);
    if(position != INVALID_POSITION) {
        result = adapter.getItem(position);
    }
    display(result);  // show the selected item somewhere
}
Clyde
la source
0

J'essaierais d'appeler

spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());

après avoir appelé setAdapter (). Essayez également d'appeler avant l'adaptateur.

Vous avez toujours la solution pour aller avec le sous-classement, où vous pouvez encapsuler un indicateur booléen dans votre méthode setAdapter substituée pour ignorer l'événement.

Pentium10
la source
0

La solution avec un drapeau booléen ou un compteur ne m'a pas aidé, car lors d'un changement d'orientation onItemSelected () appelle "survoler" le drapeau ou le compteur.

J'ai sous android.widget.Spinner- classé et fait de petits ajouts. Les parties pertinentes sont ci-dessous. Cette solution a fonctionné pour moi.

private void setHandleOnItemSelected()
{
  final StackTraceElement [] elements = Thread.currentThread().getStackTrace();

  for (int index = 1; index < elements.length; index++)
  {
     handleOnItemSelected = elements[index].toString().indexOf("PerformClick") != -1; //$NON-NLS-1$

     if (handleOnItemSelected)
     {
        break;
     }
  }
}

@Override
public void setSelection(int position, boolean animate)
{
  super.setSelection(position, animate);

  setHandleOnItemSelected();
}

@Override
public void setSelection(int position)
{
  super.setSelection(position);

  setHandleOnItemSelected();
}

public boolean shouldHandleOnItemSelected()
{
  return handleOnItemSelected;
}
RobinBobin
la source
0

Ce n'est pas non plus une solution élégante. En fait c'est plutôt Rube-Goldberg mais ça semble marcher. Je m'assure que le spinner a été utilisé au moins une fois en étendant l'adaptateur de tableau et en remplaçant son getDropDownView. Dans la nouvelle méthode getDropDownView, j'ai un indicateur booléen qui est configuré pour montrer que le menu déroulant a été utilisé au moins une fois. J'ignore les appels à l'auditeur jusqu'à ce que l'indicateur soit défini.

MainActivity.onCreate ():

ActionBar ab = getActionBar();
ab.setDisplayShowTitleEnabled(false);
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
ab.setListNavigationCallbacks(null, null);

ArrayList<String> abList = new ArrayList<String>();
abList.add("line 1");
...

ArAd  abAdapt = new ArAd (this
   , android.R.layout.simple_list_item_1
   , android.R.id.text1, abList);
ab.setListNavigationCallbacks(abAdapt, MainActivity.this);

adaptateur de matrice remplacé:

private static boolean viewed = false;
private class ArAd extends ArrayAdapter<String> {
    private ArAd(Activity a
            , int layoutId, int resId, ArrayList<String> list) {
        super(a, layoutId, resId, list);
        viewed = false;
    }
    @Override
    public View getDropDownView(int position, View convertView,
            ViewGroup parent) {
        viewed = true;
        return super.getDropDownView(position, convertView, parent);
    }
}

écouteur modifié:

@Override
public boolean onNavigationItemSelected(
   int itemPosition, long itemId) {
   if (viewed) {
     ...
   }
   return false;
}
Steven Smith
la source
0

si vous devez recréer une activité à la volée, par exemple: changer de thème, un simple drapeau / compteur ne fonctionnera pas

utiliser la fonction onUserInteraction () pour détecter l'activité de l'utilisateur,

référence: https://stackoverflow.com/a/25070696/4772917

dev-gaek
la source
0

J'ai fait de la manière la plus simple:

private AdapterView.OnItemSelectedListener listener;
private Spinner spinner;

onCreate ();

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

listener = new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {

            Log.i("H - Spinner selected position", position);
        }

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

        }
    };

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

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

        }
    });

Terminé

Hiren Patel
la source
C'est une solution intéressante. Pourrait utiliser plus d'explications. Fondamentalement, il ignore intentionnellement le premier événement onItemSelected. Ils peuvent bien fonctionner dans certains cas, mais pas dans d'autres, comme lorsque les options d'accessibilité sont activées (voir l'explication de Jorrit) .
jk7
0
if () {        
       spinner.setSelection(0);// No reaction to create spinner !!!
     } else {
        spinner.setSelection(intPosition);
     }


spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

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

         if (position > 0) {
           // real selection
         }

      }

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

     }
});
Gennady Kozlov
la source
0

C'est ma solution finale et facile à utiliser:

public class ManualSelectedSpinner extends Spinner {
    //get a reference for the internal listener
    private OnItemSelectedListener mListener;

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

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

    public ManualSelectedSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
        mListener = listener;
        super.setOnItemSelectedListener(listener);
    }

    public void setSelectionWithoutInformListener(int position){
        super.setOnItemSelectedListener(null);
        super.setSelection(position);
        super.setOnItemSelectedListener(mListener);
    }

    public void setSelectionWithoutInformListener(int position, boolean animate){
        super.setOnItemSelectedListener(null);
        super.setSelection(position, animate);
        super.setOnItemSelectedListener(mListener);
    }
}

Utilisez la valeur setSelection(...)par défaut pour le comportement par défaut ou utilisez setSelectionWithoutInformListener(...)pour sélectionner un élément dans le spinner sans déclencher le rappel OnItemSelectedListener.

MatPag
la source
0

Je dois utiliser mSpinnerdans ViewHolder, donc l'indicateur mOldPositionest défini dans la classe interne anonyme.

mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            int mOldPosition = mSpinner.getSelectedItemPosition();

            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long l) {
                if (mOldPosition != position) {
                    mOldPosition = position;
                    //Do something
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {
                //Do something
            }
        });
Francis Bacon
la source
0

Je voudrais stocker l'index initial lors de la création de l'objet onClickListener.

   int thisInitialIndex = 0;//change as needed

   myspinner.setSelection(thisInitialIndex);

   myspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

      int initIndex = thisInitialIndex;

      @Override
      public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
         if (id != initIndex) { //if selectedIndex is the same as initial value
            // your real onselecteditemchange event
         }
      }

      @Override
      public void onNothingSelected(AdapterView<?> parent) {
      }
  });
Ray Lionfang
la source
0

Ma solution utilise onTouchListenermais ne restreint pas son utilisation. Il crée un wrapper pour onTouchListenersi nécessaire où l'installation onItemSelectedListener.

public class Spinner extends android.widget.Spinner {
    /* ...constructors... */

    private OnTouchListener onTouchListener;
    private OnItemSelectedListener onItemSelectedListener;

    @Override
    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
        onItemSelectedListener = listener;
        super.setOnTouchListener(wrapTouchListener(onTouchListener, onItemSelectedListener));
    }

    @Override
    public void setOnTouchListener(OnTouchListener listener) {
        onTouchListener = listener;
        super.setOnTouchListener(wrapTouchListener(onTouchListener, onItemSelectedListener));
    }

    private OnTouchListener wrapTouchListener(final OnTouchListener onTouchListener, final OnItemSelectedListener onItemSelectedListener) {
        return onItemSelectedListener != null ? new OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                Spinner.super.setOnItemSelectedListener(onItemSelectedListener);
                return onTouchListener != null && onTouchListener.onTouch(view, motionEvent);
            }
        } : onTouchListener;
    }
}
Dem0n13
la source
0

Je réponds peut-être trop tard au cours de la publication, mais j'ai réussi à y parvenir en utilisant la bibliothèque de liaison de données Android Android Databinding . J'ai créé une liaison personnalisée pour m'assurer que l'écouteur n'est pas appelé jusqu'à ce que l'élément sélectionné soit modifié, même si l'utilisateur sélectionne la même position encore et encore, l'événement n'est pas déclenché.

Fichier xml de mise en page

    <layout>
  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin"
xmlns:app="http://schemas.android.com/apk/res-auto">


<Spinner
    android:id="@+id/spinner"
    android:layout_width="150dp"
    android:layout_height="wrap_content"
    android:spinnerMode="dropdown"
    android:layout_below="@id/member_img"
    android:layout_marginTop="@dimen/activity_vertical_margin"
    android:background="@drawable/member_btn"
    android:padding="@dimen/activity_horizontal_margin"
    android:layout_marginStart="@dimen/activity_horizontal_margin"
    android:textColor="@color/colorAccent"
    app:position="@{0}"
    />
 </RelativeLayout>
 </layout>

app:position est l'endroit où vous passez la position à sélectionner.

Reliure personnalisée

  @BindingAdapter(value={ "position"}, requireAll=false)
  public static void setSpinnerAdapter(Spinner spinner, int selected) 
  {

    final int [] selectedposition= new int[1];
    selectedposition[0]=selected;


    // custom adapter or you can set default adapter
        CustomSpinnerAdapter customSpinnerAdapter = new CustomSpinnerAdapter(spinner.getContext(), <arraylist you want to add to spinner>);
        spinner.setAdapter(customSpinnerAdapter);
            spinner.setSelection(selected,false);


    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

            String item = parent.getItemAtPosition(position).toString();
        if( position!=selectedposition[0]) {
                        selectedposition[0]=position;
            // do your stuff here
                    }
                }


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

        }
    });
}

Vous pouvez en savoir plus sur la liaison de données personnalisées ici Android Setter personnalisé

REMARQUE

  1. N'oubliez pas d'activer la liaison de données dans votre fichier Gradle

       android {
     ....
     dataBinding {
     enabled = true
    }
    }
  2. Inclure vos fichiers de mise en page dans les <layout>balises

N.Moudgil
la source
-1
mYear.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View arg1, int item, long arg3) {
                if (mYearSpinnerAdapter.isEnabled(item)) {

                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
            }
        });
Saurabh Malik
la source
2
1) Veuillez formater correctement votre code. 2) Une explication sur ce que fait votre code serait également appréciée. Tous les extraits de code ne sont pas compris immédiatement lors de la lecture du code.
Mike Koch du