Android Spinner: evite las llamadas onItemSelected durante la inicialización

156

Creé una aplicación de Android con ay Spinnera TextView. Quiero mostrar el elemento seleccionado de la lista desplegable de Spinner en TextView. Implementé el Spinner en el onCreatemétodo, por lo que cuando ejecuto el programa, muestra un valor en TextView(antes de seleccionar un elemento de la lista desplegable).

Quiero mostrar el valor en TextView solo después de seleccionar un elemento de la lista desplegable. ¿Cómo hago esto?

Aquí está mi código:

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;

public class GPACal01Activity extends Activity implements OnItemSelectedListener {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Spinner spinner = (Spinner) findViewById(R.id.noOfSubjects);

        // Create an ArrayAdapter using the string array and a default spinner layout
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,R.array.noofsubjects_array, android.R.layout.simple_spinner_item);
        // Specify the layout to use when the list of choices appears
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        // Apply the adapter to the spinner
        spinner.setAdapter(adapter);
        spinner.setOnItemSelectedListener(this);
    }

    public void onItemSelected(AdapterView<?> parent, View arg1, int pos,long id) {
        TextView textView = (TextView) findViewById(R.id.textView1);
        String str = (String) parent.getItemAtPosition(pos);
        textView.setText(str);
    }

    public void onNothingSelected(AdapterView<?> arg0) {
        // TODO Auto-generated method stub

    }
}
Conceder
fuente
los hilanderos siempre tienen un valor seleccionado por defecto, si desea tener un hilandero sin un valor seleccionado por defecto, debe crear su hilandero personalizado, o crear un hilandero personalizado con una entrada vacía, y en el método getView (), cambie la visibilidad de el diseño en bruto para GONE
Houcine
44
¿Cómo es que un error tan molesto todavía existe a fines de 2018?
usuario-123

Respuestas:

174
spinner.setOnItemSelectedListener(this); // Will call onItemSelected() Listener.

Así que la primera vez maneje esto con cualquier valor entero

Ejemplo: tomar inicialmente int check = 0;

public void onItemSelected(AdapterView<?> parent, View arg1, int pos,long id) {
   if(++check > 1) {
      TextView textView = (TextView) findViewById(R.id.textView1);
      String str = (String) parent.getItemAtPosition(pos);
      textView.setText(str);
   }
}

Puede hacerlo con un valor booleano y también verificando las posiciones actuales y anteriores. Mira aquí

Abhi
fuente
2
Hombre impresionante ... cuando me enfrenté a este problema por primera vez, traté de implementar una ruleta personalizada ... pero no funcionó ... pero su solución funcionó de maravilla ... Gracias.
Sash_KP
1
¿Dónde declaras el cheque? ¿Fuera de getView ()? Dentro de la vista ¿Dónde? Probé tu solución pero no funciona para mí.
user3718908
9
Es un parche, no una solución, supongo.
saksham
1
Estoy enfrentando el mismo problema. ¿Es algún tipo de error? Además, si selecciono el mismo elemento repetidamente, el oyente no funciona después de la primera vez. Funciona solo si el elemento de selección cambia. Cualquier comentario, ayuda?
amitava el
1
Intenté esta solución con mi mismo problema, pero este solo funciona uno, por ejemplo: cuando tengo 2 elementos en el menú desplegable, funciona cuando selecciono cualquier elemento de spinner la primera vez, cuando intento la segunda vez no funciona correctamente
Waseem
116

Simplemente ponga esta línea antes de configurar OnItemSelectedListener

spinner.setSelection(0,false)
Dayanand Waghmare
fuente
77
Sería una mejor respuesta si escribiera cómo esto ayuda y por qué.
user3533716
1
Ayuda, pero ¿cómo?
AEMLoviji
14
Esto funciona porque primero configura la selección y luego agrega un oyente, pero no se llamará al oyente porque ya eligió esta selección antes. Solo las nuevas selecciones llamarán al oyente.
Desarrollador de Android el
44
Esto funciona porque setSelection(int, boolean)llama setSelectionInt()internamente, y debe configurar el oyente después (en lugar de antes) de llamar a esto. Cuidado con que setSelection(int)no funcionará, porque llama setNextSelectedPositionInt()internamente, y esto es lo que me llevó aquí.
Hai Zhang
3
Esto no funciona si se declara durante o antes de onCreateView (). Se llamará a onItemSelected.
Arash
62

A partir del nivel 3 de API, puede usar onUserInteraction () en una actividad con un valor booleano para determinar si el usuario está interactuando con el dispositivo.

http://developer.android.com/reference/android/app/Activity.html#onUserInteraction ()

@Override
public void onUserInteraction() {
     super.onUserInteraction();
     userIsInteracting = true;
}

Como campo en la Actividad tengo:

 private boolean userIsInteracting;

Finalmente, mi ruleta:

      mSpinnerView.setOnItemSelectedListener(new OnItemSelectedListener() {

           @Override
           public void onItemSelected(AdapterView<?> arg0, View view, int position, long arg3) {
                spinnerAdapter.setmPreviousSelectedIndex(position);
                if (userIsInteracting) {
                     updateGUI();
                }
           }

           @Override
           public void onNothingSelected(AdapterView<?> arg0) {

           }
      });

A medida que entra y pasa por la actividad, el valor booleano se restablece a falso. Funciona de maravilla.

Bill Mote
fuente
3
Buen Bill ... Creo que esta es una mejor solución que la aceptada como respuesta
Ritesh Gune
donde obtienes setmPreviousSelectedIndex?!?!
TheQ
¿Error de tipografía? :) setPreviousSelectedIndex ()
Bill Mote
44
Esto no funcionará en el caso de fragmentos de nido ya que onUserInteraction es el método de actividad. ¿Alguna otra solución?
Chitrang
1
@ErikB nvm, lo descubrí. Establecerlo falso dentro del oyente funciona bien.
Sikander
20

Esto funciono para mi

La inicialización de Spinner en Android es problemática, a veces el problema anterior se resolvió mediante este patrón.

Spinner.setAdapter();
Spinner.setSelected(false);  // must
Spinner.setSelection(0,true);  //must
Spinner.setonItemSelectedListener(this);

El adaptador de configuración debe ser la primera parte y onItemSelectedListener (esto) será el último al inicializar un spinner. Por el patrón de arriba, mi OnItemSelected () no se llama durante la inicialización de la ruleta

saksham
fuente
11

jaja ... tengo la misma pregunta. Cuando initViews () simplemente hace esto: la secuencia es la clave, el oyente es el último. Buena suerte !

spinner.setAdapter(adapter);
spinner.setSelection(position);
spinner.setOnItemSelectedListener(listener);
Boca del árbol
fuente
Bueno uno! Nada funcionó para mí, sea lo que sea que haya aplicado antes, pero este me ha funcionado de maravilla. Gracias @TreeSouth
Deepika Lalra
11
Para mí trabajó spinner.setSelection (posición, falso) utilizado de la misma manera. Con el método setSelection (posición) se llamó al oyente durante la inicialización.
Mario Kutlev
2
@HugoGresse Intente llamar a spinner.setSelection (0, falso); . La cosa es que ahora ignorará la selección de esta posición, porque ya está seleccionada
desarrollador de Android
8

Mi solución:

protected boolean inhibit_spinner = true;


@Override
        public void onItemSelected(AdapterView<?> arg0, View arg1,
                int pos, long arg3) {

            if (inhibit_spinner) {
                inhibit_spinner = false;
            }else {

            if (getDataTask != null) getDataTask.cancel(true);
            updateData();
            }

        }
codareee
fuente
7

Para evitar llamar a spinner.setOnItemSelectedListener () durante la inicialización

spinner.setSelection(Adapter.NO_SELECTION, true); //Add this line before setting listener
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {

    }
});
Ketan Ramani
fuente
6

Puedes hacer esto de esta manera:

AdapterView.OnItemSelectedListener listener = new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
            //set the text of TextView
        }

        @Override
        public void onNothingSelected(AdapterView<?> adapterView) {

        }
    });

yourSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
            yourSpinner.setOnItemSelectedListener(listener);
        }

        @Override
        public void onNothingSelected(AdapterView<?> adapterView) {

        }
    });

Al principio creo un oyente y lo atribuyo a una devolución de llamada variable; luego creo un segundo oyente anónimo y cuando se llama por primera vez, esto cambia el oyente =]

charlestón
fuente
3

El indicador de interacción del usuario puede establecerse en verdadero en el método onTouch y restablecerse onItemSelected()una vez que se haya manejado el cambio de selección. Prefiero esta solución porque el indicador de interacción del usuario se maneja exclusivamente para la ruleta, y no para otras vistas en la actividad que puedan afectar el comportamiento deseado.

En codigo:

Crea tu oyente para la ruleta:

public class SpinnerInteractionListener implements AdapterView.OnItemSelectedListener, View.OnTouchListener {

    boolean userSelect = false;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        userSelect = true;
        return false;
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
        if (userSelect) { 
            userSelect = false;
            // Your selection handling code here
        }
    }

}

Agregue el oyente a la ruleta como an OnItemSelectedListenery an OnTouchListener:

SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);
Ranjith Kumar
fuente
2

Una solución simple similar que permite múltiples hiladores es colocar el AdapterView en una colección, en la superclase de Actividades, en la primera ejecución de onItemSelected (...) Luego verifique si el AdaptadorView está en la colección antes de ejecutarlo. Esto habilita un conjunto de métodos en la superclase y admite múltiples AdapterViews y, por lo tanto, múltiples hiladores.

Superclase ...

private Collection<AdapterView> AdapterViewCollection = new ArrayList<AdapterView>();

   protected boolean firstTimeThrough(AdapterView parent) {
    boolean firstTimeThrough = ! AdapterViewCollection.contains(parent);
    if (firstTimeThrough) {
       AdapterViewCollection.add(parent);
     }
    return firstTimeThrough;
   }

Subclase ...

public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
      if (! firstTimeThrough(parent)) {
        String value = safeString(parent.getItemAtPosition(pos).toString());
        String extraMessage = EXTRA_MESSAGE;
        Intent sharedPreferencesDisplayIntent = new         Intent(SharedPreferencesSelectionActivity.this,SharedPreferencesDisplayActivity.class);
    sharedPreferencesDisplayIntent.putExtra(extraMessage,value);
    startActivity(sharedPreferencesDisplayIntent);
  }
  // don't execute the above code if its the first time through
  // do to onItemSelected being called during view initialization.

}

Jonathan Cole
fuente
2

crear un campo booleano

private boolean inispinner;

dentro de crear la actividad

    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            if (!inispinner) {
                inispinner = true;
                return;
            }
            //do your work here
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {

        }
    });
Afjalur Rahman Rana
fuente
2

Prueba esto

spinner.postDelayed(new Runnable() {
        @Override
        public void run() {
            addListeners();
        }
    }, 1000);.o
Ubirajara Erthal
fuente
1

Puede lograrlo mediante setOnTouchListener primero y luego setOnItemSelectedListener en onTouch

@Override
public boolean onTouch(final View view, final MotionEvent event) {
 view.setOnItemSelectedListener(this)
 return false;
}
vviieett
fuente
Me encanta esto. Aunque crea un nuevo oyente cada vez que un usuario toca la vista. Así que prefiero almacenar en caché el primer oyente creado y reutilizarlo.
Vlad
1

Esto funcionó para mí:

    spinner.setSelection(0, false);
    new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                spinner.setOnItemSelectedListener(listener);
            }, 500);
Khushal
fuente
1

Basado en la respuesta de Abhi, hice este simple oyente

class SpinnerListener constructor(private val onItemSelected: (position: Int) -> Unit) : AdapterView.OnItemSelectedListener {

    private var selectionCount = 0

    override fun onNothingSelected(parent: AdapterView<*>?) {
        //no op
    }

    override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        if (selectionCount++ > 1) {
           onItemSelected(position)
        }
    }
}
AdrianoCelentano
fuente
0

Tuve el mismo problema y esto funciona para mí:

Tengo 2 hilanderos y los actualizo durante init y durante las interacciones con otros controles o después de obtener datos del servidor.

Aquí está mi plantilla:

public class MyClass extends <Activity/Fragment/Whatever> implements Spinner.OnItemSelectedListener {

    private void removeSpinnersListeners() {
        spn1.setOnItemSelectedListener(null);
        spn2.setOnItemSelectedListener(null);
    }

    private void setSpinnersListeners() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                spn1.setOnItemSelectedListener(MyClass.this);
                spn2.setOnItemSelectedListener(MyClass.this);
            }
        }, 1);
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        // Your code here
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {

    }
}

Cuando la clase se inicia, use setSpinnersListeners () en lugar de configurar directamente el oyente.

El Runnable evitará que la ruleta se dispare en ItemSelected justo después de que establezca sus valores.

Si necesita actualizar la ruleta (después de una llamada al servidor, etc.) use removeSpinnersListeners () justo antes de sus líneas de actualización, y setSpinnersListeners () justo después de las líneas de actualización. Esto evitará que onItemSelected se active después de la actualización.

RoyBS
fuente
0

Código

spinner.setOnTouchListener(new View.OnTouchListener() { 
@Override public boolean onTouch(View view, MotionEvent motionEvent) { isSpinnerTouch=true; return false; }});

holder.spinner_from.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int slot_position, long l) {
                if(isSpinnerTouch)
                {
                    Log.d("spinner_from", "spinner_from");
                    spinnerItemClickListener.onSpinnerItemClickListener(position, slot_position, AppConstant.FROM_SLOT_ONCLICK_CODE);
                }
                else {

                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });
Messi
fuente
0

Para mí, la solución de Abhi funciona muy bien hasta el nivel 27 de Api.

Pero parece que desde el nivel 28 de Api en adelante, onItemSelected () no se llama cuando se configura el oyente, lo que significa que onItemSelected () nunca se llama.

Por lo tanto, agregué una breve declaración if para verificar el nivel de API:

public void onItemSelected(AdapterView<?> parent, View arg1, int pos,long id) {

            if(Build.VERSION.SDK_INT >= 28){ //onItemSelected() doesn't seem to be called when listener is set on Api 28+
                check = 1;
            }

            if(++check > 1) {
                //Do your action here
            }
        }

Creo que es bastante extraño y no estoy seguro si otros también tienen este problema, pero en mi caso funcionó bien.

Jannik B
fuente
0

Coloqué un TextView encima del Spinner, del mismo tamaño y fondo que el Spinner, para tener más control sobre cómo se veía antes de que el usuario haga clic en él. Con TextView allí, también podría usar TextView para marcar cuando el usuario ha comenzado a interactuar.

Mi código de Kotlin se parece a esto:

private var mySpinnerHasBeenTapped = false

private fun initializeMySpinner() {

    my_hint_text_view.setOnClickListener {
        mySpinnerHasBeenTapped = true //turn flag to true
        my_spinner.performClick() //call spinner click
    }

    //Basic spinner setup stuff
    val myList = listOf("Leonardo", "Michelangelo", "Rafael", "Donatello")
    val dataAdapter: ArrayAdapter<String> = ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item, myList)
    my_spinner.adapter = dataAdapter

    my_spinner.onItemSelectedListener = object : OnItemSelectedListener {

        override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {

            if (mySpinnerHasBeenTapped) { //code below will only run after the user has clicked
                my_hint_text_view.visibility = View.GONE //once an item has been selected, hide the textView
                //Perform action here
            }
        }

        override fun onNothingSelected(parent: AdapterView<*>?) {
            //Do nothing
        }
    }
}

El archivo de diseño se ve más o menos así, con la parte importante de que Spinner y TextView comparten el mismo ancho, alto y márgenes:

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <Spinner
                android:id="@+id/my_spinner"
                android:layout_width="match_parent"
                android:layout_height="35dp"
                android:layout_marginStart="10dp"
                android:layout_marginEnd="10dp"
                android:background="@drawable/bg_for_spinners"

                android:paddingStart="8dp"
                android:paddingEnd="30dp"
                android:singleLine="true" />

            <TextView
                android:id="@+id/my_hint_text_view"
                android:layout_width="match_parent"
                android:layout_height="35dp"                
                android:layout_marginStart="10dp"
                android:layout_marginEnd="10dp"
                android:background="@drawable/bg_for_spinners"

                android:paddingStart="8dp"
                android:paddingEnd="30dp"
                android:singleLine="true"
                android:gravity="center_vertical"
                android:text="*Select A Turtle"
                android:textColor="@color/green_ooze"
                android:textSize="16sp" />

        </FrameLayout>

Estoy seguro de que las otras soluciones funcionan donde ignora la primera llamada onItemSelected, pero realmente no me gusta la idea de suponer que siempre se llamará.

iOS_Mouse
fuente