Android Spinner: Evite chamadas onItemSelected durante a inicialização


156

Eu criei um aplicativo Android com a Spinnere a TextView. Quero exibir o item selecionado na lista suspensa do Spinner no TextView. Eu implementei o Spinner no onCreatemétodo, portanto, quando estou executando o programa, ele mostra um valor no TextView(antes de selecionar um item na lista suspensa).

Quero mostrar o valor no TextView somente depois de selecionar um item na lista suspensa. Como eu faço isso?

Aqui está o meu 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

    }
}

spinners sempre têm um valor selecionado padrão; se você deseja ter um spinner sem valor selecionado, deve criar seu spinner personalizado ou criar um spinner personalizado com uma entrada vazia e, no método getView (), alterar a visibilidade de o layout bruto para GONE
— Houcine

4
Como é que um bug tão irritante ainda existe no final de 2018 ???
— usar o seguinte

Respostas:


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

Então, pela primeira vez, lide com isso com qualquer valor Inteiro

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

Você pode fazer isso com valor booleano e também verificando as posições atuais e anteriores. Veja aqui


2
Homem impressionante ... quando enfrentei esse problema, tentei implementar o spinner personalizado ... ainda não funcionou .. mas sua solução funcionou como um encanto .. Obrigado.
— Sash_KP

1
Onde você declara cheque? Fora do getView ()? Dentro do viewHolder? Onde? tentei sua solução, mas não funciona para mim.
— user3718908

9
é um patch, não uma solução, eu acho.
— saksham

1
Estou enfrentando o mesmo problema. É algum tipo de bug? Além disso, se eu selecionar o mesmo item repetidamente, o ouvinte não funcionará após a primeira vez. Funciona apenas se o item de seleção for alterado. Algum comentário, ajuda?
— Amitava 1/11

1
Eu tentei esta solução com o meu mesmo problema, mas este funciona apenas um, por exemplo: quando eu tenho 2 itens no menu suspenso, ele funciona quando seleciono qualquer item giratório na primeira vez, quando tento na segunda vez que não está funcionando corretamente
— Waseem

116

Basta colocar esta linha antes de definir OnItemSelectedListener

spinner.setSelection(0,false)

7
Seria uma resposta melhor se você escrever como isso ajuda e por quê.
— user3533716

1
Isso ajuda, mas quer como?
— AEMLoviji

14
Isso funciona porque você primeiro define a seleção e depois adiciona um ouvinte, mas o ouvinte não será chamado porque você já escolheu essa seleção antes. Somente novas seleções chamarão o ouvinte.
— desenvolvedor android

4
Isso funciona porque setSelection(int, boolean)chama setSelectionInt()internamente e você precisa definir o ouvinte após (em vez de antes) chamar isso. Cuidado que setSelection(int)não funcionará, porque chama setNextSelectedPositionInt()internamente, e foi isso que me levou até aqui.
— Hai Zhang

3
Isso não funciona se for declarado durante ou antes de onCreateView (). onItemSelected será chamado.
— Arash

62

A partir do nível 3 da API, você pode usar onUserInteraction () em uma Atividade com um valor booleano para determinar se o usuário está interagindo com o dispositivo.

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

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

Como um campo da Atividade, tenho:

 private boolean userIsInteracting;

Finalmente, meu spinner:

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

           }
      });

À medida que você entra e sai da atividade, o booleano é redefinido para false. Funciona como um encanto.


3
Boas Bill..I um penso que esta é uma solução melhor do que o aceito como resposta
— Ritesh Gune

onde você obtém setmPreviousSelectedIndex?!?!
— TheQ

Erro de digitação? :) setPreviousSelectedIndex ()
— Bill Mote

4
Isso não funcionará no caso de fragmentos de ninho, pois o método onUserInteraction é Activity. Alguma outra solução?
— Chitrang 16/09

1
@ErikB nvm, entendi. Configurá-lo como falso dentro do ouvinte funciona bem.
— Sikander

20

Isso funcionou para mim

A inicialização do Spinner no Android é problemática às vezes, o problema acima foi resolvido por esse padrão.

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

O adaptador de configuração deve ser a primeira parte e onItemSelectedListener (this) será o último ao inicializar um girador. Pelo padrão acima, meu OnItemSelected () não é chamado durante a inicialização do spinner


11

haha ... eu tenho a mesma pergunta. Quando initViews () apenas faz assim. A sequência é a chave, o ouvinte é o último. Boa sorte !

spinner.setAdapter(adapter);
spinner.setSelection(position);
spinner.setOnItemSelectedListener(listener);

Um bom! Nada funcionou para mim, seja o que for que tenha aplicado antes, mas este funcionou para mim como um encanto. Obrigado @TreeSouth
— Deepika Lalra

11
Para mim trabalhou spinner.setSelection (position, false) usado da mesma maneira. Com o método setSelection (position), o ouvinte foi chamado durante a inicialização.
— Mario Kutlev 12/10

2
@HugoGresse Tente chamar spinner.setSelection (0, false); . O problema é que agora ele ignorará a seleção dessa posição, porque já está selecionada
— desenvolvedor Android

8

Minha solução:

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

        }

7

Para evitar chamar spinner.setOnItemSelectedListener () durante a inicialização

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

    }
});

6

Você pode fazer isso desta maneira:

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

        }
    });

Inicialmente, crio um ouvinte e atribuo a um retorno de chamada variável; então eu crio um segundo ouvinte anônimo e quando isso é chamado pela primeira vez, isso altera o ouvinte =]


3

O sinalizador de interação do usuário pode ser definido como true no método onTouch e redefinido onItemSelected()após a alteração na seleção. Prefiro esta solução porque o sinalizador de interação do usuário é tratado exclusivamente para o girador, e não para outras visualizações da atividade que podem afetar o comportamento desejado.

Em código:

Crie seu ouvinte para o spinner:

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

}

Adicione o ouvinte ao girador como an OnItemSelectedListenere an OnTouchListener:

SpinnerInteractionListener listener = new SpinnerInteractionListener();
mSpinnerView.setOnTouchListener(listener);
mSpinnerView.setOnItemSelectedListener(listener);

2

Solução simples semelhante que permite vários rotadores é colocar o AdapterView em uma coleção - na superclasse de Atividades - na primeira execução de onItemSelected (...). Depois, verifique se o AdapterView está na coleção antes de executá-lo. Isso permite um conjunto de métodos na superclasse e suporta vários AdapterViews e, portanto, vários giradores.

Superclasse ...

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

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

Subclasse ...

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.

}


2

crie um campo booleano

private boolean inispinner;

dentro de criar a atividade

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

        }
    });

2

Tente isto

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

1

Você pode conseguir isso setOnTouchListener primeiro e depois setOnItemSelectedListener no onTouch

@Override
public boolean onTouch(final View view, final MotionEvent event) {
 view.setOnItemSelectedListener(this)
 return false;
}

Eu amo isto. Mesmo que ele crie um novo ouvinte toda vez que um usuário tocar na exibição. Então, eu prefiro armazenar em cache o primeiro ouvinte criado e reutilizá-lo.
— Vlad

1

Isso funcionou para mim:

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

1

Com base na resposta de Abhi, fiz este ouvinte simples

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

0

Teve o mesmo problema e isso funciona para mim:

Eu tenho 2 spinners e os atualizo durante o init e durante as interações com outros controles ou após obter dados do servidor.

Aqui está o meu modelo:

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

    }
}

Quando a classe estiver iniciando, use setSpinnersListeners () em vez de definir diretamente o ouvinte.

O Runnable impedirá que o girador seja acionado onItemSelected logo após você definir seus valores.

Se você precisar atualizar o botão giratório (após uma chamada do servidor etc.), use removeSpinnersListeners () imediatamente antes das linhas de atualização e setSpinnersListeners () logo após as linhas de atualização. Isso impedirá que o onItemSelected seja acionado após a atualização.


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

            }
        });

0

Para mim, a solução da Abhi funciona muito bem até o nível 27 da API.

Mas parece que a partir do Api nível 28 e acima, onItemSelected () não é chamado quando o listener é definido, o que significa que onItemSelected () nunca é chamado.

Portanto, adicionei uma declaração if curta para verificar o nível da 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
            }
        }

Acho isso muito estranho e não tenho certeza se os outros também têm esse problema, mas no meu caso funcionou bem.


0

Coloquei um TextView em cima do Spinner, do mesmo tamanho e plano de fundo que o Spinner, para que eu tivesse mais controle sobre a aparência antes de o usuário clicar nele. Com o TextView lá, eu também poderia usar o TextView para sinalizar quando o usuário começar a interagir.

Meu código Kotlin é mais ou menos assim:

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

O arquivo de layout se parece com isso, com a parte importante sendo que o Spinner e o TextView compartilham a mesma largura, altura e margens:

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

Tenho certeza de que as outras soluções funcionam onde você ignora a primeira chamada onItemSelected, mas realmente não gosto da ideia de assumir que ela sempre será chamada.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.