Android Spinner: evita le chiamate onItemSelected durante l'inizializzazione


157

Ho creato un'applicazione Android con a Spinnere a TextView. Voglio visualizzare l'elemento selezionato dall'elenco a discesa dello Spinner in TextView. Ho implementato lo Spinner nel onCreatemetodo, quindi quando eseguo il programma, mostra un valore inTextView (prima di selezionare un elemento dall'elenco a discesa).

Voglio mostrare il valore in TextView solo dopo aver selezionato un elemento dall'elenco a discesa. Come faccio a fare questo?

Ecco il mio codice:

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

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

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

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

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

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

    }
}

Gli spinner hanno sempre un valore selezionato predefinito, se si desidera avere uno spinner senza un valore selezionato predefinito, è necessario creare uno spinner personalizzato o creare uno spinner personalizzato con una voce vuota e, nel metodo getView (), modificare la visibilità di il layout grezzo di GONE
Houcine,

5
Come mai un bug così fastidioso esiste ancora alla fine del 2018 ???
user-123,

Risposte:


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

Quindi la prima volta gestisci questo con qualsiasi valore intero

Esempio: Inizialmente Take int check = 0;

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

Puoi farlo con un valore booleano e anche controllando le posizioni attuali e precedenti. Vedere qui


2
Uomo fantastico..quando ho affrontato per la prima volta questo problema ho provato a implementare lo spinner personalizzato ... ma non ha funzionato..ma la tua soluzione ha funzionato come un fascino..Grazie.
Sash_KP,

1
Dove si dichiara l'assegno? Fuori da getView ()? All'interno della vistaHolder? Dove? ho provato la tua soluzione ma non funziona per me.
user3718908

10
è una patch non una soluzione immagino.
Saksham,

1
Sono di fronte allo stesso problema. È una specie di bug? anche se seleziono più volte lo stesso oggetto, l'ascoltatore non funziona dopo la prima volta. Funziona solo se l'elemento di selezione cambia. Qualche commento, aiuto?
amitava,

1
Ho provato questa soluzione con il mio stesso problema, ma questo ne risveglia solo uno, ad esempio: quando ho 2 elementi sul menu a discesa, si attiva quando seleziono qualsiasi elemento spinner la prima volta, quando provo la seconda volta non funziona correttamente
Waseem

117

Basta inserire questa riga prima di impostare OnItemSelectedListener

spinner.setSelection(0,false)

7
Sarebbe una risposta migliore se scrivessi come questo aiuta e perché.
user3533716

1
Aiuta, ma vuoi come?
AEMLoviji,

14
Funziona perché hai prima impostato la selezione, quindi aggiungi un listener, ma il listener non verrà chiamato perché hai già scelto questa selezione in precedenza. Solo le nuove selezioni chiameranno l'ascoltatore.
sviluppatore Android

4
Questo funziona perché setSelection(int, boolean)chiama setSelectionInt()internamente ed è necessario impostare l'ascoltatore dopo (anziché prima) chiamare questo. Attenzione che setSelection(int)non funzionerà, perché chiama setNextSelectedPositionInt()internamente, e questo è ciò che mi ha portato qui.
Hai Zhang,

3
Questo non funziona se è stato dichiarato durante o prima su OnCreateView (). onItemSelected verrà chiamato.
Arash,

62

A partire dal livello API 3 è possibile utilizzare onUserInteraction () su un'attività con un valore booleano per determinare se l'utente sta interagendo con il dispositivo.

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

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

Come campo sull'attività ho:

 private boolean userIsInteracting;

Finalmente il mio filatore:

      mSpinnerView.setOnItemSelectedListener(new OnItemSelectedListener() {

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

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

           }
      });

Man mano che procedi e svolgi l'attività, il valore booleano viene ripristinato su falso. Funziona come un fascino.


3
Buona fattura ... Penso che questa sia la soluzione migliore di quella accettata come risposta
Ritesh Gune,

dove si trova setmPreviousSelectedIndex?!?!
TheQ

Errore di battitura? :) setPreviousSelectedIndex ()
Bill Mote,

4
Questo non funzionerà in caso di frammenti di nidificazione poiché onUserInteraction è il metodo Activity. Qualche altra soluzione?
Chitrang,

1
@ErikB nvm, capito. L'impostazione di false in listener funziona correttamente.
Sikander,

20

Questo ha funzionato per me

L'inizializzazione di Spinner in Android è problematica a volte il problema sopra è stato risolto da questo modello.

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

L'impostazione dell'adattatore dovrebbe essere la prima parte e onItemSelectedListener (questo) sarà l'ultimo quando si inizializza uno spinner. Dal modello sopra il mio OnItemSelected () non viene chiamato durante l'inizializzazione di spinner


11

haha ... ho la stessa domanda. Quando initViews () fa proprio così: la sequenza è la chiave, listener è l'ultimo. In bocca al lupo !

spinner.setAdapter(adapter);
spinner.setSelection(position);
spinner.setOnItemSelectedListener(listener);

Buona! Niente ha funzionato per me qualunque cosa io abbia applicato prima, ma questo ha funzionato per me come un incantesimo. Grazie @TreeSouth
Deepika Lalra il

11
Per me ha funzionato spinner.setSelection (position, false) usato allo stesso modo. Con il metodo setSelection (posizione) il listener è stato chiamato durante l'inizializzazione.
Mario Kutlev,

2
@HugoGresse Prova a chiamare spinner.setSelection (0, false); . Il fatto è che ora ignorerà la selezione di questa posizione, perché è già selezionata
sviluppatore Android il

8

La mia soluzione:

protected boolean inhibit_spinner = true;


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

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

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

        }

7

Per evitare di chiamare spinner.setOnItemSelectedListener () durante l'inizializzazione

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

    }

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

    }
});

6

Puoi farlo in questo modo:

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

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

        }
    });

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

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

        }
    });

All'inizio creo un ascoltatore e attribuisco a un callback variabile; poi creo un secondo ascoltatore anonimo e quando questo viene chiamato per la prima volta, questo cambia l'ascoltatore =]


3

Il flag di interazione dell'utente può quindi essere impostato su true nel metodo onTouch e reimpostato onItemSelected()una volta gestita la modifica della selezione. Preferisco questa soluzione perché il flag di interazione dell'utente è gestito esclusivamente per lo spinner e non per altre viste nell'attività che potrebbero influire sul comportamento desiderato.

Nel codice:

Crea il tuo ascoltatore per lo spinner:

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

    boolean userSelect = false;

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

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

}

Aggiungi il listener allo spinner come an OnItemSelectedListenere an OnTouchListener:

SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);

2

Una soluzione semplice simile che consente a più spinner è di inserire AdapterView in una raccolta - nella superclasse Attività - alla prima esecuzione di onItemSelected (...) Quindi verificare se AdapterView è nella raccolta prima di eseguirlo. Ciò consente una serie di metodi nella superclasse e supporta più AdapterView e quindi più spinner.

Superclass ...

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

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

Sottoclasse ...

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

}


2

crea un campo booleano

private boolean inispinner;

all'interno della creazione dell'attività

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

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

        }
    });

2

Prova questo

spinner.postDelayed(new Runnable() {
        @Override
        public void run() {
            addListeners();
        }
    }, 1000);.o

1

È possibile ottenerlo prima tramite setOnTouchListener, quindi setOnItemSelectedListener in onTouch

@Override
public boolean onTouch(final View view, final MotionEvent event) {
 view.setOnItemSelectedListener(this)
 return false;
}

Amo questo. Anche se crea un nuovo ascoltatore ogni volta che un utente tocca la vista. Quindi preferisco memorizzare nella cache il primo ascoltatore creato e riutilizzarlo.
Vlad,

1

Questo ha funzionato per me:

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

1

Sulla base della risposta di Abhi ho realizzato questo semplice ascoltatore

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

    private var selectionCount = 0

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

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

0

Ho avuto lo stesso problema e questo funziona per me:

Ho 2 spinner e li aggiorno durante init e durante le interazioni con altri controlli o dopo aver ottenuto i dati dal server.

Ecco il mio modello:

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

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

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

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

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

    }
}

Quando la classe inizia, usa setSpinnersListeners () invece di impostare direttamente il listener.

Runnable impedirà allo spinner di sparare suItemSelected subito dopo aver impostato i loro valori.

Se è necessario aggiornare lo spinner (dopo una chiamata al server, ecc.) Utilizzare removeSpinnersListeners () subito prima delle linee di aggiornamento e setSpinnersListeners () subito dopo le linee di aggiornamento. Ciò impedirà l'attivazione di onItemSelected dopo l'aggiornamento.


0

Codice

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

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

                }
            }

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

            }
        });

0

Per me, la soluzione di Abhi funziona benissimo fino al livello Api 27.

Ma sembra che dal livello Api 28 in poi, onItemSelected () non venga chiamato quando è impostato listener, il che significa che onItemSelected () non viene mai chiamato.

Pertanto, ho aggiunto una breve istruzione if per verificare il livello Api:

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

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

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

Penso che sia abbastanza strano e non sono sicuro che anche altri abbiano questo problema, ma nel mio caso ha funzionato bene.


0

Ho posizionato un oggetto TextView sopra lo Spinner, le stesse dimensioni e lo stesso sfondo dello Spinner, in modo da avere un maggiore controllo su come appariva prima che l'utente facesse clic su di esso. Con TextView lì, potrei anche usare TextView per contrassegnare quando l'utente ha iniziato a interagire.

Il mio codice Kotlin è simile al seguente:

private var mySpinnerHasBeenTapped = false

private fun initializeMySpinner() {

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

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

    my_spinner.onItemSelectedListener = object : OnItemSelectedListener {

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

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

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

Il file di layout è simile al seguente, con la parte importante che Spinner e TextView condividono la stessa larghezza, altezza e margini:

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

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

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

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

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

        </FrameLayout>

Sono sicuro che le altre soluzioni funzionano laddove ignori la prima chiamata onItemSelected, ma non mi piace l'idea di supporre che verrà sempre chiamata.

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.