Como manter o onItemSelected de disparar em um Spinner recém-instanciado?

419

Pensei em maneiras menos elegantes de resolver isso, mas sei que devo estar perdendo alguma coisa.

Meu onItemSelectedacionamento é acionado imediatamente, sem qualquer interação com o usuário, e esse é um comportamento indesejado. Desejo que a interface do usuário aguarde até que o usuário selecione algo antes de fazer qualquer coisa.

Eu até tentei configurar o ouvinte no onResume(), esperando que isso ajudasse, mas não ajuda.

Como posso impedir que isso seja disparado antes que o usuário possa tocar no controle?

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
fonte
2
Você pode olhar para esta solução, é fácil e prática. stackoverflow.com/a/10102356/621951
Günay Gültekin 11/12
1
Uma solução simples seria deixar o primeiro item Spinnervazio e por dentro onItemSelectedvocê pode detectar se a String não está vazia startActivity!
Muhammad Babar
Esse padrão funciona corretamente stackoverflow.com/questions/13397933/…
saksham

Respostas:

78

Eu esperava que sua solução funcionasse - embora o evento de seleção não seja acionado se você configurar o adaptador antes de configurar o ouvinte.

Dito isto, um simples sinalizador booleano permitiria detectar o primeiro evento de seleção não autorizado e ignorá-lo.

CommonsWare
fonte
15
sim. Isso é o que eu quis dizer com solução deselegante. Parece que deve haver uma maneira melhor. Obrigado mesmo assim.
FauxReal
5
Esta discussão no ml de desenvolvedor tem mais informações sobre isso: groups.google.com/group/android-developers/browse_thread/thread/… - Infelizmente, nenhuma solução foi dada ...
BoD
25
O processo de disposição dos componentes aciona o ouvinte de seleção. Portanto, você teria que adicionar o ouvinte após a conclusão do layout. Não consegui encontrar um local adequado e simples para fazer isso, pois o layout parece acontecer em algum momento depois onResume()e onPostResume(), portanto, todos os ganchos normais foram concluídos quando o layout aconteceu.
Dan Dyer
28
Eu ficaria longe desse sinalizador booleano - como se o comportamento mudasse no futuro, isso poderia causar um bug. Uma solução mais à prova de balas seria manter uma variável com o "índice selecionado atual", inicializado no primeiro item selecionado. Em seguida, no evento de seleção - verifique se é igual à nova posição - retorne e não faça nada. Claro que atualize a variável na seleção.
Daniel.gindi 18/09/2013
2
Isso não funciona. Responder por @casanova works. Essa deve ser a resposta aceita.
Siddharth
379

O uso de Runnables está completamente incorreto.

Use setSelection(position, false);na seleção inicial antes desetOnItemSelectedListener(listener)

Dessa forma, você define sua seleção sem animação, o que faz com que o ouvinte selecionado no item seja chamado. Mas o ouvinte é nulo, então nada é executado. Em seguida, seu ouvinte é atribuído.

Então, siga esta sequência exata:

Spinner s = (Spinner)Util.findViewById(view, R.id.sound, R.id.spinner);
s.setAdapter(adapter);
s.setSelection(position, false);
s.setOnItemSelectedListener(listener);
Brad
fonte
48
+1 jóia escondida! Passar false como parâmetro "animar" não chama o retorno de chamada do ouvinte. Impressionante!
pkk
3
+1 Solução estranha, mas elegante :) Felizmente, eu já tinha que ligar para setSelection de qualquer maneira ...
Martin T.
35
O ouvinte ainda dispara quando o elemento da interface do usuário do Spinner é montado, portanto dispara independentemente do que não impede o comportamento indesejado descrito pelo OP. Isso funciona muito bem se não for declarado durante ou antes do onCreateView (), mas não foi o que eles pediram.
Rudi Kershaw
6
Útil, mas resolve um problema diferente do OP apresentado. OP refere-se a um evento de seleção que (infelizmente) é acionado automaticamente quando a exibição aparece pela primeira vez, mesmo que o programador não tenha configurado setSelection .
Página Inicial>
2
O parâmetro do valor "false" no método setSelection (..) foi a solução para mim. ty!
23415 Dani
195

Referindo-se à resposta de Dan Dyer, tente registrar o OnSelectListenerem um post(Runnable)método:

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

Ao fazer isso por mim, o comportamento desejado finalmente ocorreu.

Nesse caso, também significa que o ouvinte é acionado apenas em um item alterado.

casaflowa
fonte
1
Eu recebo um erro dizendo: O método setOnItemSelectedListener (AdapterView.OnItemSelectedListener) no tipo AdapterView <SpinnerAdapter> não é aplicável aos argumentos (novo Runnable () {}), por que isso?
11266 Jakob
Isso não é essencialmente configurar uma condição de corrida entre o Runnable e o Thread da interface do usuário?
Kenny_k
6
@theFunkyEngineer - Esse código deve ser executado a partir de um dos métodos principais de threads, por exemplo onCreate(), onResume()etc. Nesse caso, é um truque fantástico, sem risco de condição de corrida. Eu normalmente uso esse truque onCreate()logo após o código do layout.
Richard Le Mesurier
1
Esta é uma ótima solução e definitivamente não é um hack! Esse tipo de funcionalidade é como as coisas são feitas profundamente na estrutura. É uma pena que o Spinner não faça isso internamente. No entanto, essa é a maneira mais limpa de garantir a execução de algum código após a criação da Atividade. Isso funciona porque o ouvinte ainda não está definido no Spinner quando a Atividade tenta notificá-los.
jophde
1
Esta é uma solução aceitável . não é um tiro cego. outra solução é mais propensa a problemas de mudança de comportamento no futuro.
Kuldeep Singh Dhaka
50

Criei um pequeno método utilitário para alterar a Spinnerseleção sem notificar o usuário:

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

Desativa o ouvinte, altera a seleção e reativa o ouvinte depois disso.

O truque é que as chamadas são assíncronas ao segmento da interface do usuário, portanto, você deve fazer isso em postagens consecutivas de manipulador.

karooolek
fonte
Impressionante. Eu tinha vários giradores e tentei definir todos os seus ouvintes como nulos antes de definir seus valores, depois coloquei todos eles de volta ao que deveriam ser, mas por algum motivo isso não funcionou. tentei essa função e funcionou. Eu não sei por que o meu não funcionou, mas isso funciona, então eu não me importo: D
JStephen
4
Nota: se você ligar setSpinnerSelectionWithoutCallingListenerduas vezes rapidamente, para que a segunda chamada seja feita enquanto a primeira já tiver definido o ouvinte null, seu botão giratório ficará preso ao nullouvinte para sempre. Proponho a seguinte correção: add if (listener == null) return;after spinner.setSelection(selection).
Giraffe Violet
34

Infelizmente, parece que as duas soluções sugeridas com mais freqüência para esse problema, ou seja, contar ocorrências de retorno de chamada e postar um Runnable para definir o retorno de chamada posteriormente, podem falhar quando, por exemplo, as opções de acessibilidade estão ativadas. Aqui está uma classe auxiliar que resolve esses problemas. Explicações adicionais estão no bloco de comentários.

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
fonte
3
Essa deve ser a resposta mais votada. É simples, mas brilhante. Ele permite que você mantenha toda a sua implementação atual igual, exceto a linha em que você inicializa. Definitivamente simplificou projetos antigos com bastante facilidade. Além disso, matei dois coelhos com uma cajadada, implementando a interface OnTouchLisener para fechar o teclado quando o girador é aberto. Agora todos os meus giradores se comportam exatamente do jeito que eu quero.
user3829751
Bela resposta. Ele ainda é acionado para o 0º elemento quando eu adicionoAll () ao adaptador, mas meu 0º elemento é uma elipse para comportamento neutro (não faça nada).
jwehrle
31

Eu tive muitos problemas com o acionador giratório de quando não queria, e todas as respostas aqui não são confiáveis. Eles funcionam - mas apenas algumas vezes. Você acabará enfrentando cenários em que eles falharão e introduzirão erros no seu código.

O que funcionou para mim foi armazenar o último índice selecionado em uma variável e avaliá-lo no ouvinte. Se for o mesmo que o novo índice selecionado, não faça nada e retorne; caso contrário, continue com o ouvinte. Faça isso:

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

}

Confie em mim quando digo isso, esta é de longe a solução mais confiável. Um hack, mas funciona!

Chris
fonte
Isso funcionará mesmo se você estiver tentando alterar o valor? No meu caso, estou tentando definir o valor para algo como 3 quando na verdade é 0 sem acionar os ouvintes de alterações. Você está dizendo que int i retorna apenas um valor diferente se o usuário o estiver selecionando?
JStephen 11/02
Oi JStephen, não tenho 100% de certeza do que você quer dizer. Mas int i será a posição do botão giratório sempre que onItemSelected for acionado. O problema é que onItemSelected é acionado sempre que o botão giratório é carregado pela primeira vez, sem nenhuma interação real do usuário, levando a comportamentos indesejados nesse caso. int i será igual a 0 neste ponto inicial, pois este é o índice inicial padrão quando o botão giratório é carregado pela primeira vez. Portanto, minha solução verifica se um item diferente real está selecionado em vez de o item atualmente selecionado ser selecionado novamente ... Isso responde à sua pergunta?
21415 Chris
Oi Chris, Eu tenho uma página que extrai informações do banco de dados para o usuário editar. quando a página é aberta, preencho os giradores e defino suas posições com os valores que estavam no banco de dados. Portanto, se eu definir sua posição como 3, por exemplo, isso fará com que o onItemSelected seja acionado com i definido como 3, que é diferente da inicial. Eu estava pensando que você estava dizendo que só será definido se o usuário realmente o tiver alterado.
JStephen
4
E se o usuário selecionar a posição 0? Eles serão ignorados.
precisa saber é o seguinte
Não acho que a última posição seja uma boa ideia. Inicio spinners carregando a posição de SharedPreferences e usando setSelection. Muitas vezes, os valores em SharedPrefs não são iguais aos valores padrão quando os giradores são criados; portanto, o onItemSelected será acionado no início.
Arthez
26

Eu estava em situação semelhante e tenho uma solução simples funcionando para mim.

Parece métodos setSelection(int position)e setSelected(int position, boolean animate)tem implementação interna diferente.

Quando você usa o segundo método setSelected(int position, boolean animate)com sinalizador de animação falsa, obtém a seleção sem disparar o onItemSelectedouvinte.

Michal
fonte
A melhor abordagem é não se preocupar com as chamadas extras para onItemSelected, mas garantir que ela mostre a seleção correta. Portanto, chamar spinner.setSelection (selectedIndex) antes de adicionar o listener fez com que ele funcionasse consistentemente para mim.
andude
1
ali não é setSelected (int posição, booleano animado) método para girador
shift66
4
A chamada real que você precisa ésetSelection(int position, boolean animate);
Brad
+1 para você. Esta resolver um problema mais geral quando o código modificar mais vezes Spinner conteúdo e seleção mantendo onItemSelected apenas para interação do usuário
alrama
4
infelizmente bandeira falsa animação ainda chama onItemSelectedem API23
mcy
23

Apenas para obter dicas sobre como usar o onTouchListener para distinguir entre chamadas automáticas para o setOnItemSelectedListener (que fazem parte da inicialização da atividade etc.) versus chamadas acionadas pela interação real do usuário, fiz o seguinte depois de tentar outras sugestões aqui e descobriu que funcionava bem com o menor número de linhas de código.

Basta definir um campo booleano para sua atividade / fragmento, como:

private Boolean spinnerTouched = false;

Antes de definir o setOnItemSelectedListener do seu girador, defina um 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
fonte
1
Isso funciona muito bem, e desde o Android 6+, esse é o único método que funciona. MAS, você também precisa fazer o mesmo com setOnKeyListener () ou não funciona quando o usuário navega com o teclado.
Stéphane
Funciona muito bem, todas as outras soluções têm problemas com telefones diferentes.
Ziwei Zeng 06/06
Isso é simples e absolutamente perfeito! Nenhuma bobagem extra necessária, basta manter a lógica em mente. Estou feliz por ter percorrido todo o caminho até aqui!
user3833732
Em vez de setOnKeyListener (), você pode subclass girador e definir sinalizador spinnerTouched = true no método preformClick () substituído, que é chamado nos dois casos (toque / tecla). O resto é o mesmo.
Todo
Só queria mencionar que isso parece resolver o mesmo bug com o DropDownPreferences que eu postei recentemente aqui: stackoverflow.com/questions/61867118/… Eu não posso acreditar nisso tbh: D
Daniel Wilson
13
spinner.setSelection(Adapter.NO_SELECTION, false);
j2emanue
fonte
3
O código pode falar por si, mas um pouco explicação vai um longo caminho :)
nhaarman
8

Depois de puxar meu cabelo por um longo tempo, criei minha própria aula de Spinner. Adicionei um método que desconecta e conecta o ouvinte adequadamente.

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

Use-o no seu XML assim:

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

Tudo o que você precisa fazer é recuperar a instância do SaneSpinner após a inflação e selecionar o conjunto de chamadas assim:

mMySaneSpinner.setSelection(1, true, true);

Com isso, nenhum evento é acionado e a interação do usuário não é interrompida. Isso reduziu muito a complexidade do meu código. Isso deve ser incluído no estoque do Android, pois é realmente uma PITA.

fusion44
fonte
1
Isso não funciona para mim, ainda aciona onItemSelected.
Arthez
Arthez, verifique novamente se você está realmente passando para o terceiro argumento. Se sim, algo mais está errado aqui. Se possível, publique seu código.
fusion44
8

Não há eventos indesejados da fase de layout, se você adiar a adição do ouvinte até que o layout seja concluído:

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
fonte
Isso funciona e, na IMO, é a solução mais limpa para a questão específica do OP. Quero observar que você pode remover as ViewTreeObserver.OnGlobalLayoutListenerversões abaixo de J chamando ViewTreeObserver.removeGlobalOnLayoutListener, que está obsoleto e tem um nome semelhante ao método usado por esta resposta.
Jack Meister
7

Isso acontecerá se você estiver fazendo a seleção no código como;

   mSpinner.setSelection(0);

Em vez de usar a instrução acima

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

Editar: este método não funciona para o Mi Android Version Mi UI.

Uzair
fonte
2
Definitivamente, isso resolveu o problema para mim. Eu li a documentação sobre o widget Spinner. É absolutamente complicado entender a diferença: setSelection (int position, boolean animate) -> Salte diretamente para um item específico nos dados do adaptador. setSelection (int position) -> Define o item atualmente selecionado.
Matt
5

Eu recebi uma resposta muito simples, 100% certa 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
}
user6656805
fonte
3

Eu encontrei uma solução muito mais elegante para isso. Envolve contar quantas vezes o ArrayAdapter (no seu caso "adaptador") foi chamado. Digamos que você tenha 1 botão giratório e chame:

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 um contador int após o método onCreate e, em seguida, dentro do método onItemSelected (), coloque uma condição "if" para verificar quantas vezes o atapter foi chamado. No seu caso, você o chamou apenas uma vez:

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

Minha pequena contribuição é uma variação de algumas das opções acima, que me agradou algumas vezes.

Declare uma variável inteira como um valor padrão (ou o último valor usado salvo nas preferências). Use spinner.setSelection (myDefault) para definir esse valor antes do registro do ouvinte. No onItemSelected, verifique se o novo valor do girador é igual ao valor que você atribuiu antes de executar qualquer código adicional.

Isso tem a vantagem adicional de não executar o código se o usuário selecionar o mesmo valor novamente.

David Walton
fonte
1

Depois de ter tido o mesmo problema, cheguei a essas soluções usando tags. A idéia por trás disso é simples: sempre que o botão giratório for alterado programaticamente, verifique se a etiqueta reflete a posição selecionada. No ouvinte, você verifica se a posição selecionada é igual à marca. Nesse caso, a seleção do girador foi alterada programaticamente.

Abaixo está minha nova classe "proxy giratório":

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

Você também precisará de um arquivo XML com a configuração de tag em seu Valuesdiretório. Eu nomeei meu arquivo spinner_tag.xml, mas isso é com você. Se parece com isso:

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

Agora substitua

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

no seu código com

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

E faça com que seu manipulador pareça com isso:

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

A função isUiTriggered()retornará true se e somente se o girador tiver sido alterado pelo usuário. Observe que essa função tem um efeito colateral - ela definirá o tag, portanto uma segunda chamada na mesma chamada de ouvinte sempre retornará false.

Esse wrapper também tratará do problema com o ouvinte sendo chamado durante a criação do layout.

Divirta-se, Jens.

Jens
fonte
1

Como nada funcionou para mim, e eu tenho mais de 1 botão giratório na minha opinião (e o IMHO segurando um mapa bool é um exagero) eu uso a tag para contar os cliques:

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

Muitas respostas já, aqui está a minha.

Estendo AppCompatSpinnere adiciono um método pgmSetSelection(int pos)que permite a configuração de seleção programática sem acionar um retorno de chamada de seleção. Eu codifiquei isso com o RxJava para que os eventos de seleção sejam entregues por meio de um 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);
    }
}

Um exemplo de seu uso, chamado onCreateView()em um Fragment, por exemplo:

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

em que setSelection()é um método na visualização anexa que se parece com isso e que é chamado tanto a partir de eventos de seleção do usuário via Observablecomo também em outros lugares programaticamente, portanto, a lógica para manipular seleções é comum aos dois métodos de seleção.

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

Eu tentaria ligar

spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());

depois de chamar setAdapter (). Tente também ligar antes do adaptador.

Você sempre tem a solução para incluir a subclasse, na qual é possível agrupar um sinalizador booleano no método setAdapter substituído para ignorar o evento.

Pentium10
fonte
0

A solução com um sinalizador booleano ou um contador não me ajudou, porque durante a mudança de orientação, onItemSelected () chama "sobrecarregar" o sinalizador ou o contador.

Subclassifiquei android.widget.Spinnere fiz pequenas adições. As partes relevantes estão abaixo. Esta solução funcionou para mim.

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

Esta também não é uma solução elegante. De fato, é bastante Rube-Goldberg, mas parece funcionar. Garanto que o girador tenha sido usado pelo menos uma vez, estendendo o adaptador de matriz e substituindo seu getDropDownView. No novo método getDropDownView, tenho um sinalizador booleano definido para mostrar que o menu suspenso foi usado pelo menos uma vez. Ignoro as chamadas para o ouvinte até que o sinalizador seja definido.

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 substituído:

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

ouvinte modificado:

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

se você precisar recriar atividades em tempo real, por exemplo: alterar temas, uma simples bandeira / contador não funcionará

use a função onUserInteraction () para detectar a atividade do usuário,

referência: https://stackoverflow.com/a/25070696/4772917

dev-gaek
fonte
0

Eu fiz da maneira mais simples:

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

        }
    });

Feito

Hiren Patel
fonte
É uma solução interessante. Poderia usar mais explicações. Basicamente, é intencionalmente ignorar o primeiro evento onItemSelected. Eles podem funcionar bem em alguns casos, mas não em outros, como quando as opções de acessibilidade estão ativadas (consulte a explicação de Jorrit) .
Jk7 6/06
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
fonte
0

Essa é a minha solução final e 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 o padrão setSelection(...)para o comportamento padrão ou setSelectionWithoutInformListener(...)selecione um item no girador sem acionar o retorno de chamada OnItemSelectedListener.

MatPag
fonte
0

Eu preciso usar mSpinnerno ViewHolder, para que o sinalizador mOldPositionseja definido na classe 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
fonte
0

Eu armazenaria o índice inicial durante a criação do 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
fonte
0

Minha solução usa, onTouchListenermas não restringe seu uso. Ele cria um wrapper para, onTouchListenerse necessário, onde a instalação 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
fonte
0

Talvez eu esteja respondendo tarde demais na postagem, mas consegui isso usando a biblioteca de ligação de dados Android Android Databinding . Criei uma ligação personalizada para garantir que o ouvinte não seja chamado até que o item selecionado seja alterado, mesmo que o usuário esteja selecionando a mesma posição repetidamente o evento não seja acionado.

Arquivo xml de layout

    <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 é onde você está passando a posição a ser selecionada.

Ligação 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) {

        }
    });
}

Você pode ler mais sobre a ligação de dados personalizada aqui Android Custom Setter

NOTA

  1. Não se esqueça de ativar a ligação de dados no seu arquivo Gradle

       android {
     ....
     dataBinding {
     enabled = true
    }
    }
  2. Inclua seus arquivos de layout em <layout>tags

N.Moudgil
fonte
-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
fonte
2
1) Formate seu código corretamente. 2) Uma explicação sobre o que seu código está fazendo também seria apreciada. Nem todos os trechos de código são entendidos imediatamente ao ler o código.
Mike Koch