Yeni başlatılan bir Spinner'a ateş etmekten nasıl seçilir?


419

Bunu çözmenin zarif yollarından daha azını düşündüm, ama bir şeyleri kaçırmam gerektiğini biliyorum.

Benim onItemSelected kullanıcı ile herhangi bir etkileşim olmadan derhal yangınlar ve bu istenmeyen bir davranıştır. Kullanıcı arayüzünün herhangi bir şey yapmadan önce kullanıcı bir şey seçmesini beklemesini diliyorum.

Hatta dinleyiciyi onResume() yardımcı olacağını umarak , ama olmadı.

Kullanıcı kontrole dokunmadan önce bunun tetiklenmesini nasıl durdurabilirim?

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
Bu çözüme bakabilirsiniz, kolay ve pratiktir. stackoverflow.com/a/10102356/621951
Günay Gültekin

1
Basit bir çözüm, ilk öğeyi Spinnerboş yapmak ve içinde onItemSelectedString'in boş olup olmadığını tespit etmek olabilir startActivity!
Muhammed Babar

Bu desen düzgün çalışıyor stackoverflow.com/questions/13397933/…
saksham

Yanıtlar:


78

Çözümünüzün çalışmasını beklerdim - dinleyiciyi kurmadan önce adaptörü ayarlarsanız seçim olayı gerçekleşmez.

Bununla birlikte, basit bir boole bayrağı, haydut ilk seçim olayını algılamanıza ve görmezden gelmenize izin verir.


15
ugh, evet. Bu, yetersiz bir çözümle kastettiğim şeydi. Daha iyi bir yol olmalı gibi görünüyor. Yine de teşekkürler.
FauxReal

5
: Dev ml üzerindeki bu parçacığı bu konuda daha fazla fikir vardır groups.google.com/group/android-developers/browse_thread/thread/... ... Maalesef bir çözüm verilir -
YK

25
Bileşenleri yerleştirme işlemi, seçim dinleyicisini ateşler. Bu nedenle , düzen tamamlandıktan sonra dinleyiciyi eklemeniz gerekir . Bunu yapmak için uygun ve anlaşılır bir yer bulamadım, çünkü düzen sonra bir noktada gerçekleşiyor gibi görünüyor onResume()ve onPostResume()bu nedenle tüm normal kancalar düzen gerçekleştiği zaman tamamlandı.
Dan Dyer

28
Ben bu boolean bayrak uzak kalmak istiyorum - sanki gelecekte davranış değişiklikleri bir hataya neden olabilir. Daha kurşun geçirmez bir çözüm, seçilen ilk öğeye ilklendirilen "geçerli seçili dizine" sahip bir değişkeni tutmak olacaktır. Sonra seçim olayında - yeni konuma eşit olup olmadığını kontrol edin - geri dönün ve hiçbir şey yapmayın. Tabii ki seçimde değişkeni güncelleyin.
daniel.gindi

2
Bu çalışmıyor. Tarafından cevap @casanova works. Kabul edilen cevap bu olmalı.
Siddharth

379

Çalıştırılabilirlerin kullanımı tamamen yanlıştır.

setSelection(position, false);Önce ilk seçimde kullanınsetOnItemSelectedListener(listener)

Bu şekilde, seçili öğede bulunan dinleyicinin çağrılmasına neden olan seçiminizi animasyon olmadan ayarlarsınız. Ancak dinleyici boştur, bu yüzden hiçbir şey yapılmaz. Sonra dinleyiciniz atanır.

Bu yüzden şu sırayı takip edin:

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

48
+1 Gizli mücevher! "Animate" parametresi olarak false iletilmesi dinleyicinin geri aramasını çağırmaz. Müthiş!
pkk

3
+1 Tuhaf ama zarif bir çözüm :) Neyse ki, zaten zaten setSelection'ı çağırmak zorunda kaldım ...
Martin T.

35
Dinleyici, Spinner UI öğesi monte edildiğinde yine de ateş edecektir, bu nedenle OP tarafından açıklanan istenmeyen davranışı engellemeden bağımsız olarak ateş edecektir. Bu, onCreateView () sırasında veya öncesinde bildirilmezse harika çalışır, ancak istedikleri şey bu değildir.
Rudi Kershaw

6
Yararlı, ama sunulan OP farklı bir sorunu çözer. OP , programcı setSelection yapmasa bile görünüm ilk göründüğünde (maalesef) otomatik olarak tetiklenen bir seçim olayını ifade eder .
ToolmakerSteve

2
SetSelection (..) yöntemindeki "false" değer parametresi benim için çözümdü. ty!
Dani

195

Dan Dyer'ın cevabına bakarak, OnSelectListenerbir post(Runnable)yöntemle kaydetmeyi deneyin :

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

Bunu benim için yaparak arzu edilen davranış nihayet gerçekleşti.

Bu durumda, dinleyicinin yalnızca değiştirilmiş bir öğeye ateş ettiği anlamına gelir.


1
Bir hata mesajı alıyorum: AdapterView <SpinnerAdapter> türündeki setOnItemSelectedListener (AdapterView.OnItemSelectedListener) yöntemi (new Runnable () {}) argümanları için geçerli değil?
Jakob

Bu aslında Runnable ve UI İş Parçacığı arasında bir yarış koşulu oluşturmuyor mu?
kenny_k

6
@theFunkyEngineer - Bu kod gereken ana iş parçacığı yöntemlerinden örneğin birinden çalıştırılabilir onCreate(), onResume()yarış durumu hiçbir tehlike ile Bu durumda, vb onun fantastik bir hile. Normalde bu numarayı onCreate()düzen kodundan hemen sonra kullanıyorum.
Richard Le Mesurier

1
Bu harika bir çözüm ve kesinlikle bir kesmek değil! Bu tür işlevsellik, işlerin çerçevede derinlemesine yapılmasıdır. Spinner bunu dahili olarak yapmaz. Ancak, Etkinlik oluşturulduktan sonra bazı kodların çalışmasını garanti etmenin en temiz yolu budur. Bu durum, etkinlik onları bilgilendirmeye çalıştığında dinleyici henüz Spinner'da ayarlanmadığı için çalışır.
jophde

1
Bu kabul edilebilir bir çözümdür . kör bir atış değil. diğer çözümler gelecekte davranış değişikliği sorununa daha yatkındır.
Kuldeep Singh Dhaka

50

SpinnerKullanıcıyı bilgilendirmeden seçimi değiştirmek için küçük bir yardımcı program yöntemi oluşturdum :

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

Dinleyiciyi devre dışı bırakır, seçimi değiştirir ve bundan sonra dinleyiciyi yeniden etkinleştirir.

İşin püf noktası, çağrıların UI iş parçacığına eşzamansız olmasıdır, bu nedenle bunu ardışık işleyici gönderilerinde yapmanız gerekir.


Muhteşem. Birden fazla iplikçim vardı ve değerlerini ayarlamadan önce tüm dinleyicilerini null değerine ayarlamaya çalıştım, sonra hepsini olması gerektiği şekilde geri ayarladım, ancak bir nedenden dolayı işe yaramadı. bunun yerine bu işlevi denedi ve işe yaradı. Neden benim işe yaramadı bilmiyorum, ama bu yüzden umurumda değil çalışır: D
JStephen

4
Not: setSpinnerSelectionWithoutCallingListenerHızlı bir şekilde iki kez ararsanız, ilk çağrı dinleyiciyi zaten ayarlamışken ikinci çağrı yapılırsa null, döndürücünüz nullsonsuza kadar bir dinleyiciye yapışacaktır. Aşağıdaki düzeltmeyi öneriyorum: if (listener == null) return;sonra ekleyin spinner.setSelection(selection).
Violet Zürafa

34

Ne yazık ki, bu soruna en sık önerilen iki çözüm, yani geri arama oluşumlarını saymak ve daha sonra geri arama ayarlamak için bir Runnable yayınlamak, örneğin erişilebilirlik seçenekleri etkinleştirildiğinde başarısız olabilir. İşte bu konular üzerinde çalışan bir yardımcı sınıf. Daha fazla açıklama yorum bloğundadır.

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
Bu, en yüksek oyu alan cevap olmalıdır. Basit ama parlak. Başlattığınız tek satır hariç, geçerli tüm uygulamalarınızı aynı tutmanıza olanak tanır. Kesinlikle retro uyumlu eski projeleri oldukça kolay hale getirdi. Bunun üzerine, spinner açıldığında klavyeyi kapatmak için OnTouchLisener arabirimini uygulayarak bir taşla iki kuşu öldürdüm. Şimdi tüm iplikçilerim tam istediğim gibi davranıyor.
user3829751

Güzel cevap. Bağdaştırıcıya AddAll () eklediğimde yine de 0'ıncı öğeyi tetikliyor ancak 0ncı öğem nötr (hiçbir şey yapma) davranışı için bir üç nokta.
jwehrle

31

Ben istemediğim zaman spinner ateş ile ilgili bir sürü sorun vardı ve burada tüm cevaplar güvenilmez. Çalışıyorlar - ama sadece bazen. Sonunda başarısız olacakları senaryolarla karşılaşacaksınız ve kodunuza hatalar ekleyeceksiniz.

Benim için işe yarayan, en son seçilen dizini bir değişkende saklamak ve dinleyicide değerlendirmektir. Yeni seçilen dizinle aynıysa hiçbir şey yapmaz ve geri dönmezse, dinleyiciyle devam edin. Bunu yap:

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

}

Bunu söylediğimde bana güvenin, bu en güvenilir çözüm. Kesmek, ama işe yarıyor!


Değeri değiştirmeye çalışıyorsanız bile bu işe yarar mı? Benim durumumda, değişiklik dinleyicilerini tetiklemeden aslında 0 olduğunda değeri 3 gibi bir şeye ayarlamaya çalışıyorum. İnt i yalnızca kullanıcı seçiyorsa farklı bir değer döndürüyor musunuz?
JStephen

Merhaba JStephen, ne demek istediğinden% 100 emin değilim. Ancak onItemSelected tetiklendiğinde int i döndürücünün konumu olacaktır. Sorun, herhangi bir gerçek kullanıcı etkileşimi olmadan, ilk kez yüklendiğinde onItemSelected'ın tetiklenmesi ve bu durumda istenmeyen davranışlara yol açmasıdır. int i bu başlangıç ​​noktasında 0'a eşit olacaktır, çünkü değer değiştirici ilk yüklendiğinde varsayılan başlangıç ​​dizini budur. Bu yüzden çözümüm, seçili olan öğenin yeniden seçilmesi yerine gerçek bir farklı öğenin seçildiğinden emin olmak için kontrol eder ... Bu sorunuza cevap veriyor mu?
Chris

Merhaba Chris, ben kullanıcının düzenlemek için veritabanından bilgi çeken bir sayfa var. sayfa açıldığında, dönenleri doldurur ve konumlarını veritabanında bulunan değerlere ayarlarım. Örneğin, konumlarını 3 olarak ayarlarsam, bu onItemSelected öğesinin i'den 3'e ayarlanmış olan tetiklemesine neden olur. Sadece kullanıcı bunu gerçekten değiştirdiğinde ayarlandığımı söylediğini düşünüyordum.
JStephen

4
Kullanıcı 0 konumunu seçerse ne olur? Yok sayılırlar.
Yetti99

Son pozisyonun iyi bir fikir olduğunu düşünmüyorum. SharedPreferences'dan konum yükleyerek ve setSelection kullanarak iplikçileri başlatırım. SharedPrefs'deki değerler genellikle döndürücüler oluşturulduğunda varsayılan değerlerle aynı değildir, bu nedenle onItemSelected başlatma sırasında tetiklenir.
Arthez

26

Benzer bir durumdaydım ve benim için çalışan basit bir çözümüm var.

Yöntemler gibi görünüyor setSelection(int position)ve setSelected(int position, boolean animate)farklı bir iç uygulamaya sahipler.

İkinci yöntemi setSelected(int position, boolean animate)yanlış animasyon bayrağı ile kullandığınızda, onItemSelecteddinleyici ateşlemeden seçim yapılır .


Daha iyi yaklaşım, onItemSelected'a yapılan ekstra çağrılar hakkında endişelenmemek, ancak doğru seçimi gösterdiğinden emin olmaktır. Dinleyici eklemeden önce spinner.setSelection (selectedIndex) öğesini çağırmak, benim için tutarlı bir şekilde çalışmasını sağladı.
andude

1
değer değiştirici için setSelected (int position, boolean animate) yöntemi yok
shift66

4
İhtiyacınız olan gerçek çağrısetSelection(int position, boolean animate);
Brad

Sizin için +1. Bu, kod daha fazla kez değiştiğinde daha genel bir sorunu çözer Spinner içeriği ve seçimi devam
ediyorÖğeSadece

4
ne yazık ki yanlış animasyon bayrağı hala onItemSelectedAPI23'te çağırıyor
mcy

23

Sadece setOnItemSelectedListener'a yapılan otomatik çağrıları (Etkinlik başlatma vb .'nin bir parçası olan) ve gerçek kullanıcı etkileşimi tarafından tetiklenen çağrıları ayırt etmek için onTouchListener'ı kullanma ipuçlarını temizlemek için, burada diğer bazı önerileri denedikten sonra aşağıdakileri yaptım ve en az kod satırıyla iyi çalıştığını buldum.

Faaliyetiniz / Parçanız için aşağıdaki gibi bir Boole alanı ayarlamanız yeterlidir:

private Boolean spinnerTouched = false;

Sonra, eğiricinizin setOnItemSelectedListener öğesini ayarlamadan hemen önce, bir onTouchListener ayarlayın:

    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
Bu harika çalışıyor ve Android 6+'den beri çalışan tek yöntem bu. AMA, setOnKeyListener () ile de aynı şeyi yapmanız gerekir, aksi takdirde kullanıcı klavye ile gezintiğinde çalışmaz.
Stéphane

Harika çalışıyor, diğer tüm çözümlerin farklı telefonlarla ilgili bazı sorunları var.
Ziwei Zeng

Bu basit ve kesinlikle mükemmel! Ekstra saçmalık gerekmez, sadece mantığı aklınızda bulundurun. Ben buraya kadar aşağı kaydırdı sevindim!
user3833732

SetOnKeyListener () yerine, her iki durumda da (touch / key) çağrılan geçersiz kılınmış preformClick () yönteminde spinner'ı alt sınıflandırabilir ve flag spinnerTouched = true değerini ayarlayabilirsiniz. Dinlenme aynı.
Yüce

Sadece bunun yakın zamanda burada gönderdiğim DropDownPreferences ile aynı hatayı gidermek gibi görünüyor bahsetmek istedim: stackoverflow.com/questions/61867118/… I cant f * cking inanıyorum tbh: D
Daniel Wilson

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

3
Kod kendisi için konuşabilir, ancak biraz açıklama uzun bir yol kat ediyor :)
nhaarman

8

Saçımı uzun süre çıkardıktan sonra şimdi kendi Spinner sınıfımı oluşturdum. Dinleyicinin bağlantısını kesen ve uygun şekilde bağlayan bir yöntem ekledim.

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

XML'inizde şu şekilde kullanın:

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

Tek yapmanız gereken, enflasyondan sonra SaneSpinner örneğini almak ve şu şekilde çağrı kümesi seçimi yapmaktır:

mMySaneSpinner.setSelection(1, true, true);

Bununla hiçbir olay tetiklenmez ve kullanıcı etkileşimi kesilmez. Bu, kod karmaşıklığımı çok azalttı. Bu gerçekten bir PITA olduğu için stok Android'e dahil edilmelidir.


1
Bu benim için çalışmıyor, hala onItemSelected üzerinde tetikleniyor.
Arthez

Arthez lütfen üçüncü argümana gerçekten doğru olup olmadığınızı iki kez kontrol edin. Evet ise, burada başka bir şey yanlış. Mümkünse kodunuzu gönderin.
fusion44

8

Düzen bitene kadar dinleyiciyi eklemeyi ertelerseniz, düzen aşamasından istenmeyen etkinlikler olmaz:

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

            });

        }
    });

Bu işe yarıyor ve IMO, OP'nin özel sorununa en temiz çözüm. Kullanılmayan ve bu yanıtın kullandığı yönteme benzer bir adı olan ViewTreeObserver.OnGlobalLayoutListenerJ'nin altındaki sürümleri kaldırabileceğinizi belirtmek isterim ViewTreeObserver.removeGlobalOnLayoutListener.
Jack Meister

7

Bu kod olarak seçim yaparsanız olacaktır;

   mSpinner.setSelection(0);

Yukarıdaki ifade kullanımı yerine

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

Düzenleme: Bu yöntem Mi Android Sürüm Mi UI için çalışmaz.


2
Bu benim için problemi kesinlikle çözdü. Spinner widget'ı ile ilgili belgeleri okudum .. farkı anlamak kesinlikle zor: setSelection (int position, boolean animasyonu) -> Doğrudan adaptör verilerindeki belirli bir öğeye atlayın. setSelection (int position) -> Seçili öğeyi ayarlar.
Matt

5

Çok basit bir cevabım var,% 100 emin çalışıyor:

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

Bunun için çok daha zarif bir çözüm buldum. ArrayAdapter (sizin durumunuzda "adaptör") kaç kez çağrıldığını saymayı içerir. Diyelim ki 1 eğiriciniz var ve siz de

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

OnCreate'den sonra bir int sayacı bildirin ve sonra onItemSelected () yönteminin içinde, ataptörün kaç kez çağrıldığını kontrol etmek için bir "if" koşulu koyun. Sizin durumunuzda sadece bir kez çağrıldınız:

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

2

Benim küçük katkım, yukarıdakilerden bazılarında bana birkaç kez uyan bir varyasyon.

Bir tamsayı değişkenini varsayılan değer (veya tercihlerde kaydedilen son kullanılan değer) olarak bildirin. Dinleyici kaydedilmeden önce bu değeri ayarlamak için spinner.setSelection (myDefault) öğesini kullanın. OnItemSelected öğesinde, başka bir kod çalıştırmadan önce yeni değer değiştirici değerinin atadığınız değere eşit olup olmadığını denetleyin.

Bu, kullanıcı aynı değeri tekrar seçerse kod çalıştırmama avantajına sahiptir.


1

Aynı sorunu yaşadıktan sonra etiketleri kullanarak bu çözümlere geldim. Arkasındaki fikir basittir: Dönücü programlı olarak değiştirildiğinde, etiketin seçilen konumu yansıttığından emin olun. Dinleyicide, seçilen konumun etikete eşit olup olmadığını kontrol edersiniz. Varsa, iplik seçimi programlı olarak değiştirildi.

Yeni "spinner proxy" sınıfım aşağıdadır:

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

Ayrıca Valuesdizininizdeki etiket kurulumunu içeren bir XML dosyasına ihtiyacınız olacaktır . Dosyamı adlandırdım spinner_tag.xml, ama bu size bağlı. Şöyle görünüyor:

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

Şimdi değiştirin

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

ile kodunuzda

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

Ve işleyicinizi biraz şöyle görünmesini sağlayın:

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

İşlev isUiTriggered() , yalnızca eğirme makinesi kullanıcı tarafından değiştirildiyse true değerini döndürür. Bu işlevin bir yan etkisi olduğunu unutmayın - etiketi ayarlar, böylece aynı dinleyici çağrısında ikinci bir çağrı her zaman geri dönerfalse .

Bu sarmalayıcı, mizanpaj oluşturma sırasında çağrılan dinleyicideki sorunu da ele alacaktır.

İyi eğlenceler Jens.


1

Hiçbir şey benim için işe yaramadı ve benim görüşüme göre 1'den fazla spinner var (ve IMHO bir bool harita tutan bir overkill olduğunu) Tıklamaları saymak için etiketi kullanın:

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

Zaten bir sürü cevap, işte benim.

Bir seçim geri aramasını tetiklemeden programlı seçim ayarına izin veren AppCompatSpinnerbir yöntemi genişletip ekliyorum pgmSetSelection(int pos). Seçim olayları bir üzerinden teslim edilir, bu yüzden RxJava ile kodladım 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);
    }
}

Örnek olarak adlandırılan kullanımının onCreateView()bir Fragmentörneği:

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

burada setSelection(), ekli görünümde buna benzeyen ve hem kullanıcı seçim olaylarından hem Observablede program aracılığıyla kullanıcı seçim olaylarından çağrılan bir yöntemdir , bu nedenle seçimleri işleme mantığı her iki seçim yöntemi için ortaktır.

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

Aramaya çalışırdım

spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());

setAdapter () öğesini çağırdıktan sonra. Adaptörden önce de aramayı deneyin.

Etkinliği atlamak için, geçersiz kılınan setAdapter yönteminize bir boole bayrağı sartabileceğiniz alt sınıflama ile her zaman çözümünüz vardır.


0

Boole bayrağı veya sayacı olan çözüm bana yardımcı olmadı, çünkü yön değişikliği sırasında onItemSelected () işareti veya sayacı "overflew" olarak adlandırıyor.

Alt sınıflar android.widget.Spinneryaptım ve küçük eklemeler yaptım. İlgili parçalar aşağıdadır. Bu çözüm benim için çalıştı.

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

Bu da zarif bir çözüm değil. Aslında oldukça Rube-Goldberg ama işe yarıyor gibi görünüyor. Dizi bağdaştırıcısı genişleterek ve getDropDownView geçersiz kılma spinner en az bir kez kullanıldığından emin olun. Yeni getDropDownView yönteminde açılır menünün en az bir kez kullanıldığını göstermek için ayarlanmış bir boole bayrağı var. Bayrak ayarlanana kadar dinleyiciye yapılan çağrıları yok sayıyorum.

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

geçersiz kılınan dizi adaptörü:

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

değiştirilmiş dinleyici:

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


0

Ben var bitmiş basit yolu ile:

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

        }
    });

Bitti


Bu ilginç bir çözüm. Daha fazla açıklama kullanabilir. Temel olarak, ilk onItemSelected olayını kasıtlı olarak yok sayar. Bazı durumlarda iyi çalışabilirler, ancak erişilebilirlik seçeneklerinin etkinleştirildiği gibi diğerleri çalışmayabilir (bkz. Jorrit'in açıklaması) .
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

Bu benim son ve kullanımı kolay çözümüm:

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

setSelection(...)Varsayılan davranış için varsayılanı kullanın veya setSelectionWithoutInformListener(...)OnItemSelectedListener geri aramasını tetiklemeden çeviricideki bir öğeyi seçmek için kullanın .


0

mSpinnerBayrak mOldPositionanonim iç sınıf ayarlanır böylece ViewHolder, kullanmanız gerekir .

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

OnClickListener nesnesinin oluşturulması sırasında ilk dizini depolardım.

   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

Çözümüm kullanıyor onTouchListenerancak kullanımını kısıtlamıyor. onTouchListenerGerekirse kurulum için bir sarıcı oluşturur 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

Gönderiye çok geç cevap vermiş olabilirim, ancak bunu Android Veri bağlama kütüphanesi Android Veritabanını kullanarak başardım . Seçilen öğe değiştirilinceye kadar dinleyicinin çağrılmadığından emin olmak için özel bir ciltleme oluşturdum, böylece kullanıcı tekrar tekrar aynı konumu seçse bile olay tetiklenmez.

Düzen xml dosyası

    <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 seçilecek konumu geçtiğiniz yerdir.

Özel ciltleme

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

        }
    });
}

Özel veri bağlama hakkında daha fazla bilgiyi buradan edinebilirsiniz Android Custom Setter

NOT

  1. Gradle dosyanızda veritabanını etkinleştirmeyi unutmayın

       android {
     ....
     dataBinding {
     enabled = true
    }
    }
  2. Düzen dosyalarınızı <layout>etiketlere ekleyin


-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) Lütfen kodunuzu doğru şekilde biçimlendirin. 2) Kodunuzun ne yaptığına dair bir açıklama da takdir edilecektir. Kodu okurken kod pasajlarının tümü hemen anlaşılamaz.
Mike Koch
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.