Android Spinner: Vermeiden Sie onItemSelected-Aufrufe während der Initialisierung

156

Ich habe eine Android-Anwendung mit a Spinnerund a erstellt TextView. Ich möchte das ausgewählte Element aus der Dropdown-Liste des Spinners in der Textansicht anzeigen. Ich habe den Spinner in die onCreateMethode implementiert. Wenn ich das Programm ausführe, wird ein Wert in TextViewangezeigt (bevor ein Element aus der Dropdown-Liste ausgewählt wird).

Ich möchte den Wert in der Textansicht erst anzeigen, nachdem ich ein Element aus der Dropdown-Liste ausgewählt habe. Wie mache ich das?

Hier ist mein Code:

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

    }
}
Gewähren
quelle
Spinner haben immer einen ausgewählten Standardwert. Wenn Sie einen Spinner ohne ausgewählten Standardwert haben möchten, sollten Sie Ihren benutzerdefinierten Spinner oder einen benutzerdefinierten Spinner mit einem leeren Eintrag erstellen und in der Methode getView () die Sichtbarkeit von ändern das rohe Layout zu GONE
Houcine
4
Wie kommt es, dass es Ende 2018 noch so einen nervigen Bug gibt ???
Benutzer-123

Antworten:

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

Behandeln Sie dies also zum ersten Mal mit einem beliebigen Integer-Wert

Beispiel: Nehmen Sie zunächst 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);
   }
}

Sie können dies mit einem booleschen Wert und auch durch Überprüfen der aktuellen und vorherigen Positionen tun. Siehe hier

Abhi
quelle
2
Super Mann ... als ich zum ersten Mal mit diesem Problem konfrontiert war, habe ich versucht, einen benutzerdefinierten Spinner zu implementieren. Trotzdem hat es nicht funktioniert. Aber Ihre Lösung hat wie ein Zauber funktioniert. Danke.
Sash_KP
1
Wo deklarieren Sie Scheck? Außerhalb des getView ()? In der AnsichtHalter? Wo? habe deine Lösung ausprobiert, funktioniert aber bei mir nicht.
user3718908
8
Es ist ein Patch, keine Lösung, denke ich.
Saksham
1
Ich stehe vor dem gleichen Problem. Ist es eine Art Fehler? Auch wenn ich dasselbe Element wiederholt auswähle, funktioniert der Listener nach dem ersten Mal nicht. Dies funktioniert nur, wenn sich das Auswahlelement ändert. Irgendein Kommentar, Hilfe?
Amitava
1
Ich habe diese Lösung mit demselben Problem ausprobiert, aber dies ist nur ein Wok, z. B.: Wenn ich 2 Elemente in der Dropdown-Liste habe, wird es aktiviert, wenn ich zum ersten Mal ein Spinner-Element auswähle, wenn ich es zum zweiten Mal versuche, funktioniert es nicht richtig
Waseem
116

Setzen Sie einfach diese Zeile, bevor Sie OnItemSelectedListener festlegen

spinner.setSelection(0,false)
Dayanand Waghmare
quelle
7
Es wäre eine bessere Antwort, wenn Sie schreiben würden, wie dies hilft und warum.
user3533716
1
Es hilft, aber willst du wie?
AEMLoviji
14
Dies funktioniert, weil Sie zuerst die Auswahl festlegen und dann einen Listener hinzufügen. Der Listener wird jedoch nicht aufgerufen, da Sie diese Auswahl bereits zuvor ausgewählt haben. Nur neue Auswahlen rufen den Listener an.
Android-Entwickler
4
Dies funktioniert , weil setSelection(int, boolean)Anrufe setSelectionInt()intern, und Sie müssen den Hörer nach (statt vorher) ruft diese einzustellen. Vorsicht, das setSelection(int)wird nicht funktionieren, weil es setNextSelectedPositionInt()intern anruft , und das hat mich hierher geführt.
Hai Zhang
3
Dies funktioniert nicht, wenn es während oder vor onCreateView () deklariert wurde. onItemSelected wird aufgerufen.
Arash
62

Ab API-Ebene 3 können Sie onUserInteraction () für eine Aktivität mit einem Booleschen Wert verwenden, um festzustellen, ob der Benutzer mit dem Gerät interagiert.

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

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

Als Feld für die Aktivität habe ich:

 private boolean userIsInteracting;

Endlich mein Spinner:

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

           }
      });

Während Sie durch die Aktivität kommen und gehen, wird der Boolesche Wert auf false zurückgesetzt. Klappt wunderbar.

Bill Mote
quelle
3
Gute Rechnung ... Ich denke, dies ist eine bessere Lösung als die als Antwort akzeptierte
Ritesh Gune
Woher bekommst du setmPreviousSelectedIndex?!?!
TheQ
Tippfehler? :) setPreviousSelectedIndex ()
Bill Mote
4
Dies funktioniert bei Nestfragmenten nicht, da onUserInteraction die Aktivitätsmethode ist. Irgendeine andere Lösung?
Chitrang
1
@ErikB nvm, habe es herausgefunden. Das Setzen auf falsch im Listener funktioniert einwandfrei.
Sikander
20

Das hat bei mir funktioniert

Die Initialisierung von Spinner in Android ist problematisch. Manchmal wurde das obige Problem durch dieses Muster gelöst.

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

Der Einstellungsadapter sollte der erste Teil sein und onItemSelectedListener (dies) wird der letzte sein, wenn ein Spinner initialisiert wird. Nach dem obigen Muster wird mein OnItemSelected () während der Initialisierung des Spinners nicht aufgerufen

Saksham
quelle
11

haha ... ich habe die gleiche frage. Wenn initViews () einfach so läuft. Die Sequenz ist der Schlüssel, der Listener der letzte. Viel Glück !

spinner.setAdapter(adapter);
spinner.setSelection(position);
spinner.setOnItemSelectedListener(listener);
Treesouth
quelle
Guter! Nichts hat für mich funktioniert, was auch immer ich zuvor angewendet habe, aber dieses hat für mich wie ein Zauber funktioniert. Danke @TreeSouth
Deepika Lalra
11
Bei mir hat spinner.setSelection (position, false) auf die gleiche Weise verwendet. Bei der Methode setSelection (position) wurde der Listener während der Initialisierung aufgerufen.
Mario Kutlev
2
@HugoGresse Versuchen Sie, spinner.setSelection (0, false) aufzurufen. . Die Sache ist, jetzt wird die Auswahl dieser Position ignoriert, da sie bereits ausgewählt ist
Android-Entwickler
8

Meine Lösung:

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

        }
codareee
quelle
6

Sie können dies auf folgende Weise tun:

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

        }
    });

Zuerst erstelle ich einen Listener und ordne ihn einem variablen Rückruf zu. dann erstelle ich einen zweiten anonymen Listener und wenn dieser zum ersten Mal aufgerufen wird, ändert dies den Listener =]

Charleston
quelle
6

Um zu vermeiden, dass spinner.setOnItemSelectedListener () während der Initialisierung aufgerufen wird

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

    }
});
Ketan Ramani
quelle
3

Das Benutzerinteraktionsflag kann dann in der onTouch-Methode auf true gesetzt und zurückgesetzt werden, onItemSelected()sobald die Auswahländerung vorgenommen wurde. Ich bevorzuge diese Lösung, da das Benutzerinteraktionsflag ausschließlich für den Spinner und nicht für andere Ansichten in der Aktivität behandelt wird, die das gewünschte Verhalten beeinflussen können.

In Code:

Erstellen Sie Ihren Listener für den 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
        }
    }

}

Fügen Sie den Listener dem Spinner als an OnItemSelectedListenerund an hinzu OnTouchListener:

SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);
Ranjith Kumar
quelle
2

Eine ähnliche einfache Lösung, die mehrere Spinner ermöglicht, besteht darin, die AdapterView bei der ersten Ausführung von onItemSelected (...) in eine Sammlung - in der Superklasse Aktivitäten - zu stellen. Überprüfen Sie dann, ob sich die AdapterView in der Sammlung befindet, bevor Sie sie ausführen. Dies ermöglicht einen Satz von Methoden in der Oberklasse und unterstützt mehrere AdapterViews und damit mehrere Spinner.

Superklasse ...

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

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

Unterklasse ...

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.

}}

Jonathan Cole
quelle
2

Erstellen Sie ein boolesches Feld

private boolean inispinner;

innerhalb einer der Aktivitäten erstellen

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

        }
    });
Afjalur Rahman Rana
quelle
2

Versuche dies

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

Sie können dies erreichen, indem Sie zuerst setOnTouchListener und dann setOnItemSelectedListener in onTouch festlegen

@Override
public boolean onTouch(final View view, final MotionEvent event) {
 view.setOnItemSelectedListener(this)
 return false;
}
vviieett
quelle
Ich liebe es. Obwohl jedes Mal, wenn ein Benutzer die Ansicht berührt, ein neuer Listener erstellt wird. Daher bevorzuge ich es, den zuerst erstellten Listener zwischenzuspeichern und wiederzuverwenden.
Vlad
1

Das hat bei mir funktioniert:

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

Basierend auf Abhis Antwort habe ich diesen einfachen Zuhörer gemacht

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)
        }
    }
}
AdrianoCelentano
quelle
0

Hatte das gleiche Problem und das funktioniert bei mir:

Ich habe 2 Spinner und aktualisiere sie während der Initialisierung und während der Interaktion mit anderen Steuerelementen oder nachdem ich Daten vom Server erhalten habe.

Hier ist meine Vorlage:

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

    }
}

Verwenden Sie beim Starten der Klasse setSpinnersListeners (), anstatt den Listener direkt festzulegen .

Das Runnable verhindert, dass der Spinner direkt nach dem Festlegen der Werte auf ItemSelected ausgelöst wird.

Wenn Sie den Spinner aktualisieren müssen (nach einem Serveraufruf usw.), verwenden Sie removeSpinnersListeners () direkt vor Ihren Aktualisierungszeilen und setSpinnersListeners () direkt nach den Aktualisierungszeilen. Dadurch wird verhindert, dass onItemSelected nach dem Update ausgelöst wird.

RoyBS
quelle
0

Code

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

            }
        });
Messi
quelle
0

Für mich funktioniert Abhis Lösung bis zu Api Level 27 hervorragend.

Es scheint jedoch, dass ab API-Level 28 und höher onItemSelected () nicht aufgerufen wird, wenn der Listener festgelegt ist, was bedeutet, dass onItemSelected () niemals aufgerufen wird.

Daher habe ich eine kurze if-Anweisung hinzugefügt, um die API-Ebene zu überprüfen:

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

Ich finde das ziemlich seltsam und ich bin mir nicht sicher, ob andere auch dieses Problem haben, aber in meinem Fall hat es gut funktioniert.

Jannik B.
quelle
0

Ich habe eine Textansicht auf dem Spinner platziert, die dieselbe Größe und denselben Hintergrund wie der Spinner hat, damit ich mehr Kontrolle darüber habe, wie sie aussieht, bevor der Benutzer darauf klickt. Mit der dortigen TextView könnte ich auch die TextView verwenden, um zu kennzeichnen, wann der Benutzer mit der Interaktion begonnen hat.

Mein Kotlin-Code sieht ungefähr so ​​aus:

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

Die Layoutdatei sieht ungefähr so ​​aus, wobei der wichtige Teil darin besteht, dass Spinner und TextView dieselbe Breite, Höhe und Ränder haben:

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

Ich bin mir sicher, dass die anderen Lösungen funktionieren, bei denen Sie den ersten Aufruf von onItemSelected ignorieren, aber die Idee, dass er immer aufgerufen wird, gefällt mir wirklich nicht.

iOS_Mouse
quelle