Android Spinner: unikaj wywołań onItemSelected podczas inicjowania

156

Stworzyłem aplikację na Androida z rozszerzeniem Spinneri TextView. Chcę wyświetlić wybrany element z rozwijanej listy Spinner w TextView. Zaimplementowałem Spinner w onCreatemetodzie, więc kiedy uruchamiam program, pokazuje on wartość w TextView(przed wybraniem pozycji z rozwijanej listy).

Chcę pokazać wartość w TextView dopiero po wybraniu elementu z rozwijanej listy. Jak mam to zrobic?

Oto mój kod:

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

    }
}
Dotacja
źródło
pokrętła zawsze mają domyślną wybraną wartość, jeśli chcesz mieć pokrętło bez domyślnej wybranej wartości, powinieneś utworzyć niestandardowy spinner lub utworzyć niestandardowy spinner z pustym wpisem, aw metodzie getView () zmień widoczność surowy układ do
GONE
4
Dlaczego taki irytujący błąd nadal istnieje pod koniec 2018 roku ???
user-123

Odpowiedzi:

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

Więc za pierwszym razem obsłuż to z dowolną wartością całkowitą

Przykład: Początkowo 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);
   }
}

Możesz to zrobić z wartością logiczną, a także sprawdzając aktualne i poprzednie pozycje. Spójrz tutaj

Abhi
źródło
2
Niesamowity człowieku ... kiedy pierwszy raz napotkałem ten problem, próbowałem zaimplementować niestandardową przędzarkę, ale to nie zadziałało, ale Twoje rozwiązanie działało jak urok ... Dzięki.
Sash_KP
1
Gdzie deklarujesz czek? Poza getView ()? Wewnątrz widoku Gdzie? wypróbowałem twoje rozwiązanie, ale nie działa dla mnie.
user3718908
9
to łatka, a nie rozwiązanie, jak sądzę.
saksham
1
Mam ten sam problem. Czy to jakiś błąd? również jeśli wielokrotnie wybiorę ten sam element, odbiornik nie będzie działał za pierwszym razem. Działa tylko w przypadku zmiany elementu wyboru. Jakiś komentarz, pomoc?
amitava
1
Wypróbowałem to rozwiązanie z tym samym problemem, ale to działa tylko z jednym, np .: kiedy mam 2 pozycje na liście rozwijanej, uruchamia się, gdy wybieram dowolny element typu spinner 1 raz, kiedy próbuję 2 raz, nie działa poprawnie
Waseem
116

Po prostu umieść tę linię przed ustawieniem OnItemSelectedListener

spinner.setSelection(0,false)
Dayanand Waghmare
źródło
7
Byłoby lepszą odpowiedzią, gdybyś napisał, jak to pomaga i dlaczego.
user3533716
1
To pomaga, ale chcesz jak?
AEMLoviji
14
Działa to, ponieważ najpierw ustawiasz wybór, a następnie dodajesz odbiornik, ale odbiorca nie zostanie wywołany, ponieważ już wcześniej wybrałeś ten wybór. Tylko nowe wybory będą dzwonić do słuchacza.
programista Androida
4
To działa, ponieważ setSelection(int, boolean)wywołuje setSelectionInt()wewnętrznie i musisz ustawić odbiornik po (zamiast przed) wywołaniem tego. Uważaj, setSelection(int)to nie zadziała, ponieważ dzwoni setNextSelectedPositionInt()wewnętrznie i to mnie tu doprowadziło.
Hai Zhang,
3
To nie działa, jeśli zostanie zadeklarowane podczas lub przed onCreateView (). onItemSelected zostanie wywołana.
Arash
62

Począwszy od poziomu interfejsu API 3, można użyć metody onUserInteraction () w działaniu z wartością logiczną, aby określić, czy użytkownik wchodzi w interakcję z urządzeniem.

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

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

Jako pole na Aktywności mam:

 private boolean userIsInteracting;

Wreszcie mój 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) {

           }
      });

Gdy przychodzisz i przechodzisz przez działanie, wartość logiczna jest resetowana do wartości fałsz. Działa jak marzenie.

Bill Mote
źródło
3
Dobry Bill ... myślę, że to lepsze rozwiązanie niż to przyjęte jako odpowiedź
Ritesh Gune
skąd wziąć setmPreviousSelectedIndex?!?!
TheQ
Literówka? :) setPreviousSelectedIndex ()
Bill Mote
4
To nie zadziała w przypadku fragmentów gniazd, ponieważ onUserInteraction jest metodą Activity. Jakieś inne rozwiązanie?
Chitrang
1
@ErikB nvm, rozgryzłem to. Ustawienie wartości false wewnątrz listenera działa dobrze.
Sikander,
20

To zadziałało dla mnie

Inicjalizacja Spinnera w systemie Android jest problematyczna, czasami powyższy problem został rozwiązany przez ten wzorzec.

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

Ustawienie adaptera powinno być pierwszą częścią, a onItemSelectedListener (this) będzie ostatnim podczas inicjowania przędzarki. Według wzorca powyżej moja OnItemSelected () nie jest wywoływana podczas inicjalizacji przędzarki

saksham
źródło
11

haha ... mam to samo pytanie. Kiedy initViews () robi to w ten sposób: sekwencja jest kluczem, listener jest ostatnim. Powodzenia !

spinner.setAdapter(adapter);
spinner.setSelection(position);
spinner.setOnItemSelectedListener(listener);
Treeouth
źródło
Dobry! Nic nie działało dla mnie, cokolwiek wcześniej aplikowałem, ale ten zadziałał dla mnie jak urok. Dzięki @TreeSouth
Deepika Lalra
11
U mnie działał spinner.setSelection (position, false) używany w ten sam sposób. Przy metodzie setSelection (position) detektor został wywołany podczas inicjalizacji.
Mario Kutlev
2
@HugoGresse Spróbuj wywołać spinner.setSelection (0, false); . Rzecz w tym, że teraz zignoruje wybór tej pozycji, ponieważ jest już wybrana
programista android
8

Moje rozwiązanie:

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
źródło
7

Aby uniknąć wywoływania spinner.setOnItemSelectedListener () podczas inicjalizacji

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
źródło
6

Możesz to zrobić w ten sposób:

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

        }
    });

Najpierw tworzę odbiornik i przypisuję zmienną callback; następnie tworzę anonimowego drugiego nasłuchiwania, a gdy jest to wywoływane po raz pierwszy, zmienia to listener =]

Czarleston
źródło
3

Znacznik interakcji użytkownika można następnie ustawić na wartość true w metodzie onTouch i zresetować onItemSelected()po dokonaniu zmiany wyboru. Preferuję to rozwiązanie, ponieważ flaga interakcji użytkownika jest obsługiwana wyłącznie dla przędzarki, a nie dla innych widoków w działaniu, które mogą wpływać na pożądane zachowanie.

W kodzie:

Utwórz odbiornik dla spinnera:

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

}

Dodaj słuchacza do pokrętła zarówno jako an, jak OnItemSelectedListeneri jako OnTouchListener:

SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);
Ranjith Kumar
źródło
2

Podobnym prostym rozwiązaniem, które włącza wiele spinnerów, jest umieszczenie AdapterView w kolekcji - w nadklasie Działania - przy pierwszym wykonaniu onItemSelected (...) Następnie sprawdź, czy AdapterView znajduje się w kolekcji przed jego wykonaniem. Umożliwia to jeden zestaw metod w nadklasie i obsługuje wiele widoków AdapterViews, a tym samym wiele przędzarek.

Nadklasa ...

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

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

Podklasa ...

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
źródło
2

utwórz pole boolowskie

private boolean inispinner;

wewnątrz podczas tworzenia działania

    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
źródło
2

Spróbuj tego

spinner.postDelayed(new Runnable() {
        @Override
        public void run() {
            addListeners();
        }
    }, 1000);.o
Ubirajara Erthal
źródło
1

Można to osiągnąć najpierw przez setOnTouchListener, a następnie setOnItemSelectedListener w onTouch

@Override
public boolean onTouch(final View view, final MotionEvent event) {
 view.setOnItemSelectedListener(this)
 return false;
}
vviieett
źródło
Uwielbiam to. Mimo że tworzy nowego słuchacza za każdym razem, gdy użytkownik dotyka widoku. Dlatego wolę buforować pierwszy utworzony odbiornik i używać go ponownie.
Vlad
1

To zadziałało dla mnie:

    spinner.setSelection(0, false);
    new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                spinner.setOnItemSelectedListener(listener);
            }, 500);
Khushal
źródło
1

Na podstawie odpowiedzi Abhiego stworzyłem prostego słuchacza

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
źródło
0

Miałem ten sam problem i to działa dla mnie:

Mam 2 spinnerki i aktualizuję je podczas inicjalizacji i podczas interakcji z innymi kontrolkami lub po pobraniu danych z serwera.

Oto mój szablon:

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

    }
}

Kiedy klasa jest inicjowana, użyj setSpinnersListeners () zamiast bezpośredniego ustawiania detektora.

Runnable zapobiegnie odpaleniu błystki onItemSelected zaraz po ustawieniu ich wartości.

Jeśli potrzebujesz zaktualizować spinner (po wywołaniu serwera itp.), Użyj removeSpinnersListeners () tuż przed liniami aktualizacji i setSpinnersListeners () tuż po liniach aktualizacji. Zapobiegnie to uruchomieniu onItemSelected po aktualizacji.

RoyBS
źródło
0

Kod

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
źródło
0

Dla mnie rozwiązanie Abhi działa świetnie do poziomu Api 27.

Wygląda jednak na to, że od poziomu 28 interfejsu API w górę onItemSelected () nie jest wywoływana, gdy ustawiono detektor, co oznacza, że ​​onItemSelected () nigdy nie jest wywoływana.

Dlatego dodałem krótką instrukcję if, aby sprawdzić poziom interfejsu 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
            }
        }

Myślę, że to dość dziwne i nie jestem pewien, czy inni też mają ten problem, ale w moim przypadku zadziałało dobrze.

Jannik B
źródło
0

Umieściłem TextView na Spinner, o tym samym rozmiarze i tle co Spinner, aby mieć większą kontrolę nad tym, jak wygląda, zanim użytkownik go kliknie. Mając tam TextView, mógłbym również użyć TextView do oznaczenia, kiedy użytkownik rozpoczął interakcję.

Mój kod Kotlin wygląda mniej więcej tak:

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

Plik układu wygląda mniej więcej tak, a ważną częścią jest to, że Spinner i TextView mają tę samą szerokość, wysokość i marginesy:

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

Jestem pewien, że inne rozwiązania działają, gdy ignorujesz pierwsze wywołanie onItemSelected, ale naprawdę nie podoba mi się pomysł, że zawsze będzie wywoływany.

iOS_Mouse
źródło