Wie kann man verhindern, dass onItemSelected auf einen neu instanziierten Spinner abgefeuert wird?


418

Ich habe mir weniger elegante Wege ausgedacht, um das zu lösen, aber ich weiß, dass mir etwas fehlen muss.

Mein onItemSelectedFeuer wird sofort ohne Interaktion mit dem Benutzer ausgelöst, und dies ist ein unerwünschtes Verhalten. Ich möchte, dass die Benutzeroberfläche wartet, bis der Benutzer etwas auswählt, bevor er etwas tut.

Ich habe sogar versucht, den Listener in der einzurichten, in der onResume()Hoffnung, dass das helfen würde, aber das tut es nicht.

Wie kann ich verhindern, dass dies ausgelöst wird, bevor der Benutzer die Steuerung berühren kann?

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

2
Sie können sich diese Lösung ansehen, sie ist einfach und praktisch. stackoverflow.com/a/10102356/621951
Günay Gültekin

1
Eine einfache Lösung wäre, das erste Element Spinnerleer zu machen und darin zu onItemSelectederkennen, ob der String dann nicht leer ist startActivity!
Muhammad Babar

Dieses Muster funktioniert ordnungsgemäß stackoverflow.com/questions/13397933/…
saksham

Antworten:


78

Ich hätte erwartet, dass Ihre Lösung funktioniert - ich dachte, das Auswahlereignis würde nicht ausgelöst, wenn Sie den Adapter vor dem Einrichten des Listeners einstellen.

Abgesehen davon würde ein einfaches boolesches Flag es Ihnen ermöglichen, das erste Auswahlereignis eines Schurken zu erkennen und es zu ignorieren.


15
ugh, ja. Das habe ich mit einer uneleganten Lösung gemeint. Es scheint, als müsste es einen besseren Weg geben. Trotzdem danke.
FauxReal

5
Dieser Thread auf dem Dev ml hat mehr Einblick in diese: groups.google.com/group/android-developers/browse_thread/thread/… - Leider wird keine Lösung gegeben ...
BoD

25
Das Layout der Komponenten löst den Auswahllistener aus. Sie müssten daher den Listener hinzufügen, nachdem das Layout fertig ist. Ich konnte keinen geeigneten, unkomplizierten Ort dafür finden, da das Layout irgendwann nach onResume()und zu erfolgen scheint und onPostResume()alle normalen Hooks zum Zeitpunkt des Layouts abgeschlossen sind.
Dan Dyer

28
Ich würde mich von dieser booleschen Flagge fernhalten - als ob sich das Verhalten in Zukunft ändern würde, könnte es einen Fehler verursachen. Eine kugelsichere Lösung wäre, eine Variable mit dem "aktuell ausgewählten Index" beizubehalten, der mit dem ersten ausgewählten Element initialisiert wird. Dann bei Auswahlereignis - prüfen Sie, ob es der neuen Position entspricht - zurückkehren und nichts tun. Aktualisieren Sie natürlich die Variable bei der Auswahl.
daniel.gindi

2
Das funktioniert nicht. Antwort von @casanova funktioniert. Das sollte die akzeptierte Antwort sein.
Siddharth

379

Die Verwendung von Runnables ist völlig falsch.

Verwenden Sie setSelection(position, false);in der ersten Auswahl vorsetOnItemSelectedListener(listener)

Auf diese Weise stellen Sie Ihre Auswahl ohne Animation ein, wodurch der Listener für das ausgewählte Element aufgerufen wird. Der Listener ist jedoch null, sodass nichts ausgeführt wird. Dann wird Ihr Hörer zugewiesen.

Befolgen Sie also genau diese Reihenfolge:

Spinner s = (Spinner)Util.findViewById(view, R.id.sound, R.id.spinner);
s.setAdapter(adapter);
s.setSelection(position, false);
s.setOnItemSelectedListener(listener);

48
+1 Versteckter Edelstein! Wenn Sie false als "animate" -Parameter übergeben, wird der Listener-Rückruf nicht aufgerufen. Genial!
pkk

3
+1 Seltsame aber elegante Lösung :) Zum Glück musste ich sowieso schon setSelection aufrufen ...
Martin T.

35
Der Listener wird weiterhin ausgelöst, wenn das Spinner-UI-Element zusammengesetzt wird, sodass er unabhängig davon ausgelöst wird, was das vom OP beschriebene unerwünschte Verhalten nicht verhindert. Dies funktioniert hervorragend, wenn es nicht während oder vor onCreateView () deklariert wurde, aber das ist nicht das, wonach sie gefragt haben.
Rudi Kershaw

6
Nützlich, löst aber ein anderes Problem als das vorgestellte OP. OP bezieht sich auf ein Auswahlereignis, das (leider) automatisch ausgelöst wird, wenn die Ansicht zum ersten Mal angezeigt wird , obwohl der Programmierer setSelection nicht ausgeführt hat .
ToolmakerSteve

2
Der Parameter "false" in der Methode setSelection (..) war für mich die Lösung. ty!
Dani

194

Versuchen Sie unter Bezugnahme auf die Antwort von Dan Dyer, die OnSelectListenerin einer post(Runnable)Methode zu registrieren :

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

Auf diese Weise trat für mich endlich das gewünschte Verhalten auf.

In diesem Fall bedeutet dies auch, dass der Listener nur auf ein geändertes Element feuert.


1
Ich erhalte die Fehlermeldung: Die Methode setOnItemSelectedListener (AdapterView.OnItemSelectedListener) vom Typ AdapterView <SpinnerAdapter> gilt nicht für die Argumente (new Runnable () {}). Warum ist das so?
Jakob

Ist dies nicht im Wesentlichen eine Racebedingung zwischen dem Runnable und dem UI-Thread?
kenny_k

6
@theFunkyEngineer - Dieser Code soll von einem der Haupt - Thread Methoden zB ausgeführt werden onCreate(), onResume()usw. In diesem Fall seines fantastischen Tricks, ohne die Gefahr einer Race - Bedingung. Normalerweise verwende ich diesen Trick onCreate()direkt nach dem Layoutcode.
Richard Le Mesurier

1
Dies ist eine großartige Lösung und definitiv kein Hack! Mit dieser Art von Funktionalität werden die Dinge tief im Framework erledigt. Es ist eine Schande, dass Spinner dies nicht intern tut. Dies ist jedoch die sauberste Methode, um sicherzustellen, dass Code nach der Erstellung der Aktivität ausgeführt wird. Dies funktioniert, weil der Listener noch nicht auf dem Spinner eingestellt ist, wenn die Aktivität versucht, ihn zu benachrichtigen.
Jophde

1
Dies ist eine akzeptable Lösung . kein blinder Schuss. Andere Lösungen sind in Zukunft anfälliger für Verhaltensänderungen.
Kuldeep Singh Dhaka

50

Ich habe eine kleine Dienstprogrammmethode zum Ändern der SpinnerAuswahl erstellt, ohne den Benutzer zu benachrichtigen:

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

Es deaktiviert den Listener, ändert die Auswahl und aktiviert den Listener danach wieder.

Der Trick besteht darin, dass Aufrufe asynchron zum UI-Thread sind, sodass Sie dies in aufeinanderfolgenden Handler-Posts tun müssen.


Genial. Ich hatte mehrere Spinner und habe versucht, alle ihre Listener auf Null zu setzen, bevor ich ihre Werte festlegte. Dann habe ich sie alle auf das zurückgesetzt, was sie sein sollten, aber aus irgendeinem Grund hat das nicht funktioniert. habe stattdessen diese Funktion ausprobiert und es hat funktioniert. Ich weiß nicht, warum meine nicht funktioniert hat, aber das funktioniert, also ist es mir egal: D
JStephen

4
Bemerkenswert: Wenn Sie setSpinnerSelectionWithoutCallingListenerzweimal schnell anrufen , sodass der zweite Anruf getätigt wird, während der erste den Hörer bereits eingestellt hat null, bleibt Ihr Spinner für immer bei einem nullHörer. Ich schlage vor , das folgende Update: add if (listener == null) return;nach spinner.setSelection(selection).
Violette Giraffe

34

Leider scheinen die beiden am häufigsten vorgeschlagenen Lösungen für dieses Problem, nämlich das Zählen von Rückrufereignissen und das Posten eines Runnable zum späteren Festlegen des Rückrufs, beide fehlschlagen zu können, wenn beispielsweise Eingabehilfen aktiviert sind. Hier ist eine Hilfsklasse, die diese Probleme umgeht. Weitere Erläuterungen finden Sie im Kommentarblock.

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

3
Dies sollte die Antwort mit der höchsten Stimme sein. Es ist einfach und doch brillant. Damit können Sie alle Ihre aktuellen Implementierungen bis auf die eine Zeile, in der Sie initialisieren, beibehalten. Auf jeden Fall das Nachrüsten älterer Projekte ganz einfach gemacht. Darüber hinaus habe ich zwei Fliegen mit einer Klappe geschlagen, indem ich die OnTouchLisener-Oberfläche implementiert habe, um die Tastatur beim Öffnen des Spinners zu schließen. Jetzt verhalten sich alle meine Spinner genau so, wie ich es will.
user3829751

Schöne Antwort. Es wird immer noch zum 0. Element ausgelöst, wenn ich dem AdapterAll () hinzufüge, aber mein 0. Element ist eine Ellipse für neutrales (nichts tun) Verhalten.
jwehrle

31

Ich hatte viele Probleme mit dem Spinnerfeuer, als ich nicht wollte, und alle Antworten hier sind unzuverlässig. Sie funktionieren - aber nur manchmal. Sie werden schließlich auf Szenarien stoßen, in denen sie fehlschlagen und Fehler in Ihren Code einführen.

Für mich hat es funktioniert, den zuletzt ausgewählten Index in einer Variablen zu speichern und im Listener auszuwerten. Wenn es mit dem neu ausgewählten Index identisch ist, tun Sie nichts und kehren Sie zurück, andernfalls fahren Sie mit dem Listener fort. Mach das:

//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

}

Vertrauen Sie mir, wenn ich das sage, das ist bei weitem die zuverlässigste Lösung. Ein Hack, aber es funktioniert!


Funktioniert das überhaupt, wenn Sie versuchen, den Wert zu ändern? In meinem Fall versuche ich, den Wert auf 3 zu setzen, wenn er tatsächlich 0 ist, ohne die Änderungslistener auszulösen. Wollen Sie damit sagen, dass int i nur dann einen anderen Wert zurückgibt, wenn der Benutzer ihn auswählt?
JStephen

Hallo JStephen, ich bin mir nicht 100% sicher, was du meinst. Aber int i ist die Position des Spinners, wenn onItemSelected ausgelöst wird. Das Problem ist, dass onItemSelected beim ersten Laden des Spinners ohne tatsächliche Benutzerinteraktion ausgelöst wird, was in diesem Fall zu unerwünschtem Verhalten führt. int i ist an diesem Anfangspunkt gleich 0, da dies der Standardstartindex ist, wenn der Spinner zum ersten Mal geladen wird. Meine Lösung prüft also, ob ein tatsächlich anderes Element ausgewählt ist, anstatt dass das aktuell ausgewählte Element erneut ausgewählt wird. Beantwortet dies Ihre Frage?
Chris

Hallo Chris, ich habe eine Seite, die Informationen aus der Datenbank abruft, die der Benutzer bearbeiten kann. Wenn die Seite geöffnet wird, fülle ich die Spinner und setze ihre Positionen auf die Werte, die in der Datenbank waren. Wenn ich zum Beispiel ihre Position auf 3 setze, wird das onItemSelected ausgelöst, wobei i auf 3 gesetzt wird, was sich von der Initiale unterscheidet. Ich dachte, Sie sagten, ich sei nur eingestellt, wenn der Benutzer es tatsächlich selbst geändert hat.
JStephen

4
Was ist, wenn der Benutzer Position 0 auswählt? Sie werden ignoriert.
Yetti99

Ich denke nicht, dass der letzte Weg eine gute Idee ist. Ich initiiere Spinner, indem ich die Position aus SharedPreferences lade und setSelection verwende. Sehr oft stimmen die Werte in SharedPrefs nicht mit den Standardwerten überein, wenn die Spinner erstellt werden, sodass onItemSelected bei der Initiierung ausgelöst wird.
Arthez

26

Ich war in einer ähnlichen Situation und habe eine einfache Lösung für mich.

Es scheint wie Methoden setSelection(int position)und setSelected(int position, boolean animate)haben unterschiedliche interne Implementierung.

Wenn Sie die zweite Methode setSelected(int position, boolean animate)mit falschem Animationsflag verwenden, erhalten Sie die Auswahl, ohne den onItemSelectedListener auszulösen.


Der bessere Ansatz besteht darin, sich nicht um die zusätzlichen Aufrufe von onItemSelected zu kümmern, sondern sicherzustellen, dass die richtige Auswahl angezeigt wird. Wenn Sie also spinner.setSelection (selectedIndex) aufrufen, bevor Sie Listener hinzufügen, funktioniert dies für mich konsistent.
Andude

1
Es gibt keine setSelected-Methode (int position, boolean animate) für den Spinner
shift66

4
Der tatsächliche Anruf, den Sie benötigen, istsetSelection(int position, boolean animate);
Brad

+1 für dich. Dies löst ein allgemeineres Problem, wenn der Code mehrmals geändert wird. Spinner-Inhalt und Auswahl bleiben onItemSelected nur für Benutzerinteraktion
Alrama

4
onItemSelectedLeider ruft die falsche Animationsflagge immer noch API23 auf
mcy

23

Um die Hinweise zur Verwendung des onTouchListener zur Unterscheidung zwischen automatischen Aufrufen des setOnItemSelectedListener (die Teil der Aktivitätsinitialisierung usw. sind) und Aufrufen des durch die tatsächliche Benutzerinteraktion ausgelösten Aufrufs zu konkretisieren, habe ich Folgendes ausgeführt, nachdem ich hier und hier einige andere Vorschläge ausprobiert hatte fand heraus, dass es mit den wenigsten Codezeilen gut funktionierte.

Legen Sie einfach ein Boolesches Feld für Ihre Aktivität / Ihr Fragment fest:

private Boolean spinnerTouched = false;

Bevor Sie den setOnItemSelectedListener Ihres Spinners festlegen, legen Sie einen onTouchListener fest:

    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;

1
Das funktioniert großartig und seit Android 6+ ist dies die einzige Methode, die funktioniert. ABER Sie müssen dasselbe auch mit setOnKeyListener () tun, oder es funktioniert nicht, wenn der Benutzer mit der Tastatur navigiert.
Stéphane

Funktioniert hervorragend, alle anderen Lösungen haben Probleme mit verschiedenen Telefonen.
Ziwei Zeng

Das ist einfach und absolut perfekt! Kein zusätzlicher Unsinn nötig, denken Sie einfach an die Logik. Ich bin froh, dass ich bis hierher gescrollt habe!
Benutzer3833732

Anstelle von setOnKeyListener () können Sie in der überschriebenen preformClick () -Methode, die in beiden Fällen aufgerufen wird (touch / key), die Unterklasse spinner und das Flag spinnerTouched = true setzen. Ruhe ist das gleiche.
Allmächtiger

Ich wollte nur erwähnen, dass dies den gleichen Fehler mit DropDownPreferences zu beheben scheint, den ich kürzlich hier gepostet habe: stackoverflow.com/questions/61867118/… Ich kann es nicht glauben, tbh: D
Daniel Wilson

13
spinner.setSelection(Adapter.NO_SELECTION, false);

3
Der Code mag für sich selbst sprechen, aber ein bisschen Erklärung reicht weit :)
Nhaarman

8

Nachdem ich mir schon lange die Haare ausgezogen habe, habe ich meine eigene Spinner-Klasse erstellt. Ich habe eine Methode hinzugefügt, die den Listener entsprechend trennt und verbindet.

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

Verwenden Sie es in Ihrem XML wie folgt:

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

Alles, was Sie tun müssen, ist, die Instanz von SaneSpinner nach dem Aufblasen abzurufen und die Set-Auswahl wie folgt aufzurufen:

mMySaneSpinner.setSelection(1, true, true);

Damit wird kein Ereignis ausgelöst und die Benutzerinteraktion wird nicht unterbrochen. Dies hat meine Codekomplexität stark reduziert. Dies sollte auf Lager Android enthalten sein, da es wirklich eine PITA ist.


1
Dies funktioniert bei mir nicht, es wird immer noch onItemSelected ausgelöst.
Arthez

Arthez, bitte überprüfen Sie noch einmal, ob Sie dem dritten Argument wirklich treu bleiben. Wenn ja, stimmt hier etwas anderes nicht. Wenn möglich, geben Sie Ihren Code ein.
Fusion44

8

Keine unerwünschten Ereignisse aus der Layoutphase, wenn Sie das Hinzufügen des Listeners verschieben, bis das Layout fertig ist:

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

            });

        }
    });

Dies funktioniert und IMO ist es die sauberste Lösung für das spezifische Problem des OP. Ich möchte darauf hinweisen, dass Sie die ViewTreeObserver.OnGlobalLayoutListenerOn-Versionen unter J durch Aufrufen entfernen können. ViewTreeObserver.removeGlobalOnLayoutListenerDies ist veraltet und hat einen ähnlichen Namen wie die in dieser Antwort verwendete Methode.
Jack Meister

7

Dies geschieht, wenn Sie im Code als auswählen.

   mSpinner.setSelection(0);

Anstelle der obigen Anweisung verwenden

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

Bearbeiten: Diese Methode funktioniert nicht für Mi Android Version Mi UI.


2
Dies hat das Problem definitiv für mich gelöst. Ich habe die Dokumentation zum Spinner-Widget gelesen. Es ist absolut schwierig, den Unterschied zu verstehen: setSelection (int position, boolean animieren) -> Direkt zu einem bestimmten Element in den Adapterdaten springen. setSelection (int position) -> Legt das aktuell ausgewählte Element fest.
Matt

5

Ich habe eine sehr einfache Antwort erhalten, 100% sicher, dass es funktioniert:

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
}

3

Ich habe eine viel elegantere Lösung dafür gefunden. Dabei wird gezählt, wie oft der ArrayAdapter (in Ihrem Fall "Adapter") aufgerufen wurde. Angenommen, Sie haben 1 Spinner und rufen an:

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

Deklarieren Sie einen int-Zähler nach der onCreate-Methode und geben Sie dann innerhalb der onItemSelected () -Methode eine "if" -Bedingung ein, um zu überprüfen, wie oft der atapter aufgerufen wurde. In Ihrem Fall haben Sie es nur einmal so genannt:

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

2

Mein kleiner Beitrag ist eine Variation einiger der oben genannten Punkte, die mir einige Male gefallen hat.

Deklarieren Sie eine Ganzzahlvariable als Standardwert (oder als zuletzt verwendeten Wert, der in den Einstellungen gespeichert wurde). Verwenden Sie spinner.setSelection (myDefault), um diesen Wert festzulegen, bevor der Listener registriert wird. Überprüfen Sie in onItemSelected, ob der neue Spinner-Wert dem von Ihnen zugewiesenen Wert entspricht, bevor Sie weiteren Code ausführen.

Dies hat den zusätzlichen Vorteil, dass kein Code ausgeführt wird, wenn der Benutzer denselben Wert erneut auswählt.


1

Nachdem ich das gleiche Problem hatte, kam ich mit Tags zu diesen Lösungen. Die Idee dahinter ist einfach: Wenn der Spinner programmgesteuert geändert wird, stellen Sie sicher, dass das Tag die ausgewählte Position widerspiegelt. Im Listener prüfen Sie dann, ob die ausgewählte Position dem Tag entspricht. In diesem Fall wurde die Auswahl der Drehfelder programmgesteuert geändert.

Unten ist meine neue "Spinner Proxy" Klasse:

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

Sie benötigen außerdem eine XML-Datei mit dem Tag-Setup in Ihrem ValuesVerzeichnis. Ich habe meine Datei benannt spinner_tag.xml, aber das liegt bei Ihnen. Es sieht aus wie das:

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

Jetzt ersetzen

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

in Ihrem Code mit

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

Und lassen Sie Ihren Handler so aussehen:

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

Die Funktion isUiTriggered()gibt nur dann true zurück, wenn der Spinner vom Benutzer geändert wurde. Beachten Sie, dass diese Funktion einen Nebeneffekt hat - sie setzt das Tag, sodass ein zweiter Aufruf im selben Listener-Aufruf immer zurückkehrt false.

Dieser Wrapper behandelt auch das Problem, dass der Listener während der Layouterstellung aufgerufen wird.

Viel Spaß, Jens.


1

Da bei mir nichts funktioniert hat und ich mehr als einen Spinner in meiner Ansicht habe (und IMHO, der eine Bool-Map hält, ist ein Overkill), benutze ich das Tag, um die Klicks zu zählen:

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

1

Viele Antworten schon, hier ist meine.

Ich erweitere AppCompatSpinnerund füge eine Methode hinzu pgmSetSelection(int pos), die eine programmatische Auswahleinstellung ermöglicht, ohne einen Auswahlrückruf auszulösen. Ich habe dies mit RxJava codiert, damit die Auswahlereignisse über eine übermittelt werden 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);
    }
}

Ein Beispiel für seine Verwendung, das onCreateView()in einem FragmentBeispiel aufgerufen wird :

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

wo setSelection()ist ein Verfahren , in der umschließenden Ansicht , die so aussieht, und die von beiden Benutzerauswahlen Ereignissen über die aufgerufen wird , an Observableanderer Stelle programmatisch und auch, so die Logik für die Auswahl der Handhabung zu beiden Auswahlmethoden ist weit verbreitet.

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
}

0

Ich würde versuchen anzurufen

spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());

nachdem Sie setAdapter () aufgerufen haben. Versuchen Sie auch, vor dem Adapter anzurufen.

Sie haben immer die Lösung für die Unterklasse, bei der Sie ein boolesches Flag in Ihre überschriebene setAdapter-Methode einschließen können, um das Ereignis zu überspringen.


0

Die Lösung mit einem booleschen Flag oder einem Zähler hat mir nicht geholfen, da onItemSelected () während der Orientierungsänderung das Flag oder den Zähler "überflog".

Ich habe eine Unterklasse erstellt android.widget.Spinnerund winzige Ergänzungen vorgenommen. Die relevanten Teile sind unten. Diese Lösung hat bei mir funktioniert.

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

0

Dies ist auch keine elegante Lösung. Eigentlich ist es eher Rube-Goldberg, aber es scheint zu funktionieren. Ich stelle sicher, dass der Spinner mindestens einmal verwendet wurde, indem ich den Array-Adapter erweitere und seine getDropDownView überschreibe. In der neuen Methode getDropDownView habe ich ein boolesches Flag gesetzt, das anzeigt, dass das Dropdown-Menü mindestens einmal verwendet wurde. Ich ignoriere Anrufe an den Listener, bis das Flag gesetzt ist.

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

Array-Adapter überschreiben:

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

modifizierter Listener:

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

0

Wenn Sie Aktivitäten im laufenden Betrieb neu erstellen müssen, z. B. das Ändern von Themen, funktioniert eine einfache Flagge / ein einfacher Zähler nicht

Verwenden Sie die Funktion onUserInteraction (), um Benutzeraktivitäten zu erkennen.

Referenz: https://stackoverflow.com/a/25070696/4772917


0

Ich habe es auf einfachste Weise gemacht:

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

        }
    });

Erledigt


Es ist eine interessante Lösung. Könnte mehr Erklärung gebrauchen. Grundsätzlich wird das erste onItemSelected-Ereignis absichtlich ignoriert. In einigen Fällen funktionieren sie möglicherweise gut, in anderen jedoch nicht, wenn die Eingabehilfen aktiviert sind (siehe Jorrits Erklärung) .
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) {

     }
});

0

Das ist meine endgültige und einfach zu verwendende Lösung:

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

Verwenden Sie die Standardeinstellung setSelection(...)für das Standardverhalten oder die setSelectionWithoutInformListener(...)Auswahl eines Elements im Drehfeld, ohne den OnItemSelectedListener-Rückruf auszulösen.


0

Ich muss mSpinnerin ViewHolder verwenden, damit das Flag mOldPositionin der anonymen inneren Klasse gesetzt wird.

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

0

Ich würde den anfänglichen Index während der Erstellung des onClickListener-Objekts speichern.

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

0

Meine Lösung verwendet onTouchListener, beschränkt sich jedoch nicht auf ihre Verwendung. Es wird onTouchListenerbei Bedarf ein Wrapper für die Einrichtung erstellt 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;
    }
}

0

Ich antworte möglicherweise zu spät über den Beitrag, aber ich habe es geschafft, dies mithilfe der Android-Datenbindungsbibliothek Android Databinding zu erreichen . Ich habe eine benutzerdefinierte Bindung erstellt, um sicherzustellen, dass der Listener erst aufgerufen wird, wenn das ausgewählte Element geändert wird. Selbst wenn der Benutzer immer wieder dieselbe Position auswählt, wird das Ereignis nicht ausgelöst.

Layout-XML-Datei

    <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 Hier übergeben Sie die zu wählende Position.

Kundenspezifische Bindung

  @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) {

        }
    });
}

Weitere Informationen zur benutzerdefinierten Datenbindung finden Sie hier Android Custom Setter

HINWEIS

  1. Vergessen Sie nicht, die Datenbindung in Ihrer Gradle-Datei zu aktivieren

       android {
     ....
     dataBinding {
     enabled = true
    }
    }
  2. Fügen Sie Ihre Layoutdateien in <layout>Tags ein


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

2
1) Bitte formatieren Sie Ihren Code richtig. 2) Eine Erklärung darüber, was Ihr Code tut, wäre ebenfalls willkommen. Nicht alle Codefragmente werden beim Lesen des Codes sofort verstanden.
Mike Koch
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.