441 Stimmen

Wie zu halten onItemSelected von Auslösen auf eine neu instanziierte Spinner?

Ich habe mir ein paar weniger elegante Lösungen überlegt, aber ich weiß, dass ich etwas übersehen muss.

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

Ich habe sogar versucht, den Hörer in der onResume() in der Hoffnung, das würde helfen, aber das tut es nicht.

Wie kann ich verhindern, dass diese Funktion ausgelöst wird, bevor der Benutzer das Steuerelement 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.
    }
}
}

3 Stimmen

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

1 Stimmen

Eine einfache Lösung wäre, den ersten Punkt in Spinner leer und innen onItemSelected können Sie erkennen, ob die Zeichenfolge nicht leer ist, dann startActivity !

0 Stimmen

Dieses Muster funktioniert richtig stackoverflow.com/questions/13397933/

399voto

Brad Punkte 8965

Die Verwendung von Runnables ist völlig falsch.

Utilice setSelection(position, false); in der ersten Auswahl vor setOnItemSelectedListener(listener)

Auf diese Weise setzen Sie Ihre Auswahl ohne Animation, was dazu führt, dass der Listener on item selected aufgerufen wird. Aber der Listener ist null, also wird nichts ausgeführt. Dann wird Ihr Listener 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);

51 Stimmen

+1 Verborgenes Juwel! Die Übergabe von false als "animate"-Parameter führt nicht zum Aufruf des Listener-Callbacks. Genial!

3 Stimmen

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

37 Stimmen

Der Listener wird immer noch ausgelöst, wenn das Spinner-UI-Element zusammengestellt wird, so dass es unabhängig davon ausgelöst wird, die nicht das unerwünschte Verhalten durch den OP beschrieben verhindert. Dies funktioniert gut, wenn nicht während oder vor onCreateView() deklariert, aber das ist nicht das, was sie für gefragt.

208voto

casaflowa Punkte 2147

Versuchen Sie, unter Bezugnahme auf die Antwort von Dan Dyer, die OnSelectListener in einem post(Runnable) Methode:

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

Dadurch trat für mich endlich das gewünschte Verhalten ein.

In diesem Fall bedeutet dies auch, dass der Listener nur bei einem geänderten Element ausgelöst wird.

1 Stimmen

Ich erhalte eine Fehlermeldung: Die Methode setOnItemSelectedListener(AdapterView.OnItemSelectedListener) im Typ AdapterView<SpinnerAdapter> ist für die Argumente (new Runnable(){}) nicht anwendbar, woran liegt das?

0 Stimmen

Ist dies nicht im Wesentlichen eine Race Condition zwischen dem Runnable und dem UI-Thread einrichten?

6 Stimmen

@theFunkyEngineer - Dieser Code sollte von einer der Methoden des Hauptthreads ausgeführt werden, z. B. onCreate() , onResume() usw. In diesem Fall ist es ein fantastischer Trick, bei dem keine Gefahr einer Race Condition besteht. Ich verwende diesen Trick normalerweise in onCreate() direkt nach dem Layout-Code.

82voto

CommonsWare Punkte 950864

Ich hätte erwartet, dass Ihre Lösung funktioniert - ich dachte, das Auswahl-Ereignis würde nicht ausgelöst werden, wenn Sie den Adapter vor dem Einrichten des Hörers festlegen.

Ein einfaches boolesches Flag würde es Ihnen jedoch ermöglichen, das fehlerhafte erste Auswahlereignis zu erkennen und zu ignorieren.

15 Stimmen

Ugh, ja. Das ist es, was ich mit einer uneleganten Lösung meinte. Es muss doch einen besseren Weg geben. Ich danke Ihnen trotzdem.

5 Stimmen

Dieser Thread auf der Dev ml bietet mehr Einblicke in dieses Thema: groups.google.com/group/Android-developers/browse_thread/thread/ - Leider wird keine Lösung angegeben...

25 Stimmen

Der Prozess des Auslegens der Komponenten löst den Auswahl-Hörer aus. Sie müssen daher den Listener hinzufügen nach das Layout ist fertig. Ich konnte keinen geeigneten, unkomplizierten Ort finden, um dies zu tun, da das Layout anscheinend irgendwann nach onResume() y onPostResume() so dass alle normalen Haken abgeschlossen sind, wenn das Layout erfolgt.

55voto

karooolek Punkte 675

Ich habe eine kleine Utility-Methode zum Ändern von Spinner Auswahl, 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);
                }
            });
        }
    });
}

Er deaktiviert den Hörer, ändert die Auswahl und aktiviert den Hörer danach wieder.

Der Trick dabei ist, dass die Aufrufe asynchron zum UI-Thread erfolgen, so dass Sie sie in aufeinanderfolgenden Handler-Posts ausführen müssen.

0 Stimmen

Fantastisch. Ich hatte mehrere Spinner und versucht, alle ihre Hörer auf null vor der Einstellung ihrer Werte, dann habe ich alle von ihnen zurück zu dem, was sie sein sollten, aber aus irgendeinem Grund, die nicht funktionierte. versuchte diese Funktion stattdessen und es funktionierte. Ich weiß nicht, warum meins nicht funktioniert hat, aber das funktioniert, also ist es mir egal :D

6 Stimmen

Hinweis: Wenn Sie anrufen setSpinnerSelectionWithoutCallingListener zweimal schnell, so dass der zweite Aufruf erfolgt, während der erste den Hörer bereits auf null wird Ihr Spinner mit einer null Hörer für immer. Ich schlage die folgende Lösung vor: Hinzufügen if (listener == null) return; nach spinner.setSelection(selection) .

35voto

Jorrit Punkte 574

Leider scheint es, dass die beiden am häufigsten vorgeschlagenen Lösungen für dieses Problem, nämlich das Zählen von Callback-Ereignissen und das Senden eines Runnable, um den Callback zu einem späteren Zeitpunkt zu setzen, beide fehlschlagen können, wenn zum Beispiel Barrierefreiheitsoptionen aktiviert sind. Hier ist eine Hilfsklasse, die diese Probleme umgeht. Weitere Erklärungen 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 Stimmen

Dies sollte die am höchsten bewertete Antwort sein. Sie ist einfach und doch genial. Sie ermöglicht es Ihnen, Ihre gesamte aktuelle Implementierung beizubehalten, mit Ausnahme der einen Zeile, in der Sie initialisieren. Das macht das Nachrüsten älterer Projekte wirklich sehr einfach. Darüber hinaus habe ich zwei Fliegen mit einer Klappe geschlagen, indem ich die OnTouchLisener-Schnittstelle implementiert habe, um die Tastatur zu schließen, wenn der Spinner geöffnet wird. Jetzt verhalten sich alle meine Spinner genau so, wie ich es will.

0 Stimmen

Schöne Antwort. Es ist immer noch auf das 0. Element auslösen, wenn ich addAll() an den Adapter, aber mein 0. Element ist eine Ellipse für neutrale (nichts tun) Verhalten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X