Bagaimana menjaga agar Item tidak Terpilih dari Spinner yang baru dipakai?

419

Saya telah memikirkan beberapa cara yang kurang elegan untuk menyelesaikan ini, tetapi saya tahu saya pasti kehilangan sesuatu.

onItemSelectedFires saya langsung padam tanpa interaksi dengan pengguna, dan ini adalah perilaku yang tidak diinginkan. Saya berharap UI menunggu sampai pengguna memilih sesuatu sebelum melakukan apa pun.

Saya bahkan mencoba mengatur pendengar di onResume(), berharap itu akan membantu, tetapi tidak.

Bagaimana saya bisa menghentikan ini agar tidak menyala sebelum pengguna dapat menyentuh kontrol?

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.
    }
}
}
FauxReal
sumber
2
Anda dapat melihat solusi ini, mudah dan praktis. stackoverflow.com/a/10102356/621951
Günay Gültekin
1
Solusi sederhana adalah membuat item pertama Spinnerkosong dan di dalam onItemSelectedAnda dapat mendeteksi jika String tidak kosong lalu startActivity!
Muhammad Babar
Pola ini berfungsi dengan baik stackoverflow.com/questions/13397933/…
saksham

Jawaban:

78

Saya akan mengharapkan solusi Anda bekerja - Saya pikir acara pemilihan tidak akan menyala jika Anda mengatur adaptor sebelum mengatur pendengar.

Yang sedang berkata, bendera boolean sederhana akan memungkinkan Anda untuk mendeteksi acara seleksi pertama yang nakal dan mengabaikannya.

CommonsWare
sumber
15
ya, ya. Itulah yang saya maksud dengan solusi yang tidak bagus. Sepertinya harus ada cara yang lebih baik. Terima kasih.
FauxReal
5
Utas pada Dev ml ini memiliki wawasan lebih lanjut tentang ini: groups.google.com/group/android-developers/browse_thread/thread/… - Sayangnya tidak ada solusi yang diberikan ...
BoD
25
Proses menata komponen membakar pendengar seleksi. Karena itu Anda harus menambahkan pendengar setelah tata letak dilakukan. Saya tidak dapat menemukan tempat yang tepat dan langsung untuk melakukan ini karena tata letak tampaknya terjadi di beberapa titik setelah onResume()dan onPostResume(), jadi semua kait normal telah selesai pada saat tata letak terjadi.
Dan Dyer
28
Saya akan tinggal jauh dari bendera boolean ini - seolah-olah perubahan perilaku di masa depan dapat menyebabkan bug. Solusi yang lebih tahan peluru adalah menjaga variabel dengan "indeks terpilih saat ini", diinisialisasi ke item pertama yang dipilih. Kemudian pada acara seleksi - periksa apakah itu sama dengan posisi baru - kembali dan tidak melakukan apa pun. Tentu saja perbarui variabel pada seleksi.
daniel.gindi
2
Ini tidak bekerja. Jawaban oleh karya @casanova. Itu harus menjadi jawaban yang diterima.
Siddharth
379

Penggunaan Runnables sepenuhnya salah.

Gunakan setSelection(position, false);dalam seleksi awal sebelumnyasetOnItemSelectedListener(listener)

Dengan cara ini Anda mengatur pilihan Anda tanpa animasi yang menyebabkan pendengar yang dipilih pada item dipilih. Tapi pendengarnya nol sehingga tidak ada yang berjalan. Kemudian pendengar Anda ditugaskan.

Jadi ikuti urutan yang tepat ini:

Spinner s = (Spinner)Util.findViewById(view, R.id.sound, R.id.spinner);
s.setAdapter(adapter);
s.setSelection(position, false);
s.setOnItemSelectedListener(listener);
Brad
sumber
48
+1 Permata tersembunyi! Melewati false sebagai parameter "animate" tidak memanggil panggilan balik pendengar. Luar biasa!
pkk
3
+1 Solusi aneh tapi elegan :) Untungnya, saya tetap harus memanggil setSelection ...
Martin T.
35
Pendengar masih akan menyala ketika elemen Spinner UI dirakit, jadi itu akan menyala terlepas dari apa yang tidak mencegah perilaku yang tidak diinginkan yang dijelaskan oleh OP. Ini berfungsi baik jika tidak dideklarasikan selama atau sebelum onCreateView (), tetapi bukan itu yang mereka minta.
Rudi Kershaw
6
Berguna, tetapi memecahkan masalah yang berbeda dari yang disajikan OP. OP merujuk ke acara pemilihan yang (sayangnya) secara otomatis menyala ketika tampilan pertama kali muncul meskipun programmer tidak melakukan setSelection .
ToolmakerSteve
2
Parameter nilai "false" dalam metode setSelection (..) adalah solusi bagi saya. ty!
Dani
195

Mengacu pada jawaban Dan Dyer, cobalah mendaftar OnSelectListenerdalam post(Runnable)metode:

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

Dengan melakukan itu untuk saya perilaku yang diinginkan akhirnya terjadi.

Dalam hal ini juga berarti bahwa pendengar hanya menembak pada item yang diubah.

casaflowa
sumber
1
Saya mendapatkan pesan kesalahan: Metode setOnItemSelectedListener (AdapterView.OnItemSelectedListener) pada tipe AdapterView <SpinnerAdapter> tidak berlaku untuk argumen (Runnable baru () {}) mengapa begitu?
Jakob
Bukankah ini pada dasarnya mengatur kondisi balapan antara Runnable dan UI Thread?
kenny_k
6
@theFunkyEngineer - Kode ini harus dijalankan dari salah satu metode utas misalnya onCreate(), onResume()dll. Dalam hal ini, ini adalah trik yang fantastis, tanpa bahaya kondisi balapan. Saya biasanya menggunakan trik ini onCreate()hanya setelah kode tata letak.
Richard Le Mesurier
1
Ini solusi hebat dan jelas bukan peretasan! Fungsionalitas semacam ini adalah bagaimana segala sesuatu dilakukan jauh di dalam kerangka kerja. Sayang Spinner tidak melakukan ini secara internal. Namun, ini cara paling bersih untuk memiliki beberapa kode yang dijamin berjalan setelah pembuatan Aktivitas. Ini berfungsi karena pendengar belum disetel pada Spinner ketika Activity mencoba memberi tahu mereka.
ampun
1
Ini solusi yang bisa diterima . bukan tembakan buta. solusi lain lebih rentan terhadap masalah perubahan perilaku di masa depan.
Kuldeep Singh Dhaka
50

Saya membuat metode utilitas kecil untuk mengubah Spinnerpilihan tanpa memberi tahu pengguna:

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

Ini menonaktifkan pendengar, mengubah pilihan, dan mengaktifkan kembali pendengar setelah itu.

Kuncinya adalah bahwa panggilan tidak sinkron dengan utas UI, jadi Anda harus melakukannya di posting handler berturut-turut.

karooolek
sumber
Luar biasa. Saya memiliki banyak pemintal dan mencoba mengatur semua pendengar mereka menjadi nol sebelum menetapkan nilai-nilai mereka, kemudian saya mengatur semuanya kembali ke apa yang seharusnya, tetapi untuk beberapa alasan itu tidak berhasil. mencoba fungsi ini sebagai gantinya dan itu berhasil. Saya tidak tahu mengapa milik saya tidak berfungsi, tetapi ini berfungsi jadi saya tidak peduli: D
JStephen
4
Catatan: jika Anda menelepon setSpinnerSelectionWithoutCallingListenerdua kali dengan cepat, sehingga panggilan kedua dilakukan saat yang pertama telah mengatur pendengar null, pemintal Anda akan terjebak dengan nullpendengar selamanya. Saya mengusulkan perbaikan berikut: tambahkan if (listener == null) return;setelah spinner.setSelection(selection).
Violet Giraffe
34

Sayangnya tampaknya dua solusi yang paling umum disarankan untuk masalah ini, yaitu menghitung kejadian panggilan balik dan memposting Runnable untuk mengatur panggilan balik di lain waktu dapat gagal ketika misalnya opsi aksesibilitas diaktifkan. Inilah kelas pembantu yang menangani masalah-masalah ini. Penjelasan lebih lanjut ada di blok komentar.

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(); }
}
Jorrit
sumber
3
Ini harus menjadi jawaban dengan suara tertinggi. Sederhana namun brilian. Hal ini memungkinkan Anda untuk menjaga semua implementasi Anda saat ini tetap sama, kecuali satu baris di mana Anda menginisialisasi. Proyek retro-fitting tua yang pasti dibuat sangat mudah. Selain itu, saya membunuh dua burung dengan satu batu dengan mengimplementasikan antarmuka OnTouchLisener untuk menutup keyboard ketika spinner terbuka. Sekarang semua pemintal saya berperilaku persis seperti yang saya inginkan.
user3829751
Jawaban yang indah Ini masih memicu elemen 0 ketika saya addAll () ke adaptor tapi elemen 0 saya adalah elipsis untuk perilaku netral (tidak melakukan apa-apa).
jwehrle
31

Saya telah memiliki BANYAK masalah dengan penembakan pemintal ketika saya tidak mau, dan semua jawaban di sini tidak dapat diandalkan. Mereka bekerja - tetapi hanya kadang-kadang. Anda akhirnya akan mengalami skenario di mana mereka akan gagal dan memperkenalkan bug ke dalam kode Anda.

Apa yang berhasil bagi saya adalah menyimpan indeks yang dipilih terakhir dalam sebuah variabel dan mengevaluasinya di pendengar. Jika sama dengan indeks baru yang dipilih tidak melakukan apa pun dan kembali, lanjutkan dengan pendengar. Melakukan hal ini:

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

}

Percayalah pada saya ketika saya mengatakan ini, ini adalah solusi yang paling dapat diandalkan. Peretasan, tapi berhasil!

Chris
sumber
Apakah ini akan berhasil jika Anda mencoba mengubah nilainya? Dalam kasus saya, saya mencoba untuk menetapkan nilai ke sesuatu seperti 3 ketika sebenarnya 0 tanpa memicu perubahan pendengar. Apakah Anda mengatakan bahwa int saya hanya mengembalikan nilai yang berbeda jika pengguna memilihnya?
JStephen
Hai JStephen, saya tidak 100% yakin apa yang Anda maksud. Tetapi int i akan menjadi posisi pemintal kapan pun onItemSelected dipicu. Masalahnya adalah bahwa onItemSelected dipicu setiap kali spinner pertama kali dimuat, tanpa interaksi pengguna yang sebenarnya, yang mengarah ke perilaku yang tidak diinginkan dalam kasus ini. int i akan sama dengan 0 pada titik awal ini karena ini adalah indeks awal default ketika spinner pertama kali dimuat. Jadi solusi saya memeriksa untuk memastikan bahwa item yang berbeda sebenarnya dipilih dan bukan item yang dipilih saat ini dipilih kembali ... Apakah ini menjawab pertanyaan Anda?
Chris
Halo Chris, saya punya halaman yang menarik informasi dari database untuk diedit pengguna. ketika halaman terbuka saya mengisi pemintal, dan kemudian mengatur posisi mereka ke nilai-nilai yang ada di database. Jadi jika saya mengatur posisi mereka ke 3 misalnya, ini menyebabkan onItemSelected untuk memicu dengan saya mengatur ke 3 yang berbeda dari yang awal. Saya pikir Anda mengatakan saya hanya mengatur jika pengguna benar-benar mengubahnya sendiri.
JStephen
4
Bagaimana jika pengguna memilih posisi 0? Mereka akan diabaikan.
Yetti99
Saya tidak berpikir cara posisi terakhir adalah ide yang bagus. Saya init spinners dengan memuat posisi dari SharedPreferences dan menggunakan setSelection. Sangat sering nilai dalam SharedPrefs tidak sama dengan nilai default ketika pemintal dibuat, sehingga onItemSelected akan terpicu saat inisiasi.
Arthez
26

Saya berada dalam situasi yang sama, dan saya punya solusi sederhana yang bekerja untuk saya.

Sepertinya metode setSelection(int position)dan setSelected(int position, boolean animate)memiliki implementasi internal yang berbeda.

Ketika Anda menggunakan metode kedua setSelected(int position, boolean animate)dengan bendera bernyawa palsu, Anda mendapatkan pilihan tanpa menembakkan onItemSelectedpendengar.

Michal
sumber
Pendekatan yang lebih baik adalah tidak khawatir tentang panggilan tambahan ke onItemSelected, tetapi untuk memastikan bahwa itu menunjukkan pilihan yang tepat. Jadi, memanggil spinner.setSelection (selectedIndex) sebelum menambahkan pendengar membuatnya bekerja secara konsisten untuk saya.
andude
1
tidak ada metode
setSelected
4
Panggilan sebenarnya yang Anda butuhkan adalahsetSelection(int position, boolean animate);
Brad
+1 untuk Anda. Ini memecahkan masalah yang lebih umum ketika kode memodifikasi lebih banyak konten Spinner & menjaga pemilihan hanya dipilih untuk interaksi pengguna
alrama
4
sayangnya bendera animasi palsu masih memanggil onItemSelecteddi API23
mcy
23

Hanya untuk menghilangkan petunjuk tentang penggunaan onTouchListener untuk membedakan antara panggilan otomatis ke setOnItemSelectedListener (yang merupakan bagian dari inisialisasi Kegiatan, dll.) Vs. panggilan yang dipicu oleh interaksi pengguna yang sebenarnya, saya melakukan yang berikut setelah mencoba beberapa saran lain di sini dan menemukan bahwa itu bekerja dengan baik dengan baris kode paling sedikit.

Cukup setel bidang Boolean untuk Aktivitas / Fragmen Anda seperti:

private Boolean spinnerTouched = false;

Kemudian tepat sebelum Anda mengatur setOnItemSelectedListener pemintal Anda, atur 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;
JASON G PETERSON
sumber
1
Itu bekerja dengan baik, dan karena Android 6+, itulah satu-satunya metode yang berfungsi. TETAPI, Anda juga harus melakukan hal yang sama dengan setOnKeyListener (), atau tidak berfungsi ketika pengguna menavigasi dengan keyboard.
Stéphane
Berfungsi bagus, semua solusi lain memiliki beberapa masalah dengan telepon yang berbeda.
Ziwei Zeng
Ini sederhana dan sangat sempurna! Tidak perlu omong kosong tambahan, hanya perlu diingat logika. Saya senang saya menggulir sampai ke sini!
user3833732
Alih-alih setOnKeyListener (), Anda bisa subclass spinner dan mengatur flag spinnerTouched = true dalam metode preformClick () yang diganti, yang disebut dalam kedua kasus (sentuh / tombol). Istirahatnya sama.
Mahakuasa
Hanya ingin menyebutkan ini tampaknya untuk menyelesaikan bug yang sama dengan DropDownPreferences Saya baru-baru ini diposting di sini: stackoverflow.com/questions/61867118/... Saya tidak dapat percaya bahwa tbh: D
Daniel Wilson
13
spinner.setSelection(Adapter.NO_SELECTION, false);
j2emanue
sumber
3
Kode mungkin berbicara sendiri, tetapi sedikit penjelasan berjalan jauh :)
nhaarman
8

Setelah mencabut rambut saya untuk waktu yang lama sekarang saya telah membuat kelas Spinner saya sendiri. Saya telah menambahkan metode yang memutuskan dan menghubungkan pendengar dengan tepat.

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

Gunakan dalam XML Anda seperti ini:

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

Yang harus Anda lakukan adalah mengambil instance SaneSpinner setelah inflasi dan memanggil set pilihan seperti ini:

mMySaneSpinner.setSelection(1, true, true);

Dengan ini, tidak ada peristiwa yang dipecat dan interaksi pengguna tidak terganggu. Ini banyak mengurangi kompleksitas kode saya. Ini harus dimasukkan dalam stok Android karena itu benar-benar PITA.

fusion44
sumber
1
Ini tidak bekerja untuk saya, masih memicu onItemSelected.
Arthez
Arthez, silakan periksa kembali apakah Anda benar-benar menyetujui argumen ketiga. Jika ya ada sesuatu yang salah di sini. Jika memungkinkan poskan kode Anda.
fusion44
8

Tidak ada kejadian yang tidak diinginkan dari fase tata letak jika Anda menunda menambahkan pendengar sampai tata letak selesai:

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

            });

        }
    });
redocoder
sumber
Ini berfungsi, dan IMO itu adalah solusi terbersih untuk masalah spesifik OP. Saya ingin mencatat bahwa Anda dapat menghapus ViewTreeObserver.OnGlobalLayoutListenerversi di bawah J dengan memanggil ViewTreeObserver.removeGlobalOnLayoutListener, yang sudah usang dan memiliki nama yang mirip dengan metode yang digunakan jawaban ini.
Jack Meister
7

Ini akan terjadi jika Anda membuat seleksi dalam kode sebagai;

   mSpinner.setSelection(0);

Alih-alih menggunakan pernyataan di atas

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

Sunting: Metode ini tidak berfungsi untuk Mi Android Version Mi UI.

Uzair
sumber
2
Ini jelas memecahkan masalah bagi saya. Saya membaca dokumentasi tentang widget Spinner .. benar-benar sulit untuk memahami perbedaannya: setSelection (posisi int, boolean animate) -> Langsung langsung ke item tertentu di data adaptor. setSelection (posisi int) -> Mengatur item yang dipilih saat ini.
Matt
5

Saya mendapat jawaban yang sangat sederhana, 100% yakin itu berfungsi:

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
}
pengguna6656805
sumber
3

Saya telah menemukan solusi yang jauh lebih elegan untuk ini. Ini melibatkan menghitung berapa kali ArrayAdapter (dalam kasus Anda "adaptor") telah dipanggil. Katakanlah Anda memiliki 1 pemintal dan Anda menelepon:

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

Deklarasikan penghitung int setelah onCreate dan kemudian di dalam metode onItemSelected () beri syarat "jika" untuk memeriksa berapa kali atapter telah dipanggil. Dalam kasus Anda, Anda telah memanggilnya sekali saja:

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

Kontribusi kecil saya adalah variasi pada beberapa hal di atas yang cocok untuk saya beberapa kali.

Deklarasikan variabel integer sebagai nilai default (atau nilai yang digunakan terakhir disimpan dalam preferensi). Gunakan spinner.setSelection (myDefault) untuk menetapkan nilai itu sebelum pendengar terdaftar. Di cek onItemSelected apakah nilai pemintal baru sama dengan nilai yang Anda tetapkan sebelum menjalankan kode lebih lanjut.

Ini memiliki keuntungan tambahan dari tidak menjalankan kode jika pengguna memilih nilai yang sama lagi.

David Walton
sumber
1

Setelah mengalami masalah yang sama, saya sampai pada solusi ini menggunakan tag. Gagasan di baliknya sederhana: Setiap kali pemintal diubah secara programatis, pastikan tag mencerminkan posisi yang dipilih. Di pendengar maka Anda memeriksa apakah posisi yang dipilih sama dengan tag. Jika ya, pemilihan pemintal diubah secara programatik.

Di bawah ini adalah kelas "proxy pemintal" baru saya:

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

Anda juga akan memerlukan file XML dengan pengaturan tag di Valuesdirektori Anda . Saya memberi nama file saya spinner_tag.xml, tetapi terserah Anda. Ini terlihat seperti ini:

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

Sekarang ganti

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

dalam kode Anda dengan

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

Dan buat pawang Anda terlihat seperti ini:

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

Fungsi isUiTriggered()akan mengembalikan true jika dan hanya jika pemintal telah diubah oleh pengguna. Perhatikan bahwa fungsi ini memiliki efek samping - ia akan mengatur tag, sehingga panggilan kedua dalam panggilan pendengar yang sama akan selalu kembali false.

Pembungkus ini juga akan menangani masalah dengan pendengar yang dipanggil selama pembuatan tata letak.

Bersenang-senanglah, Jens.

Jens
sumber
1

Karena tidak ada yang bekerja untuk saya, dan saya memiliki lebih dari 1 pemintal dalam pandangan saya (dan IMHO memegang peta bool adalah kerja keras) Saya menggunakan tag untuk menghitung klik:

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

Sudah banyak jawaban, ini milik saya.

Saya memperluas AppCompatSpinnerdan menambahkan metode pgmSetSelection(int pos)yang memungkinkan pengaturan seleksi terprogram tanpa memicu callback seleksi. Saya sudah mengkodekan ini dengan RxJava sehingga acara seleksi dikirim melalui 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);
    }
}

Contoh penggunaannya, yang disebut di onCreateView()dalam Fragmentmisalnya:

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

di mana setSelection()adalah metode dalam tampilan terlampir yang terlihat seperti ini, dan yang disebut baik dari peristiwa pemilihan pengguna melalui Observabledan juga di tempat lain secara terprogram, sehingga logika untuk menangani pemilihan umum untuk kedua metode seleksi.

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
}
Clyde
sumber
0

Saya akan mencoba menelepon

spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());

setelah Anda memanggil setAdapter (). Coba juga menelepon sebelum adaptor.

Anda selalu memiliki solusi untuk pergi dengan subclassing, di mana Anda dapat membungkus bendera boolean ke metode setAdapter yang diganti untuk melewati acara tersebut.

Pentium10
sumber
0

Solusi dengan bendera boolean atau penghitung tidak membantu saya, karena selama perubahan orientasi padaItemSelected () panggilan "meluap" bendera atau penghitung.

Saya subkelas android.widget.Spinnerdan membuat tambahan kecil. Bagian yang relevan di bawah ini. Solusi ini berhasil untuk saya.

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;
}
RobinBobin
sumber
0

Ini juga bukan solusi yang elegan. Sebenarnya itu agak Rube-Goldberg tetapi tampaknya berhasil. Saya memastikan pemintal telah digunakan setidaknya sekali dengan memperluas adaptor array dan mengesampingkan getDropDownView-nya. Dalam metode getDropDownView baru saya memiliki bendera boolean yang diatur untuk menunjukkan menu dropdown telah digunakan setidaknya sekali. Saya mengabaikan panggilan ke pendengar sampai bendera ditetapkan.

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

mengganti susunan adaptor:

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

pendengar yang dimodifikasi:

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

jika Anda perlu membuat kembali aktivitas dengan cepat misalnya: mengubah tema, flag / counter sederhana tidak akan berfungsi

gunakan fungsi onUserInteraction () untuk mendeteksi aktivitas pengguna,

referensi: https://stackoverflow.com/a/25070696/4772917

dev-gaek
sumber
0

Saya telah melakukan dengan cara paling sederhana:

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

        }
    });

Selesai

Hiren Patel
sumber
Ini solusi yang menarik. Bisa menggunakan penjelasan lebih lanjut. Pada dasarnya ini sengaja mengabaikan acara onItemSelected pertama. Mereka mungkin bekerja dengan baik dalam beberapa kasus tetapi tidak yang lain seperti ketika opsi aksesibilitas diaktifkan (lihat penjelasan Jorrit) .
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) {

     }
});
Gennady Kozlov
sumber
0

Itu solusi terakhir dan mudah saya gunakan:

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

Gunakan default setSelection(...)untuk perilaku default atau gunakan setSelectionWithoutInformListener(...)untuk memilih item di spinner tanpa memicu callback OnItemSelectedListener.

MatPag
sumber
0

Saya perlu menggunakan mSpinnerdi ViewHolder, jadi flag mOldPositiondiatur dalam kelas dalam anonim.

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
            }
        });
Francis Bacon
sumber
0

Saya akan menyimpan indeks awal selama pembuatan objek 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) {
      }
  });
Ray Lionfang
sumber
0

Solusi saya menggunakan onTouchListenertetapi tidak membatasi penggunaannya. Ini membuat pembungkus onTouchListenerjika perlu di mana pengaturan 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;
    }
}
Dem0n13
sumber
0

Saya mungkin menjawab terlambat atas posting, namun saya berhasil mencapai ini menggunakan perpustakaan Data mengikat Android Android Databinding . Saya membuat pengikatan khusus untuk memastikan pendengar tidak dipanggil sampai item yang dipilih diubah sehingga meskipun pengguna memilih posisi yang sama berulang kali acara tidak dipecat.

Layout file xml

    <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 adalah posisi yang Anda lewati untuk dipilih.

Ikatan khusus

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

        }
    });
}

Anda dapat membaca lebih lanjut tentang pengikatan data khusus di sini Android Custom Setter

CATATAN

  1. Jangan lupa untuk mengaktifkan penyatuan data dalam file Gradle Anda

       android {
     ....
     dataBinding {
     enabled = true
    }
    }
  2. Sertakan file tata letak Anda dalam <layout>tag

N.Moudgil
sumber
-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) {
            }
        });
Saurabh Malik
sumber
2
1) Harap format kode Anda dengan benar. 2) Penjelasan tentang apa yang dilakukan kode Anda juga akan dihargai. Tidak semua cuplikan kode langsung dipahami saat membaca kode.
Mike Koch