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

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

Abhi
fonte
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)
Dayanand Waghmare
fonte
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.

Bill Mote
fonte
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

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

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

    }
});
Ketan Ramani
fonte
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 =]

charleston
fonte
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);
Ranjith Kumar
fonte
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.

}

Jonathan Cole
fonte
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) {

        }
    });
Afjalur Rahman Rana
fonte
2

Tente isto

spinner.postDelayed(new Runnable() {
        @Override
        public void run() {
            addListeners();
        }
    }, 1000);.o
Ubirajara Erthal
fonte
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;
}
vviieett
fonte
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);
Khushal
fonte
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)
        }
    }
}
AdrianoCelentano
fonte
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.

RoyBS
fonte
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
fonte
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.

Jannik B
fonte
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.

iOS_Mouse
fonte