Jak zachować opcję OnItemSelected przed odpaleniem na nowo utworzonym programie Spinner?


419

Myślałem o mniej niż eleganckich sposobach rozwiązania tego problemu, ale wiem, że czegoś mi brakuje.

Mój onItemSelectedwystrzeliwuje natychmiast, bez żadnej interakcji z użytkownikiem, i jest to niepożądane zachowanie. Chcę, aby interfejs użytkownika czekał, aż użytkownik wybierze coś, zanim cokolwiek zrobi.

Próbowałem nawet ustawić słuchacza w onResume(), mając nadzieję, że to pomoże, ale tak nie jest.

Jak mogę temu zapobiec, zanim użytkownik dotknie kontrolki?

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
Możesz spojrzeć na to rozwiązanie, jest łatwe i praktyczne. stackoverflow.com/a/10102356/621951
Günay Gültekin

1
Prostym rozwiązaniem byłoby uczynienie pierwszego elementu Spinnerpustym, a wewnątrz onItemSelectedmożna wykryć, czy łańcuch nie jest pusty startActivity!
Muhammad Babar

Ten wzór działa poprawnie stackoverflow.com/questions/13397933/...
saksham

Odpowiedzi:


78

Spodziewałbym się, że twoje rozwiązanie zadziała - myślałem, że zdarzenie wyboru nie uruchomi się, jeśli ustawisz adapter przed skonfigurowaniem detektora.

To powiedziawszy, prosta flaga boolowska pozwoli ci wykryć nieuczciwe zdarzenie pierwszego wyboru i zignorować je.


15
ugh, tak. To właśnie miałem na myśli nieeleganckie rozwiązanie. Wydaje się, że musi być lepszy sposób. Ale dziękuję.
FauxReal

5
Wątek na Dev Dev ma więcej informacji na ten temat: groups.google.com/group/android-developers/browse_thread/thread/… - Niestety nie podano rozwiązania ...
BoD

25
Proces układania komponentów uruchamia detektor selekcji. Musisz zatem dodać detektor po zakończeniu układu. Nie udało mi się znaleźć odpowiedni, prosty miejsce, aby to zrobić, ponieważ układ wydaje się zdarzyć w pewnym momencie po onResume()i onPostResume()tak wszystkie normalne haki zostały zakończone w momencie układ dzieje.
Dan Dyer,

28
Trzymałbym się z dala od tej flagi boolowskiej - tak jakby zmiany zachowań w przyszłości mogły spowodować błąd. Bardziej odpornym na pociski rozwiązaniem byłoby utrzymanie zmiennej o „bieżącym wybranym indeksie”, zainicjalizowanej dla pierwszego wybranego elementu. Następnie przy zdarzeniu wyboru - sprawdź, czy równa się nowej pozycji - wróć i nic nie rób. Oczywiście zaktualizuj zmienną przy wyborze.
daniel.gindi,

2
To nie działa. Odpowiedz przez @casanova działa. To powinna być zaakceptowana odpowiedź.
Siddharth,

379

Korzystanie z Runnables jest całkowicie nieprawidłowe.

Użyj setSelection(position, false);w początkowej selekcji wcześniejsetOnItemSelectedListener(listener)

W ten sposób ustawiasz swój wybór bez animacji, co powoduje, że wywoływany jest nasłuchiwanie wybranego elementu. Ale słuchacz ma wartość zerową, więc nic nie jest uruchamiane. Następnie twój słuchacz zostaje przypisany.

Postępuj zgodnie z następującą sekwencją:

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

48
+1 ukryty klejnot! Przekazanie parametru false jako parametru „animowanego” nie wywołuje wywołania zwrotnego detektora. Niesamowite!
pkk

3
+1 Dziwne, ale eleganckie rozwiązanie :) Na szczęście i tak musiałem już zadzwonić do setSelection ...
Martin T.

35
Słuchacz nadal będzie strzelał po złożeniu elementu interfejsu Spinnera, więc będzie strzelał niezależnie od tego, co nie zapobiegnie niepożądanemu zachowaniu opisanemu przez OP. Działa to świetnie, jeśli nie zostało zadeklarowane podczas lub przed onCreateView (), ale nie o to prosili.
Rudi Kershaw

6
Przydatne, ale rozwiązuje inny problem niż przedstawiony PO. OP odnosi się do zdarzenia wyboru, które (niestety) automatycznie uruchamia się, gdy widok pojawia się po raz pierwszy, nawet jeśli programista nie wykonał setSelection .
ToolmakerSteve

2
Dla mnie rozwiązaniem był parametr wartości „false” w metodzie setSelection (..). ty!
Dani,

194

Odnosząc się do odpowiedzi Dan Dyer, spróbuj zarejestrowania OnSelectListenerw post(Runnable)metodzie:

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

Robiąc to dla mnie, w końcu pojawiło się pożądane zachowanie.

W tym przypadku oznacza to również, że słuchacz strzela tylko na zmieniony element.


1
Występuje błąd z informacją: Metoda setOnItemSelectedListener (AdapterView.OnItemSelectedListener) w typie AdapterView <SpinnerAdapter> nie ma zastosowania do argumentów (nowy Runnable () {}), dlaczego tak jest?
Jakob

Czy to zasadniczo nie ustanawia warunków wyścigu między Runnable a Wątkiem interfejsu użytkownika?
kenny_k

6
@theFunkyEngineer - Kod ten powinien być uruchamiany z jednej z głównych metod gwintu np onCreate(), onResume()itp W takim przypadku jego fantastycznej sztuczki, bez niebezpieczeństwa wyścigu. Zwykle używam tej sztuczki onCreate()zaraz po kodzie układu.
Richard Le Mesurier,

1
To świetne rozwiązanie i na pewno nie hack! Tego rodzaju funkcjonalność polega na tym, jak rzeczy są wykonywane głęboko w środowisku. Szkoda, że ​​Spinner nie robi tego wewnętrznie. Jest to jednak najczystszy sposób, aby zagwarantować uruchomienie kodu po utworzeniu działania. Działa to, ponieważ nasłuchiwanie nie jest jeszcze ustawione w pokrętle, gdy działanie próbuje je powiadomić.
jophde

1
To jest akceptowalne rozwiązanie . nie ślepy strzał. inne rozwiązania są bardziej podatne na problem zmiany zachowania w przyszłości.
Kuldeep Singh Dhaka

50

Stworzyłem małą użyteczną metodę zmiany Spinnerwyboru bez powiadamiania użytkownika:

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

Wyłącza nasłuchiwanie, zmienia wybór i ponownie włącza nasłuchiwanie.

Sztuczka polega na tym, że wywołania są asynchroniczne z wątkiem interfejsu użytkownika, więc musisz to zrobić w kolejnych postach modułu obsługi.


Niesamowite. Miałem wiele spinnerów i próbowałem ustawić wszystkich ich słuchaczy na zero przed ustawieniem ich wartości, a potem przywróciłem im wszystkich do tego, czym powinni być, ale z jakiegoś powodu to nie zadziałało. wypróbowałem tę funkcję i zadziałało. Nie wiem, dlaczego mój nie działał, ale to działa, więc nie obchodzi mnie to: D
JStephen

4
Uwaga: jeśli zadzwonisz setSpinnerSelectionWithoutCallingListenerdwukrotnie szybko, aby drugie połączenie zostało wykonane, podczas gdy pierwszy już ustawił słuchacza null, spinner utknie z nullsłuchaczem na zawsze. Proponuję następującą poprawkę: dodaj if (listener == null) return;po spinner.setSelection(selection).
Violet Giraffe

34

Niestety wydaje się, że dwa najczęściej sugerowane rozwiązania tego problemu, a mianowicie liczenie wystąpień oddzwaniania i publikowanie elementu Runnable w celu ustawienia oddzwaniania w późniejszym czasie, mogą się nie powieść, gdy na przykład włączone są opcje ułatwień dostępu. Oto klasa pomocnicza, która omawia te problemy. Dalsze wyjaśnienia znajdują się w bloku komentarza.

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
To powinna być najwyżej głosowana odpowiedź. To proste, ale genialne. Pozwala zachować tę samą bieżącą implementację, z wyjątkiem jednego wiersza, w którym inicjujesz. Zdecydowanie sprawiło, że starsze projekty były dość łatwe. Ponadto zabiłem dwa ptaki jednym kamieniem, wdrażając interfejs OnTouchLisener do zamykania klawiatury po otwarciu tarczy. Teraz wszystkie moje błystki zachowują się dokładnie tak, jak chcę.
user3829751

Piękna odpowiedź. Nadal wyzwala 0 element, gdy dodam AllAll () do adaptera, ale mój 0 element jest elipsą dla zachowania neutralnego (nic nie rób).
jwehrle

31

Miałem wiele problemów z odpalaniem tarczy, kiedy nie chciałem, a wszystkie odpowiedzi tutaj są niewiarygodne. Działają - ale tylko czasami. W końcu natkniesz się na scenariusze, w których się nie powiedzie i wprowadzisz błędy do swojego kodu.

Dla mnie działało przechowywanie ostatniego wybranego indeksu w zmiennej i ocena go w detektorze. Jeśli jest taki sam jak nowy wybrany indeks, nic nie rób i wróć, w przeciwnym razie kontynuuj od nasłuchiwania. Zrób to:

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

}

Zaufaj mi, kiedy to powiem, jest to zdecydowanie najbardziej niezawodne rozwiązanie. Hack, ale działa!


Czy to w ogóle zadziała, jeśli spróbujesz zmienić wartość? W moim przypadku próbuję ustawić wartość na coś takiego jak 3, gdy w rzeczywistości jest to 0 bez wyzwalania detektorów zmian. Czy mówisz, że int zwracam inną wartość tylko wtedy, gdy użytkownik ją wybiera?
JStephen

Cześć JStephen, nie jestem w 100% pewien, co masz na myśli. Ale int będzie pozycją pokrętła za każdym razem, gdy zostanie uruchomiony element onItemSelected. Problem polega na tym, że onItemSelected jest uruchamiany za każdym razem, gdy błystka jest ładowana po raz pierwszy, bez żadnej faktycznej interakcji użytkownika, co prowadzi do niepożądanego zachowania w tym przypadku. int i będzie równa 0 w tym punkcie początkowym, ponieważ jest to domyślny indeks początkowy, gdy przędzarka jest ładowana po raz pierwszy. Więc moje rozwiązanie sprawdza, czy wybrano rzeczywiście inny element zamiast aktualnie wybranego elementu ... Czy to odpowiada na twoje pytanie?
Chris

Cześć Chris, Mam stronę, która pobiera informacje z bazy danych do edycji przez użytkownika. kiedy strona się otworzy, wypełniam błystki, a następnie ustawiam ich pozycje na wartości, które były w bazie danych. Jeśli więc ustawię ich pozycję na przykład na 3, spowoduje to uruchomienie wyzwalacza onItemSelected z ustawieniem i na wartość 3, która różni się od początkowej. Myślałem, że mówisz, że jestem ustawiony tylko wtedy, gdy użytkownik faktycznie go zmienił.
JStephen

4
Co jeśli użytkownik wybierze pozycję 0? Zostaną zignorowani.
Yetti99

Nie sądzę, aby ostatnia pozycja była dobrym pomysłem. Inicjuję błystki, ładując pozycję z SharedPreferences i używając setSelection. Bardzo często wartości w SharedPrefs nie są takie same jak wartości domyślne podczas tworzenia błystek, więc onItemSelected będzie uruchamiany przy inicjacji.
Arthez

26

Byłem w podobnej sytuacji i mam dla siebie proste rozwiązanie.

Wydaje się, że są to metody setSelection(int position)i setSelected(int position, boolean animate)mają różne wewnętrzne wdrożenie.

Kiedy używasz drugiej metody setSelected(int position, boolean animate)z flagą false animate, otrzymujesz wybór bez odpalania onItemSelecteddetektora.


Lepszym rozwiązaniem jest nie martwienie się o dodatkowe wywołania do onItemSelected, ale upewnienie się, że pokazuje właściwy wybór. Wywołanie spinner.setSelection (selectedIndex) przed dodaniem detektora sprawiło, że działał on spójnie dla mnie.
andude

1
dla
spinnera

4
Rzeczywiste połączenie tosetSelection(int position, boolean animate);
Brad

+1 dla ciebie. Rozwiązuje to bardziej ogólny problem, gdy kod modyfikuje się więcej razy Zawartość i wybór Spinnera jest utrzymywanyIdentSelected tylko do interakcji użytkownika
alrama

4
niestety flaga fałszywej animacji nadal wywołuje onItemSelectedw API23
mcy

23

Aby tylko przedstawić wskazówki dotyczące używania onTouchListener do rozróżnienia między automatycznymi wywołaniami setOnItemSelectedListener (które są częścią inicjalizacji aktywności itp.) A wywołaniami wywołanymi przez rzeczywistą interakcję użytkownika, po wypróbowaniu kilku innych sugestii zrobiłem następujące i okazało się, że działa dobrze z najmniejszą liczbą wierszy kodu.

Wystarczy ustawić pole logiczne dla swojej aktywności / fragmentu, takie jak:

private Boolean spinnerTouched = false;

Następnie, tuż przed ustawieniem setOnItemSelectedListener błystki, ustaw 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
To działa świetnie, a od Androida 6+ jest to jedyna metoda, która działa. ALE musisz zrobić to samo z setOnKeyListener (), inaczej nie działa, gdy użytkownik nawiguje za pomocą klawiatury.
Stéphane

Działa świetnie, wszystkie inne rozwiązania mają pewne problemy z różnymi telefonami.
Ziwei Zeng

To jest proste i absolutnie idealne! Nie potrzeba żadnych dodatkowych bzdur, po prostu pamiętaj o logice. Cieszę się, że przewinąłem aż do tutaj!
user3833732,

Zamiast setOnKeyListener () można podklasować spinner i ustawić flag spinnerTouched = true w przesłoniętej metodzie preformClick (), która jest wywoływana w obu przypadkach (touch / key). Reszta jest taka sama.
Wszechmogący

Chciałem tylko wspomnieć, że wydaje się to rozwiązać ten sam błąd w DropDownPreferences, które niedawno opublikowałem tutaj: stackoverflow.com/questions/61867118/... Nie mogę do cholery w to uwierzyć tbh: D
Daniel Wilson

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

3
Kod może mówić sam za siebie, ale trochę wyjaśnienia ma długą drogę :)
nhaarman

8

Po dłuższym wyciągnięciu włosów stworzyłem własną klasę Spinner. Dodałem do niego metodę, która odpowiednio rozłącza i łączy słuchacza.

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

Użyj go w swoim XML w następujący sposób:

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

Wszystko, co musisz zrobić, to pobrać instancję SaneSpinner po inflacji i wybrać zestaw połączeń w następujący sposób:

mMySaneSpinner.setSelection(1, true, true);

Dzięki temu żadne zdarzenie nie jest uruchamiane, a interakcja użytkownika nie jest przerywana. To znacznie zmniejszyło złożoność mojego kodu. To powinno być zawarte w magazynie Android, ponieważ tak naprawdę jest to PITA.


1
To nie działa dla mnie, nadal wyzwala onItemSelected.
Arthez

Arthez, proszę dokładnie sprawdzić, czy naprawdę zgadzasz się z trzecim argumentem. Jeśli tak, coś innego jest tutaj nie tak. Jeśli to możliwe, opublikuj swój kod.
fusion44

8

Brak niepożądanych zdarzeń z fazy układu, jeśli odłożysz dodawanie detektora do momentu zakończenia układu:

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

            });

        }
    });

To działa, a IMO jest najczystszym rozwiązaniem konkretnego problemu PO. Chcę zauważyć, że możesz usunąć ViewTreeObserver.OnGlobalLayoutListenerwersje poniżej J, dzwoniąc ViewTreeObserver.removeGlobalOnLayoutListener, która jest przestarzała i ma podobną nazwę do metody używanej przez tę odpowiedź.
Jack Meister

7

Stanie się tak, jeśli dokonasz wyboru w kodzie jako;

   mSpinner.setSelection(0);

Zamiast powyższej instrukcji użyj

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

Edycja: Ta metoda nie działa dla Mi Android w wersji Mi UI.


2
To zdecydowanie rozwiązało problem dla mnie. Przeczytałem dokumentację na temat widżetu Spinner. Absolutnie trudno zrozumieć różnicę: setSelection (pozycja int, animacja logiczna) -> Przejdź bezpośrednio do określonego elementu w danych adaptera. setSelection (pozycja wewnętrzna) -> Ustawia aktualnie wybrany element.
Matt

5

Otrzymałem bardzo prostą odpowiedź, 100% pewności, że to działa:

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

Znalazłem o wiele bardziej eleganckie rozwiązanie tego problemu. Polega ona na zliczeniu, ile razy ArrayAdapter (w twoim przypadku „adapter”) został wywołany. Powiedzmy, że masz 1 przędzarkę i dzwonisz:

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

Zadeklaruj licznik int po metodzie onCreate, a następnie w metodzie onItemSelected () umieść warunek „if”, aby sprawdzić, ile razy wywołano ataptera. W twoim przypadku zadzwoniłeś tylko raz, więc:

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

2

Mój niewielki wkład jest odmianą niektórych z powyższych, które mi pasowały kilka razy.

Zadeklaruj zmienną całkowitą jako wartość domyślną (lub ostatnio używaną wartość zapisaną w preferencjach). Użyj spinner.setSelection (myDefault), aby ustawić tę wartość przed zarejestrowaniem detektora. W onItemSelected sprawdź, czy nowa wartość pokrętła jest równa wartości, którą przypisałeś przed uruchomieniem dalszego kodu.

Ma to tę dodatkową zaletę, że nie uruchamia kodu, jeśli użytkownik ponownie wybierze tę samą wartość.


1

Po tym samym problemie doszedłem do tego rozwiązania przy użyciu tagów. Pomysł jest prosty: za każdym razem, gdy pokrętło zmienia się programowo, upewnij się, że znacznik odzwierciedla wybraną pozycję. Następnie w odbiorniku sprawdzasz, czy wybrana pozycja jest równa znacznikowi. Jeśli tak, wybór pokrętła został programowo zmieniony.

Poniżej moja nowa klasa „spinner proxy”:

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

Będziesz także potrzebował pliku XML z ustawieniami znaczników w swoim Valueskatalogu. Nazwałem mój plik spinner_tag.xml, ale to zależy od ciebie. To wygląda tak:

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

Teraz wymień

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

w kodzie za pomocą

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

I spraw, by twój przewodnik wyglądał tak:

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

Funkcja isUiTriggered()zwróci wartość true tylko wtedy, gdy użytkownik zmieni przędzarkę. Zauważ, że ta funkcja ma efekt uboczny - ustawi znacznik, więc drugie połączenie w tym samym wywołaniu nasłuchującym zawsze powróci false.

To opakowanie poradzi sobie również z problemem wywołania detektora podczas tworzenia układu.

Miłej zabawy, Jens.


1

Ponieważ nic dla mnie nie działało, a moim zdaniem mam więcej niż 1 pokrętło (a IMHO trzymające mapę boolową to przesada), używam tagu do liczenia kliknięć:

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

Wiele odpowiedzi już jest, oto moja.

Rozszerzam AppCompatSpinneri dodam metodę, pgmSetSelection(int pos)która umożliwia programowe ustawienie wyboru bez wyzwalania wywołania zwrotnego wyboru. Zakodowałem to za pomocą RxJava, aby zdarzenia selekcji były dostarczane za pośrednictwem 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);
    }
}

Przykładem jego użytkowania, zwanej w onCreateView()w sposób Fragment, na przykład:

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

gdzie setSelection()jest metoda w otaczającym widoku, która wygląda tak i która jest wywoływana zarówno ze zdarzeń wyboru użytkownika poprzez Observableprogramowo, jak i gdzie indziej, więc logika obsługi selekcji jest wspólna dla obu metod selekcji.

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

Chciałbym zadzwonić

spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());

po wywołaniu setAdapter (). Spróbuj także zadzwonić przed adapterem.

Zawsze masz rozwiązanie pozwalające przejść z podklasą, w której możesz zawinąć flagę logiczną do nadpisanej metody setAdapter, aby pominąć zdarzenie.


0

Rozwiązanie z flagą logiczną lub licznikiem nie pomogło mi, ponieważ podczas zmiany orientacji onItemSelected () wywołuje „przepełnienie” flagi lub licznika.

Podklasowałem android.widget.Spinneri dodawałem małe dodatki. Odpowiednie części są poniżej. To rozwiązanie działało dla mnie.

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

To też nie jest eleganckie rozwiązanie. W rzeczywistości jest to raczej Rube-Goldberg, ale wydaje się, że działa. Upewniam się, że spinner został użyty przynajmniej raz, rozszerzając adapter macierzy i zastępując jego getDropDownView. W nowej metodzie getDropDownView mam flagę logiczną, która jest ustawiona tak, aby pokazywać, że menu rozwijane zostało użyte co najmniej raz. Ignoruję wywołania do detektora, dopóki flaga nie zostanie ustawiona.

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

przesłonięty adapter tablicy:

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

zmodyfikowany odbiornik:

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


0

Mam zrobić z najprostszy sposób:

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

        }
    });

Gotowy


To ciekawe rozwiązanie. Przydałoby się więcej wyjaśnień. Zasadniczo celowo ignoruje pierwsze zdarzenie onItemSelected. Mogą działać dobrze w niektórych przypadkach, ale nie w innych, na przykład gdy włączone są opcje ułatwień dostępu (patrz wyjaśnienie Jorrita) .
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

To moje ostatnie i łatwe w użyciu rozwiązanie:

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

Użyj domyślnego setSelection(...)dla domyślnego zachowania lub użyj setSelectionWithoutInformListener(...)do wyboru elementu w pokrętle bez wyzwalania wywołania zwrotnego OnItemSelectedListener.


0

Muszę użyć mSpinnerw ViewHolder, więc flaga mOldPositionjest ustawiona w anonimowej klasie wewnętrznej.

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

Zapisuję indeks początkowy podczas tworzenia obiektu 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

Moje rozwiązanie wykorzystuje, onTouchListenerale nie ogranicza jego użycia. Tworzy opakowanie w onTouchListenerrazie potrzeby w razie instalacji 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

Być może odpowiadam zbyt późno na ten post, jednak udało mi się to osiągnąć za pomocą biblioteki powiązań danych Android Android Databinding . Utworzyłem niestandardowe powiązanie, aby upewnić się, że słuchacz nie jest wywoływany, dopóki wybrany element nie zostanie zmieniony, więc nawet jeśli użytkownik wybiera tę samą pozycję raz po raz, zdarzenie nie zostanie uruchomione.

Plik XML układu

    <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 to miejsce, w którym podajesz pozycję do wybrania.

Wiązanie niestandardowe

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

        }
    });
}

Możesz przeczytać więcej o niestandardowym wiązaniu danych tutaj Android Custom Setter

UWAGA

  1. Nie zapomnij włączyć wiązania danych w pliku Gradle

       android {
     ....
     dataBinding {
     enabled = true
    }
    }
  2. Uwzględnij pliki układu w <layout>tagach


-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) Proszę poprawnie sformatować kod. 2) Docenione zostanie również wyjaśnienie tego, co robi Twój kod. Nie wszystkie fragmenty kodu są odczytywane natychmiast po odczytaniu kodu.
Mike Koch
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.