¿Cómo evitar que OnItemSelected dispare en un Spinner recién instanciado?

419

He pensado en formas poco elegantes de resolver esto, pero sé que me falta algo.

Mi se onItemSelecteddispara inmediatamente sin ninguna interacción con el usuario, y este es un comportamiento no deseado. Deseo que la interfaz de usuario espere hasta que el usuario seleccione algo antes de hacer algo.

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

¿Cómo puedo evitar que esto se active 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.
    }
}
}
FauxReal
fuente
2
Puede ver esta solución, es fácil y práctica. stackoverflow.com/a/10102356/621951
Günay Gültekin
1
¡Una solución simple sería hacer que el primer elemento esté Spinnervacío y dentro onItemSelectedpueda detectar si la Cadena no está vacía startActivity!
Muhammad Babar
Este patrón funciona correctamente stackoverflow.com/questions/13397933/…
saksham

Respuestas:

78

Hubiera esperado que su solución funcionara, aunque pensé que el evento de selección no se dispararía si configura el adaptador antes de configurar el oyente.

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

CommonsWare
fuente
15
ugh, si. Eso es lo que quise decir con una solución poco elegante. Parece que debe haber una mejor manera. Gracias de cualquier forma.
FauxReal
55
Este hilo en el Dev ml tiene más información sobre esto: groups.google.com/group/android-developers/browse_thread/thread/… - Desafortunadamente no se da una solución ...
BoD
25
El proceso de diseño de los componentes activa el oyente de selección. Por lo tanto, deberá agregar el oyente después de que se haya realizado el diseño. No he podido encontrar un lugar adecuado y sencillo para hacer esto, ya que el diseño parece suceder en algún momento posterior onResume()y onPostResume(), por lo tanto, todos los ganchos normales se han completado para cuando se produce el diseño.
Dan Dyer
28
Me mantendría alejado de esta bandera booleana, ya que si el comportamiento cambia en el futuro podría causar un error. Una solución más a prueba de balas sería mantener una variable con el "índice seleccionado actual", inicializado en el primer elemento seleccionado. Luego, en el evento de selección, verifique si es igual a la nueva posición, regrese y no haga nada. Por supuesto, actualice la variable en la selección.
daniel.gindi
2
Esto no funciona. Respuesta de @casanova works. Esa debería ser la respuesta aceptada.
Siddharth
379

El uso de Runnables es completamente incorrecto.

Usar setSelection(position, false);en la selección inicial antessetOnItemSelectedListener(listener)

De esta manera, configura 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.

Entonces 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);
Puntilla
fuente
48
+1 joya escondida! Pasar falso como parámetro "animado" no llama a la devolución de llamada del oyente. ¡Increíble!
PK
3
+1 Solución extraña pero elegante :) Afortunadamente, de todos modos ya tuve que llamar a setSelection ...
Martin T.
35
El oyente seguirá disparando cuando el elemento Spinner UI esté ensamblado, por lo que disparará independientemente de lo que no evite el comportamiento no deseado descrito por el OP. Esto funciona muy bien si no se declara durante o antes de onCreateView (), pero eso no es lo que pidieron.
Rudi Kershaw
66
Útil, pero resuelve un problema diferente al OP presentado. OP se refiere a un evento de selección que (desafortunadamente) se dispara automáticamente cuando aparece la vista por primera vez , aunque el programador no configuró la selección .
ToolmakerSteve
2
El parámetro de valor "falso" en el método setSelection (..) fue la solución para mí. ty!
Dani
195

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

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 en un elemento modificado.

casaflowa
fuente
1
Recibo un error que dice: El método setOnItemSelectedListener (AdapterView.OnItemSelectedListener) en el tipo AdapterView <SpinnerAdapter> no es aplicable para los argumentos (nuevo Runnable () {}) ¿por qué es eso?
Jakob
¿No se trata esencialmente de establecer una condición de carrera entre Runnable y UI Thread?
kenny_k
66
@theFunkyEngineer: este código debe ejecutarse desde uno de los principales métodos de subproceso, por ejemplo onCreate(), onResume()etc. En ese caso, es un truco fantástico, sin peligro de condición de carrera. Normalmente uso este truco onCreate()justo después del código de diseño.
Richard Le Mesurier
1
¡Esta es una gran solución y definitivamente no es un truco! Este tipo de funcionalidad es cómo se hacen las cosas profundamente en el marco. Es una pena que Spinner no haga esto internamente. Sin embargo, esta es la forma más limpia de garantizar que se ejecute algún código después de la creación de la Actividad. Esto funciona porque el oyente aún no está configurado en el Spinner cuando la Actividad intenta notificarlo.
jophde
1
Esta es una solución aceptable . No es un tiro a ciegas. Otras soluciones son más propensas a problemas de cambio de comportamiento en el futuro.
Kuldeep Singh Dhaka
50

Creé un pequeño método de utilidad para cambiar la Spinnerselección 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);
                }
            });
        }
    });
}

Inhabilita al oyente, cambia la selección y vuelve a habilitar el oyente después de eso.

El truco es que las llamadas son asíncronas al hilo de la interfaz de usuario, por lo que debe hacerlo en publicaciones de manejador consecutivas.

karooolek
fuente
Increíble. Tuve múltiples hilanderos e intenté poner a todos sus oyentes en nulo antes de establecer sus valores, luego los puse de nuevo a lo que se suponía que eran, pero por alguna razón eso no funcionó. Probé esta función en su lugar y funcionó. No sé por qué el mío no funcionó, pero esto funciona, así que no me importa: D
JStephen
44
Nota: si llama setSpinnerSelectionWithoutCallingListenerdos veces rápidamente, de modo que la segunda llamada se realiza mientras la primera ya ha configurado el oyente null, su ruleta estará atascada con un nulloyente para siempre. Propongo la siguiente solución: agregar if (listener == null) return;después spinner.setSelection(selection).
Violet Giraffe
34

Desafortunadamente, parece que las dos soluciones más comúnmente sugeridas para este problema, a saber, contar las ocurrencias de devolución de llamada y publicar un Runnable para configurar 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 auxiliar que soluciona estos problemas. Más explicaciones se encuentran 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(); }
}
Jorrit
fuente
3
Esta debería ser la respuesta más votada. Es simple pero brillante. Le permite mantener toda su implementación actual igual, excepto la línea donde inicializa. Definitivamente hizo que los proyectos antiguos de ajuste retro fueran bastante fáciles. Además de eso, maté a dos pájaros de un tiro al implementar la interfaz OnTouchLisener para cerrar el teclado cuando se abre la ruleta. Ahora todos mis hilanderos se comportan exactamente como yo quiero.
user3829751
Hermosa respuesta Todavía se dispara al elemento 0 cuando agrego All () al adaptador, pero mi elemento 0 es una elipsis para un comportamiento neutral (no hacer nada).
jwehrle
31

He tenido MUCHOS problemas con el disparo de la ruleta cuando no quería, y todas las respuestas aquí no son confiables. Funcionan, pero solo a veces. Eventualmente se encontrará con escenarios donde fallarán e introducirán 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ía en mí cuando digo esto, esta es, con mucho, la solución más confiable. Un truco, pero funciona!

Chris
fuente
¿Funcionará esto incluso si está intentando cambiar el valor? En mi caso, estoy tratando de establecer el valor en algo así como 3 cuando en realidad es 0 sin activar los oyentes de cambio. ¿Está diciendo que int i solo devuelve un valor diferente si el usuario lo está seleccionando?
JStephen
Hola JStephen, no estoy 100% seguro de lo que quieres decir. Pero int i será la posición de la ruleta cada vez que se active onItemSelected. El problema es que onItemSelected se activa cada vez que se carga por primera vez la ruleta, sin ninguna interacción real del usuario, lo que conduce a un comportamiento no deseado en este caso. int i será igual a 0 en este punto inicial ya que este es el índice de inicio predeterminado cuando se carga por primera vez la ruleta. Entonces, mi solución verifica para asegurarse de que se haya seleccionado un elemento diferente real en lugar de que el elemento seleccionado actualmente se vuelva a seleccionar ... ¿Responde esto a su pregunta?
Chris
Hola Chris, tengo una página que extrae información de la base de datos para que el usuario la edite. Cuando se abre la página, relleno los hilanderos y luego establezco sus posiciones en los valores que estaban en la base de datos. Entonces, si configuro su posición en 3, por ejemplo, esto provoca que onItemSelected se active con i set en 3, que es diferente de la inicial. Estaba pensando que estabas diciendo que solo estoy configurado si el usuario realmente lo cambió.
JStephen
44
¿Qué pasa si el usuario selecciona la posición 0? Serán ignorados.
Yetti99
No creo que la última posición sea una buena idea. Inicio los hilanderos cargando la posición desde SharedPreferences y usando setSelection. Muy a menudo, los valores en SharedPrefs no son los mismos que los valores predeterminados cuando se crean los hilanderos, por lo que onItemSelected se activará al inicio.
Arthez
26

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

Parecen métodos setSelection(int position)y setSelected(int position, boolean animate)tienen una implementación interna diferente.

Cuando usa el segundo método setSelected(int position, boolean animate)con una bandera de animación falsa, obtiene la selección sin disparar al onItemSelectedoyente.

Michal
fuente
El mejor enfoque es no preocuparse por las llamadas adicionales a onItemSelected, sino asegurarse de que muestre la selección correcta. Entonces, llamar a spinner.setSelection (selectedIndex) antes de agregar oyente hizo que funcionara constantemente para mí.
andude
1
no hay un método setSelected (int position, boolean animate) para spinner
shift66
44
La llamada real que necesita essetSelection(int position, boolean animate);
Brad
+1 para ti Esto resuelve un problema más general cuando el código modificar más veces Spinner contenido y mantenimiento de la selección onItemSelected sólo para la interacción del usuario
alrama
44
La bandera de animación tristemente falsa todavía llama onItemSelecteden API23
mcy
23

Solo para dar pistas 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 llamadas activadas por la interacción real del usuario, hice lo siguiente después de probar algunas otras sugerencias aquí y descubrió que funcionaba bien con la menor cantidad de líneas de código.

Simplemente configure un campo booleano para su Actividad / Fragmento como:

private Boolean spinnerTouched = false;

Luego, justo antes de configurar el setOnItemSelectedListener de su ruleta, configure 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;
JASON G PETERSON
fuente
1
Eso funciona muy bien, y desde Android 6+, ese es el único método que funciona. PERO, también tiene que hacer lo mismo con setOnKeyListener (), o no funciona cuando el usuario navega con el teclado.
Stéphane
Funciona muy bien, todas las demás soluciones tienen algún problema con diferentes teléfonos.
Ziwei Zeng
¡Esto es simple y absolutamente perfecto! No se necesitan tonterías adicionales, solo tenga en cuenta la lógica. ¡Me alegro de haberme desplazado hasta aquí!
user3833732
En lugar de setOnKeyListener (), puede subclasificar spinner y establecer flag spinnerTouched = true en el método preformClick () anulado, que se llama en ambos casos (toque / tecla). El descanso es igual.
Todopoderoso
Solo quería mencionar que esto parece resolver el mismo error con DropDownPreferences que publiqué recientemente aquí: stackoverflow.com/questions/61867118/… No puedo creerlo tbh: D
Daniel Wilson
13
spinner.setSelection(Adapter.NO_SELECTION, false);
j2emanue
fuente
3
El código puede hablar por sí mismo, pero una pequeña explicación es muy
útil
8

Después de arrancarme el pelo durante mucho tiempo, he creado mi propia clase de Spinner. Le agregué un método que desconecta y conecta al oyente apropiadamente.

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

Úselo en su XML así:

<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 de la inflación y llamar a la selección de conjuntos de esta manera:

mMySaneSpinner.setSelection(1, true, true);

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

fusion44
fuente
1
Esto no funciona para mí, todavía activa onItemSelected.
Arthez
Arthez, comprueba si realmente estás pasando fiel al tercer argumento. Si sí, algo más está mal aquí. Si es posible, publique su código.
fusion44
8

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

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
fuente
Esto funciona, y la OMI es la solución más limpia para el problema específico del OP. Quiero señalar que puede eliminar las ViewTreeObserver.OnGlobalLayoutListenerversiones anteriores a J llamando ViewTreeObserver.removeGlobalOnLayoutListener, que está en desuso y tiene un nombre similar al método que utiliza esta respuesta.
Jack Meister
7

Esto sucederá si realiza una selección en el código como;

   mSpinner.setSelection(0);

En lugar del uso de la declaración anterior

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

Editar: Este método no funciona para Mi Android Versión Mi UI.

Uzair
fuente
2
Esto definitivamente resolvió el problema para mí. Leí la documentación sobre el widget Spinner ... es absolutamente difícil entender la diferencia: setSelection (int position, boolean animate) -> Salta directamente a un elemento específico en los datos del adaptador. setSelection (int position) -> Establece el elemento actualmente seleccionado.
Matt
5

Obtuve 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
}
usuario6656805
fuente
3

He encontrado una solución mucho más elegante para esto. Implica contar cuántas veces se ha invocado el ArrayAdapter (en su caso "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 su caso, lo tiene llamado solo una vez, entonces:

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

Mi pequeña contribución es una variación de algunos de los anteriores que me ha quedado bien varias veces.

Declare una variable entera como 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, compruebe si el nuevo valor de la ruleta es igual al valor que asignó antes de ejecutar cualquier código adicional.

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

David Walton
fuente
1

Después de haber tenido el mismo problema, llegué a estas soluciones usando etiquetas. La idea detrás de esto es simple: cada vez que la rueda giratoria se cambie programáticamente, 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 la ruleta se cambió programáticamente.

A continuación se muestra mi nueva clase "proxy spinner":

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 etiqueta en su Valuesdirectorio. Llamé a 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 reemplace

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

en tu código con

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

Y haga que su controlador 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 ruleta. Tenga en cuenta que esta función tiene un efecto secundario: establecerá la etiqueta, por lo que siempre devolverá una segunda llamada en la misma llamada de escucha false.

Este contenedor también resolverá el problema con la llamada del oyente durante la creación del diseño.

Diviértete, Jens.

Jens
fuente
1

Como nada funcionó para mí, y tengo más de 1 spinner en mi opinión (y en mi humilde opinión, tener un mapa de 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) {
        }
    });
SagiLow
fuente
1

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

Extiendo AppCompatSpinnery 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 envíen 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 Fragmentpor ejemplo:

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

donde setSelection()es un método en la vista adjunta que se ve así, y que se llama tanto desde los eventos de selección del usuario a través del programa Observablecomo también en otros lugares, 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
}
Clyde
fuente
0

Intentaría 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 setAdapter reemplazado para omitir el evento.

Pentium10
fuente
0

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

Subclase android.widget.Spinnere hice pequeñas adiciones. Las partes relevantes están a continuación. 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;
}
RobinBobin
fuente
0

Esta tampoco es una solución elegante. De hecho, es más bien Rube-Goldberg, pero parece funcionar. Me aseguro de que la ruleta se haya utilizado al menos una vez al extender el adaptador de matriz y anular su getDropDownView. En el nuevo método getDropDownView, tengo un indicador booleano configurado 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);

adaptador de matriz reemplazado:

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;
}
Steven Smith
fuente
0

si necesita recrear la actividad sobre la marcha, por ejemplo: cambiar temas, un indicador / contador simple no funcionará

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

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

dev-gaek
fuente
0

Lo he hecho de la manera más simple:

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

Hiren Patel
fuente
Es una solución interesante. Podría usar más explicaciones. Básicamente es ignorar intencionalmente el primer evento onItemSelected. Pueden funcionar bien en algunos casos, pero no en otros, como cuando las opciones de accesibilidad están habilitadas (vea la explicación de 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
fuente
0

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 valor predeterminado setSelection(...)para el comportamiento predeterminado o use setSelectionWithoutInformListener(...)para seleccionar un elemento en el control giratorio sin activar la devolución de llamada OnItemSelectedListener.

MatPag
fuente
0

Necesito usar mSpinneren ViewHolder, por lo que el indicador 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
            }
        });
Francis Bacon
fuente
0

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) {
      }
  });
Ray Lionfang
fuente
0

Mi solución usa onTouchListenerpero no restringe su uso. Crea un contenedor para, onTouchListenersi es necesario, donde se configura 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
fuente
0

Podría estar respondiendo demasiado tarde en la publicación, sin embargo, logré lograr esto usando la biblioteca de enlace de datos de Android Android Databinding . 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 selecciona la misma posición una y otra vez, el evento no se activa.

Archivo xml de diseño

    <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 el enlace de datos personalizado aquí Android Custom Setter

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

N.Moudgil
fuente
-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
fuente
2
1) Formatee correctamente su código. 2) También agradeceríamos una explicación de lo que está haciendo su código. No todos los fragmentos de código se entienden de inmediato al leer el código.
Mike Koch