Come mantenere OnItemSelected dallo sparo su uno Spinner appena istanziato?


419

Ho pensato ad alcuni modi meno che eleganti per risolverlo, ma so che mi manca qualcosa.

Il mio onItemSelectedsparo immediatamente senza alcuna interazione con l'utente, e questo è un comportamento indesiderato. Desidero che l'interfaccia utente attenda fino a quando l'utente seleziona qualcosa prima di fare qualsiasi cosa.

Ho anche provato a impostare l'ascoltatore in onResume(), sperando che potesse aiutare, ma non lo fa.

Come posso evitare che si attivi prima che l'utente possa toccare il controllo?

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
Puoi guardare questa soluzione, è semplice e pratica. stackoverflow.com/a/10102356/621951
Günay Gültekin

1
Una soluzione semplice sarebbe quella di rendere Spinnervuoto il primo elemento e al onItemSelectedsuo interno è possibile rilevare se la stringa non è vuota allora startActivity!
Muhammad Babar,

Questo modello funziona correttamente stackoverflow.com/questions/13397933/...~~V~~singular~~3rd
saksham

Risposte:


78

Mi sarei aspettato che la tua soluzione funzionasse, anche se l'evento di selezione non si attiva se si imposta l'adattatore prima di configurare l'ascoltatore.

Detto questo, una semplice bandiera booleana ti consentirebbe di rilevare il primo evento di selezione canaglia e ignorarlo.


15
sì, sì. Questo è ciò che intendevo per soluzione non elegante. Sembra che ci debba essere un modo migliore. Grazie comunque.
FauxReal

5
Questo thread sul Dev ml ha ulteriori approfondimenti al riguardo: groups.google.com/group/android-developers/browse_thread/thread/… - Purtroppo non viene fornita alcuna soluzione ...
CdA

25
Il processo di disposizione dei componenti genera l'ascoltatore di selezione. Dovresti quindi aggiungere l'ascoltatore dopo aver completato il layout. Non sono stato in grado di trovare un posto adatto e semplice per farlo poiché il layout sembra accadere ad un certo punto dopo , onResume()e onPostResume()quindi tutti i normali ganci si sono completati quando si verifica il layout.
Dan Dyer,

28
Starei lontano da questa bandiera booleana, come se il comportamento dovesse cambiare in futuro, potrebbe causare un bug. Una soluzione più a prova di proiettile sarebbe quella di mantenere una variabile con l '"indice attualmente selezionato", inizializzato sul primo elemento selezionato. Quindi, durante l'evento di selezione - controlla se è uguale alla nuova posizione - ritorna e non fare nulla. Naturalmente aggiorna la variabile alla selezione.
daniel.gindi,

2
Questo non funziona. Risposta di @casanova funziona. Questa dovrebbe essere la risposta accettata.
Siddharth,

379

L'uso di Runnables è completamente errato.

Utilizzare setSelection(position, false);nella selezione iniziale primasetOnItemSelectedListener(listener)

In questo modo si imposta la selezione senza animazione che provoca la chiamata al listener selezionato sull'elemento. Ma l'ascoltatore è null quindi non viene eseguito nulla. Quindi viene assegnato il tuo ascoltatore.

Quindi segui questa sequenza esatta:

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

48
+1 gemma nascosta! Passare false come parametro "animate" non chiama il callback del listener. Eccezionale!
pkk,

3
+1 Soluzione strana ma elegante :) Per fortuna, ho già dovuto chiamare setSelection comunque ...
Martin T.,

35
L'ascoltatore si attiverà comunque quando l'elemento UI Spinner è assemblato, quindi si attiverà indipendentemente dal fatto che non impedisce il comportamento indesiderato descritto dall'OP. Funziona alla grande se non dichiarato durante o prima su OnCreateView (), ma non è quello che hanno chiesto.
Rudi Kershaw,

6
Utile, ma risolve un problema diverso rispetto a OP presentato. OP si riferisce a un evento di selezione che (sfortunatamente) si attiva automaticamente alla prima visualizzazione della vista anche se il programmatore non ha impostato setSelection .
ToolmakerSteve

2
Il parametro del valore "false" nel metodo setSelection (..) era la soluzione per me. ty!
Dani,

195

Facendo riferimento alla risposta di Dan Dyer, provare a registrare il OnSelectListenerin un post(Runnable)metodo di:

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

Facendolo per me il comportamento desiderato finalmente si è verificato.

In questo caso significa anche che l'ascoltatore si attiva solo su un elemento modificato.


1
Viene visualizzato un errore che dice: il metodo setOnItemSelectedListener (AdapterView.OnItemSelectedListener) nel tipo AdapterView <SpinnerAdapter> non è applicabile per gli argomenti (nuovo Runnable () {}) perché?
Jakob,

Questo non sta essenzialmente impostando una condizione di competizione tra Runnable e UI Thread?
kenny_k,

6
@theFunkyEngineer - Questo codice dovrebbe essere eseguito da uno dei metodi di thread principali, ad esempio onCreate(), onResume()ecc. In tal caso, è un trucco fantastico, senza pericolo di condizioni di gara. Normalmente uso questo trucco onCreate()subito dopo il codice del layout.
Richard Le Mesurier,

1
Questa è un'ottima soluzione e sicuramente non un trucco! Questo tipo di funzionalità è il modo in cui le cose vengono fatte in profondità nel framework. È un peccato che Spinner non lo faccia internamente. Tuttavia, questo è il modo più pulito per garantire l'esecuzione del codice dopo la creazione dell'attività. Questo funziona perché il listener non è ancora impostato sullo Spinner quando l'attività tenta di avvisarli.
jophde,

1
Questa è una soluzione accettabile . non un colpo cieco. altre soluzioni sono più inclini al problema del cambiamento di comportamento in futuro.
Kuldeep Singh Dhaka,

50

Ho creato un piccolo metodo di utilità per modificare la Spinnerselezione senza avvisare l'utente:

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

Disabilita il listener, modifica la selezione e riattiva successivamente il listener.

Il trucco è che le chiamate sono asincrone al thread dell'interfaccia utente, quindi devi farlo in post di gestori consecutivi.


Eccezionale. Ho avuto più spinner e ho provato a impostare tutti i loro ascoltatori su null prima di impostare i loro valori, quindi li ho riportati tutti su quello che avrebbero dovuto essere, ma per qualche ragione non ha funzionato. ho provato invece questa funzione e ha funzionato. Non so perché il mio non ha funzionato, ma funziona così non mi interessa: D
JStephen

4
Nota: se si chiama setSpinnerSelectionWithoutCallingListenerdue volte rapidamente, in modo che venga effettuata la seconda chiamata mentre la prima ha già impostato l'ascoltatore null, lo spinner rimarrà bloccato con un nullascoltatore per sempre. Propongo la seguente correzione: aggiungere if (listener == null) return;dopo spinner.setSelection(selection).
Violet Giraffe

34

Sfortunatamente sembra che le due soluzioni più comunemente suggerite a questo problema, vale a dire il conteggio delle occorrenze di callback e la pubblicazione di un Runnable per impostare il callback in un secondo momento, possano fallire entrambe quando sono abilitate, ad esempio, le opzioni di accessibilità. Ecco una classe di supporto che risolve questi problemi. Ulteriore spiegazione è nel blocco dei commenti.

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
Questa dovrebbe essere la risposta più votata. È semplice ma geniale. Ti consente di mantenere invariata tutta la tua attuale implementazione, ad eccezione di una riga in cui esegui l'inizializzazione. Sicuramente i progetti più vecchi che si adattano al passato sono abbastanza facili. Inoltre, ho ucciso due piccioni con una fava implementando l'interfaccia OnTouchLisener per chiudere la tastiera quando si apre lo spinner. Ora tutti i miei filatori si comportano esattamente come voglio.
user3829751

Bella risposta. Sta ancora innescando il 0 ° elemento quando aggiungo All () all'adattatore ma il mio 0 ° elemento è un puntino di sospensione per un comportamento neutro (non faccio nulla).
jwehrle,

31

Ho avuto MOLTI problemi con il lancio di spinner di quando non volevo, e tutte le risposte qui sono inaffidabili. Funzionano, ma solo a volte. Alla fine ti imbatterai in scenari in cui falliranno e introducerai bug nel tuo codice.

Ciò che ha funzionato per me è stato archiviare l'ultimo indice selezionato in una variabile e valutarlo nel listener. Se è uguale al nuovo indice selezionato, non fare nulla e tornare, altrimenti continua con l'ascoltatore. Fai questo:

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

}

Fidati di me quando dico questo, questa è di gran lunga la soluzione più affidabile. Un trucco, ma funziona!


Funzionerà anche se stai cercando di cambiare il valore? Nel mio caso sto cercando di impostare il valore su qualcosa come 3 quando in realtà è 0 senza attivare i listener di modifica. Stai dicendo che int i restituisce un valore diverso solo se l'utente lo sta selezionando?
JStephen,

Ciao JStephen, non sono sicuro al 100% di cosa intendi. Ma int i sarà la posizione dello spinner ogni volta che onItemSelected viene attivato. Il problema è che onItemSelected viene attivato ogni volta che lo spinner viene caricato per la prima volta, senza alcuna effettiva interazione dell'utente, portando in questo caso a comportamenti indesiderati. int i sarà uguale a 0 in questo punto iniziale in quanto questo è l'indice iniziale predefinito al primo caricamento dello spinner. Quindi la mia soluzione controlla per assicurarsi che sia selezionato un elemento diverso effettivo invece che l'elemento attualmente selezionato venga nuovamente selezionato ... Questo risponde alla tua domanda?
Chris,

Ciao Chris, ho una pagina che estrae informazioni dal database che l'utente può modificare. quando la pagina si apre popolo i filatori e quindi imposto le loro posizioni sui valori presenti nel database. Quindi, se ho impostato la loro posizione su 3, ad esempio, questo provoca l'attivazione di onItemSelected con i set su 3, che è diverso dall'iniziale. Stavo pensando che stavi dicendo che sono impostato solo se l'utente ha effettivamente cambiato da solo.
JStephen,

4
Cosa succede se l'utente seleziona la posizione 0? Saranno ignorati.
Yetti99

Non penso che l'ultima posizione sia una buona idea. Inizio i filatori caricando la posizione da SharedPreferences e usando setSelection. Molto spesso i valori in SharedPrefs non sono gli stessi dei valori predefiniti quando vengono creati gli spinner, quindi onItemSelected verrà attivato all'avvio.
Arthez,

26

Ero in una situazione simile e ho una soluzione semplice che funziona per me.

Sembra metodi setSelection(int position)e setSelected(int position, boolean animate)hanno un'implementazione interna diversa.

Quando si utilizza il secondo metodo setSelected(int position, boolean animate)con flag di animazione falsa, si ottiene la selezione senza attivare il onItemSelectedlistener.


L'approccio migliore è non preoccuparsi delle chiamate extra a onItemSelected, ma assicurarsi che mostri la selezione giusta. Quindi, chiamare spinner.setSelection (selectedIndex) prima di aggiungere listener ha funzionato in modo coerente per me.
andude

1
non esiste un metodo selezionato (int position, boolean animate) per spinner
shift66

4
La vera chiamata di cui hai bisogno èsetSelection(int position, boolean animate);
Brad

+1 per te. Questo risolve un problema più generale quando il codice modifica più volte il contenuto e la selezione di Spinner continuando su Oggetto selezionato solo per l'interazione dell'utente
alrama

4
flag di animazione purtroppo falso chiama ancora onItemSelectedin API23
mcy

23

Solo per dare un'idea dei suggerimenti sull'uso di onTouchListener per distinguere tra chiamate automatiche a setOnItemSelectedListener (che fanno parte dell'inizializzazione delle attività, ecc.) Rispetto alle chiamate attivate dall'interazione effettiva dell'utente, ho fatto quanto segue dopo aver provato alcuni altri suggerimenti qui e scoperto che ha funzionato bene con il minor numero di righe di codice.

Basta impostare un campo booleano per la tua attività / frammento come:

private Boolean spinnerTouched = false;

Quindi appena prima di impostare setOnItemSelectedListener del tuo spinner, imposta 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;

1
Funziona benissimo, e da Android 6+, questo è l'unico metodo che funziona. MA devi anche fare lo stesso con setOnKeyListener (), oppure non funziona quando l'utente naviga con la tastiera.
Stéphane,

Funziona alla grande, tutte le altre soluzioni hanno qualche problema con telefoni diversi.
Ziwei Zeng,

Questo è semplice e assolutamente perfetto! Non sono necessarie altre sciocchezze, tieni solo a mente la logica. Sono contento di aver fatto scorrere fino a qui!
user3833732

Invece di setOnKeyListener (), è possibile sottoclassare spinner e impostare flag spinnerTouched = true nel metodo preformClick () ignorato, che viene chiamato in entrambi i casi (tocco / tasto). Il resto è lo stesso.
Onnipotente il

Volevo solo menzionare questo sembra risolvere lo stesso bug con DropDownPreferences che ho pubblicato di recente qui: stackoverflow.com/questions/61867118/… Non posso f * cking crederci tbh: D
Daniel Wilson,

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

3
Il codice potrebbe parlare da solo, ma un po 'di spiegazione fa
molta

8

Dopo aver strappato i capelli per molto tempo ora ho creato la mia classe Spinner. Ho aggiunto un metodo ad esso che disconnette e collega l'ascoltatore in modo appropriato.

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

Usalo nel tuo XML in questo modo:

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

Tutto quello che devi fare è recuperare l'istanza di SaneSpinner dopo l'inflazione e chiamare la selezione del set in questo modo:

mMySaneSpinner.setSelection(1, true, true);

Con ciò, nessun evento viene generato e l'interazione dell'utente non viene interrotta. Ciò ha ridotto molto la complessità del mio codice. Questo dovrebbe essere incluso in stock Android poiché è davvero un PITA.


1
Questo non funziona per me, si innesca ancora su ItemSelected.
Arthez,

Arthez, per favore, ricontrolla se stai davvero passando al terzo argomento. Se sì, qualcos'altro non va qui. Se possibile inserisci il tuo codice.
fusion44,

8

Nessun evento indesiderato dalla fase di layout se si rinvia l'aggiunta dell'ascoltatore fino al completamento del layout:

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

            });

        }
    });

Funziona e IMO è la soluzione più pulita al problema specifico del PO. Voglio notare che puoi rimuovere le ViewTreeObserver.OnGlobalLayoutListenerversioni on sotto J chiamando ViewTreeObserver.removeGlobalOnLayoutListener, che è deprecato e ha un nome simile al metodo utilizzato da questa risposta.
Jack Meister,

7

Questo accadrà se stai facendo una selezione nel codice come;

   mSpinner.setSelection(0);

Invece di usare le istruzioni precedenti

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

Modifica: questo metodo non funziona con l'interfaccia utente Mi versione Mi Android.


2
Questo ha sicuramente risolto il problema per me. Ho letto la documentazione sul widget Spinner .. è assolutamente difficile capire la differenza: setSelection (int position, boolean animate) -> Passa direttamente a un elemento specifico nei dati dell'adattatore. setSelection (int position) -> Imposta l'elemento attualmente selezionato.
Matt,

5

Ho una risposta molto semplice, sicuro al 100% che funzioni:

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

Ho trovato una soluzione molto più elegante a questo. Implica il conteggio di quante volte è stato richiamato ArrayAdapter (nel tuo caso "adattatore"). Supponiamo che tu abbia 1 spinner e chiami:

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

Dichiarare un contatore int dopo il metodo onCreate e quindi all'interno di onItemSelected () inserire una condizione "if" per verificare quante volte è stato chiamato l'apice. Nel tuo caso lo hai chiamato solo una volta così:

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

2

Il mio piccolo contributo è una variazione di alcuni dei precedenti che mi si è adattato alcune volte.

Dichiarare una variabile intera come valore predefinito (o l'ultimo valore utilizzato salvato nelle preferenze). Utilizzare spinner.setSelection (myDefault) per impostare quel valore prima che il listener sia registrato. In onItemSelected controlla se il nuovo valore di spinner è uguale al valore assegnato prima di eseguire qualsiasi altro codice.

Ciò ha l'ulteriore vantaggio di non eseguire il codice se l'utente seleziona nuovamente lo stesso valore.


1

Dopo aver avuto lo stesso problema, sono arrivato a queste soluzioni usando i tag. L'idea alla base è semplice: ogni volta che lo spinner viene modificato a livello di codice, assicurarsi che il tag rifletta la posizione selezionata. Nell'ascoltatore quindi controlli se la posizione selezionata è uguale al tag. In tal caso, la selezione del filatore è stata modificata in modo programmatico.

Di seguito è la mia nuova classe "proxy spinner":

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

Sarà inoltre necessario un file XML con l'impostazione del tag nella Valuesdirectory. Ho chiamato il mio file spinner_tag.xml, ma dipende da te. Sembra così:

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

Ora sostituisci

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

nel tuo codice con

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

E fai sembrare il tuo gestore un po 'così:

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 funzione isUiTriggered()restituirà vero se e solo se l'utente ha cambiato il filatore. Nota che questa funzione ha un effetto collaterale: imposterà il tag, quindi tornerà sempre una seconda chiamata nella stessa chiamata all'ascoltatorefalse .

Questo wrapper gestirà anche il problema con il listener chiamato durante la creazione del layout.

Divertiti, Jens.


1

Dal momento che nulla ha funzionato per me, e ho più di 1 spinner nella mia vista (e l'IMHO che tiene una bool map è un eccesso) uso il tag per contare i clic:

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

Molte risposte già, ecco la mia.

Estendo AppCompatSpinnere aggiungo un metodo pgmSetSelection(int pos)che consente l'impostazione della selezione programmatica senza attivare un callback di selezione. Ho codificato questo con RxJava in modo che gli eventi di selezione vengano consegnati tramite 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 esempio del suo utilizzo, chiamato onCreateView()in una Fragment, ad esempio:

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

dove setSelection()c'è un metodo nella vista chiusa che assomiglia a questo, e che viene chiamato sia dagli eventi di selezione dell'utente tramite il Observablesia anche altrove a livello di codice, quindi la logica per la gestione delle selezioni è comune a entrambi i metodi di selezione.

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

Vorrei provare a chiamare

spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());

dopo aver chiamato setAdapter (). Prova anche a chiamare prima dell'adattatore.

Hai sempre la soluzione per andare con la sottoclasse, in cui puoi avvolgere un flag booleano nel metodo setAdapter di sostituzione per saltare l'evento.


0

La soluzione con un flag booleano o un contatore non mi ha aiutato, perché durante l'orientamento cambia su ItemSelected () le chiamate "sovraccaricano" il flag o il contatore.

Ho effettuato una sottoclasse android.widget.Spinnere fatto piccole aggiunte. Le parti pertinenti sono di seguito. Questa soluzione ha funzionato per me.

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

Anche questa non è una soluzione elegante. In realtà è piuttosto Rube-Goldberg ma sembra funzionare. Mi assicuro che lo spinner sia stato usato almeno una volta estendendo l'adattatore di array e sovrascrivendolo getDropDownView. Nel nuovo metodo getDropDownView ho un flag booleano impostato per mostrare che il menu a discesa è stato usato almeno una volta. Ignoro le chiamate all'ascoltatore fino a quando non viene impostato il flag.

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

adattatore per array overriden:

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

ascoltatore modificato:

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


0

Ho fatto nel modo più semplice:

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

        }
    });

Fatto


È una soluzione interessante. Potrebbe usare più spiegazioni. Fondamentalmente ignora intenzionalmente il primo evento onItemSelected. Potrebbero funzionare bene in alcuni casi, ma non in altri come quando le opzioni di accessibilità sono abilitate (vedere la spiegazione di 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) {

     }
});

0

Questa è la mia soluzione finale e facile da usare:

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

Utilizzare l'impostazione predefinita setSelection(...)per il comportamento predefinito o utilizzare setSelectionWithoutInformListener(...)per selezionare un elemento nello spinner senza attivare il callback di OnItemSelectedListener.


0

Devo usare mSpinnerin ViewHolder, quindi il flag mOldPositionè impostato nella classe interna anonima.

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

Vorrei memorizzare l'indice iniziale durante la creazione dell'oggetto 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) {
      }
  });

0

La mia soluzione utilizza onTouchListenerma non limita il suo utilizzo. Crea un wrapper per onTouchListenerse necessario dove l'installazione 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

Potrei rispondere troppo tardi per posta, tuttavia sono riuscito a farlo utilizzando la libreria di associazione dati Android Android Databinding . Ho creato un'associazione personalizzata per assicurarmi che il listener non venga chiamato fino a quando l'elemento selezionato non viene modificato, quindi anche se l'utente sta selezionando sempre la stessa posizione l'evento non viene generato.

File XML di layout

    <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 è dove stai passando la posizione da selezionare.

Rilegatura personalizzata

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

        }
    });
}

Puoi leggere ulteriori informazioni sull'associazione di dati personalizzati qui Android Custom Setter

NOTA

  1. Non dimenticare di abilitare il databinding nel tuo file Gradle

       android {
     ....
     dataBinding {
     enabled = true
    }
    }
  2. Includi i file di layout nei <layout>tag


-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) Si prega di formattare correttamente il codice. 2) Sarebbe anche gradita una spiegazione su cosa sta facendo il tuo codice. Non tutti i frammenti di codice vengono compresi immediatamente durante la lettura del codice.
Mike Koch,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.