style llenar example custom android spinner android-spinner

llenar - spinner android example



¿Cómo evitar que ONItemSelected se dispare con un Spinner recientemente creado? (30)

He pensado en algunas formas menos elegantes de resolver esto, pero sé que debo estar perdiendo algo.

My onItemSelected dispara inmediatamente sin ninguna interacción con el usuario, y este es un comportamiento no deseado. Deseo que la UI espere hasta que el usuario seleccione algo antes de hacer algo.

Incluso intenté configurar el oyente en onResume() , esperando que eso ayude, pero no lo hace.

¿Cómo puedo evitar que esto se dispare antes de que el usuario pueda tocar el control?

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


Creé un pequeño método de utilidad para cambiar la selección de Spinner sin notificar al usuario:

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

Deshabilita el escucha, cambia la selección y vuelve a habilitar el escucha después de eso.

El truco es que las llamadas son asíncronas al subproceso de la interfaz de usuario, por lo que tiene que hacerlo en publicaciones de controlador consecutivas.


Dado que nada me funcionó, y tengo más de 1 girador en mi vista (y, en mi humilde opinión, tener un mapa bool es una exageración), uso la etiqueta para contar los clics:

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


Después de haber tenido el mismo problema, llegué a estas soluciones utilizando etiquetas. La idea detrás de esto es simple: siempre que se cambie programáticamente el control de giro, asegúrese de que la etiqueta refleje la posición seleccionada. En el oyente, verifica si la posición seleccionada es igual a la etiqueta. Si lo hace, la selección de spinner se cambió programáticamente.

Debajo está mi nueva clase de "proxy de giro":

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

También necesitará un archivo XML con la configuración de la etiqueta en su directorio de Values . spinner_tag.xml mi archivo spinner_tag.xml , pero eso depende de usted. Se parece a esto:

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

Ahora reemplazar

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

en su código con

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

Y haz que tu manejador se vea algo así:

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

La función isUiTriggered() devolverá verdadero si y solo si el usuario ha cambiado la rueda giratoria. Tenga en cuenta que esta función tiene un efecto secundario: establecerá la etiqueta, por lo que una segunda llamada en la misma llamada de escucha siempre devolverá false .

Esta envoltura también manejará el problema cuando se llame al oyente durante la creación del diseño.

Que te diviertas, Jens.


Después de sacarme el pelo durante mucho tiempo, ahora he creado mi propia clase de Spinner. Le he agregado un método que desconecta y conecta al oyente adecuadamente.

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

Úsalo en tu XML de esta manera:

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

Todo lo que tiene que hacer es recuperar la instancia de SaneSpinner después del inflado y seleccionar el conjunto de llamadas de esta manera:

mMySaneSpinner.setSelection(1, true, true);

Con esto, no se dispara ningún evento y la interacción del usuario no se interrumpe. Esto redujo mucho la complejidad de mi código. Esto debería ser incluido en el stock de Android ya que realmente es un PITA.


El uso de Runnables es completamente incorrecto.

Utilice setSelection(position, false); en la selección inicial antes de setOnItemSelectedListener(listener)

De esta manera, establece su selección sin animación, lo que hace que se llame al oyente seleccionado en el elemento. Pero el oyente es nulo por lo que no se ejecuta nada. Entonces su oyente es asignado.

Así que sigue esta secuencia exacta:

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


Estaba en una situación similar y tengo una solución simple que funciona para mí.

Parece que los métodos setSelection(int position) y setSelected(int position, boolean animate) tienen diferentes implementaciones internas.

Cuando utiliza el segundo método setSelected(int position, boolean animate) con un indicador animado falso, obtiene la selección sin disparar en el oyente onItemSelected .


Esto sucederá si está haciendo la selección en el código como;

mSpinner.setSelection(0);

En lugar de uso de la declaración anterior

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

Edición: este método no funciona para Mi versión de Android Mi UI.


Habría esperado que su solución funcionara; pensé que el evento de selección no se activaría si configuraba el adaptador antes de configurar el oyente.

Dicho esto, una simple bandera booleana le permitiría detectar el evento de primera selección maliciosa e ignorarlo.


He encontrado una solución mucho más elegante a esto. Implica contar cuántas veces se ha invocado el ArrayAdapter (en su caso, el "adaptador"). Digamos que tienes 1 spinner y llamas:

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

Declare un contador int después del método onCreate y luego dentro del método onItemSelected () ponga una condición "if" para verificar cuántas veces se ha llamado al atapter. En tu caso lo has llamado solo una vez así:

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


He tenido MUCHOS problemas con la activación de la rueda giratoria de cuando no quería hacerlo, y todas las respuestas aquí son poco confiables. Funcionan, pero sólo a veces. Eventualmente se encontrará con situaciones en las que fallarán e introducirá errores en su código.

Lo que funcionó para mí fue almacenar el último índice seleccionado en una variable y evaluarlo en el oyente. Si es lo mismo que el nuevo índice seleccionado, no haga nada y regrese, de lo contrario continúe con el oyente. Hacer esto:

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

Confíe en mí cuando digo esto, esta es la solución más confiable. Un truco, pero funciona!


Lamentablemente, parece que las dos soluciones más comúnmente sugeridas para este problema, a saber, contar las incidencias de devolución de llamada y publicar un Runnable para establecer la devolución de llamada en un momento posterior, pueden fallar cuando, por ejemplo, las opciones de accesibilidad están habilitadas. Aquí hay una clase de ayuda que trabaja alrededor de estos problemas. Una explicación más detallada está en el bloque de comentarios.

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


Mi pequeña contribución es una variación de algunas de las anteriores que me ha convenido varias veces.

Declare una variable entera como un valor predeterminado (o el último valor utilizado guardado en las preferencias). Use spinner.setSelection (myDefault) para establecer ese valor antes de que se registre el oyente. En onItemSelected, verifique si el nuevo valor del spinner es igual al valor que asignó antes de ejecutar cualquier otro código.

Esto tiene la ventaja adicional de no ejecutar el código si el usuario selecciona el mismo valor nuevamente.


Muchas respuestas ya, aquí está la mía.

AppCompatSpinner y agrego un método pgmSetSelection(int pos) que permite la configuración de selección programática sin activar una devolución de llamada de selección. He codificado esto con RxJava para que los eventos de selección se entreguen a través de un 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); } }

Un ejemplo de su uso, llamado onCreateView() en un Fragment por ejemplo:

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

donde setSelection() es un método en la vista setSelection() que se ve así, y se llama tanto a los eventos de selección del usuario a través del Observable como a cualquier otro lugar programáticamente, por lo que la lógica para manejar las selecciones es común a ambos métodos de selección.

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 }


No hay eventos no deseados de la fase de diseño si aplaza la adición del oyente hasta que finalice el diseño:

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


Refiriéndose a la respuesta de Dan Dyer, intente registrar el OnSelectListener en un método de post(Runnable) :

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

Al hacer eso por mí, el comportamiento deseado finalmente ocurrió.

En este caso, también significa que el oyente solo dispara sobre un elemento modificado.


Solo para hacer sugerencias sobre el uso de onTouchListener para distinguir entre llamadas automáticas al setOnItemSelectedListener (que son parte de la inicialización de la actividad, etc.) frente a las llamadas activadas por la interacción real del usuario, hice lo siguiente después de intentar algunas otras sugerencias aquí y Encontró que funcionó bien con la menor cantidad de líneas de código.

Solo establece un campo booleano para tu Actividad / Fragmento como:

private Boolean spinnerTouched = false;

Luego, justo antes de configurar el setOnItemSelectedListener de tu hilandero, establece un 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;


Tengo una respuesta muy simple, 100% segura de que funciona:

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 }


Trataría de llamar

spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());

después de llamar a setAdapter (). También intente llamar antes del adaptador.

Siempre tiene la solución para ir con subclases, donde puede ajustar una bandera booleana a su método anular setAdapter para omitir el evento.


Almacenaría el índice inicial durante la creación del objeto 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) { } });


Esa es mi solución final y fácil de usar:

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

Use el setSelection(...)comportamiento predeterminado para el comportamiento predeterminado o use setSelectionWithoutInformListener(...)para seleccionar un elemento en el control de giro sin activar la devolución de llamada OnItemSelectedListener.


La solución con una bandera booleana o un contador no me ayudó, porque durante el cambio de orientación onItemSelected () llama "overflew" a la bandera o al contador.

Subcomité android.widget.Spinnere hice pequeñas adiciones. Las partes relevantes están abajo. Esta solución funcionó para mí.

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


si necesita recrear la actividad sobre la marcha, por ejemplo: cambiar de tema, una simple marca / contador no funcionará

usa la función onUserInteraction () para detectar la actividad del usuario,

referencia: https://.com/a/25070696/4772917


Esto tampoco es una solución elegante. De hecho, es más bien Rube-Goldberg pero parece funcionar. Me aseguro de que el control de giro se haya utilizado al menos una vez, extendiendo el adaptador de matriz y anulando su getDropDownView. En el nuevo método getDropDownView tengo una bandera booleana configurada para mostrar que el menú desplegable se ha utilizado al menos una vez. Ignoro las llamadas al oyente hasta que se establece la bandera.

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

anular el adaptador de matriz:

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

oyente modificado:

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


Lo he hecho de la manera más sencilla:

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

Hecho


Mi solución usa onTouchListenerpero no restringe su uso. Crea una envoltura para onTouchListenersi es necesario donde la configuración 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; } }


Necesito usar mSpinneren ViewHolder, por lo que la bandera mOldPositionse establece en la clase interna anónima.

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


Puede que esté respondiendo demasiado tarde en la publicación, sin embargo, logré esto utilizando la biblioteca de enlace de datos de Android . Enlace de datos de Android . Creé un enlace personalizado para asegurarme de que no se llame al oyente hasta que se cambie el elemento seleccionado, por lo que incluso si el usuario está seleccionando la misma posición una y otra vez, el evento no se activa.

Diseño de archivo 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 Es donde está pasando la posición para ser seleccionado.

Encuadernación personalizada

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

Puede leer más sobre enlace de datos personalizado aquí Setter personalizado de Android

NOTA

  1. No olvide habilitar el enlace de datos en su archivo Gradle

    android { .... dataBinding { enabled = true } }

  2. Incluya sus archivos de diseño en las <layout>etiquetas


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


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


spinner.setSelection(Adapter.NO_SELECTION, false);