Pensei em maneiras menos elegantes de resolver isso, mas sei que devo estar perdendo alguma coisa.
Meu onItemSelected
acionamento é 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.
}
}
}
android
spinner
android-spinner
FauxReal
fonte
fonte
Spinner
vazio e por dentroonItemSelected
você pode detectar se a String não está vaziastartActivity
!Respostas:
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.
fonte
onResume()
eonPostResume()
, portanto, todos os ganchos normais foram concluídos quando o layout aconteceu.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:
fonte
Referindo-se à resposta de Dan Dyer, tente registrar o
OnSelectListener
em umpost(Runnable)
método:Ao fazer isso por mim, o comportamento desejado finalmente ocorreu.
Nesse caso, também significa que o ouvinte é acionado apenas em um item alterado.
fonte
onCreate()
,onResume()
etc. Nesse caso, é um truque fantástico, sem risco de condição de corrida. Eu normalmente uso esse truqueonCreate()
logo após o código do layout.Criei um pequeno método utilitário para alterar a
Spinner
seleção sem notificar o usuário: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.
fonte
setSpinnerSelectionWithoutCallingListener
duas vezes rapidamente, para que a segunda chamada seja feita enquanto a primeira já tiver definido o ouvintenull
, seu botão giratório ficará preso aonull
ouvinte para sempre. Proponho a seguinte correção: addif (listener == null) return;
afterspinner.setSelection(selection)
.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.
fonte
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:
Confie em mim quando digo isso, esta é de longe a solução mais confiável. Um hack, mas funciona!
fonte
Eu estava em situação semelhante e tenho uma solução simples funcionando para mim.
Parece métodos
setSelection(int position)
esetSelected(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 oonItemSelected
ouvinte.fonte
setSelection(int position, boolean animate);
onItemSelected
em API23Apenas 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:
Antes de definir o setOnItemSelectedListener do seu girador, defina um onTouchListener:
fonte
fonte
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.
Use-o no seu XML assim:
Tudo o que você precisa fazer é recuperar a instância do SaneSpinner após a inflação e selecionar o conjunto de chamadas assim:
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.
fonte
Não há eventos indesejados da fase de layout, se você adiar a adição do ouvinte até que o layout seja concluído:
fonte
ViewTreeObserver.OnGlobalLayoutListener
versões abaixo de J chamandoViewTreeObserver.removeGlobalOnLayoutListener
, que está obsoleto e tem um nome semelhante ao método usado por esta resposta.Isso acontecerá se você estiver fazendo a seleção no código como;
Em vez de usar a instrução acima
Editar: este método não funciona para o Mi Android Version Mi UI.
fonte
Eu recebi uma resposta muito simples, 100% certa de que funciona:
fonte
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:
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:
fonte
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.
fonte
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":
Você também precisará de um arquivo XML com a configuração de tag em seu
Values
diretório. Eu nomeei meu arquivospinner_tag.xml
, mas isso é com você. Se parece com isso:Agora substitua
no seu código com
E faça com que seu manipulador pareça com isso:
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.
fonte
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:
fonte
Muitas respostas já, aqui está a minha.
Estendo
AppCompatSpinner
e adiciono um métodopgmSetSelection(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 umObservable
.Um exemplo de seu uso, chamado
onCreateView()
em umFragment
, por exemplo: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 viaObservable
como também em outros lugares programaticamente, portanto, a lógica para manipular seleções é comum aos dois métodos de seleção.fonte
Eu tentaria ligar
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.
fonte
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.Spinner
e fiz pequenas adições. As partes relevantes estão abaixo. Esta solução funcionou para mim.fonte
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 ():
adaptador de matriz substituído:
ouvinte modificado:
fonte
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
fonte
Eu fiz da maneira mais simples:
onCreate ();
Feito
fonte
fonte
Essa é a minha solução final e fácil de usar:
Use o padrão
setSelection(...)
para o comportamento padrão ousetSelectionWithoutInformListener(...)
selecione um item no girador sem acionar o retorno de chamada OnItemSelectedListener.fonte
Eu preciso usar
mSpinner
no ViewHolder, para que o sinalizadormOldPosition
seja definido na classe interna anônima.fonte
Eu armazenaria o índice inicial durante a criação do objeto onClickListener.
fonte
Minha solução usa,
onTouchListener
mas não restringe seu uso. Ele cria um wrapper para,onTouchListener
se necessário, onde a instalaçãoonItemSelectedListener
.fonte
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
app:position
é onde você está passando a posição a ser selecionada.Ligação personalizada
Você pode ler mais sobre a ligação de dados personalizada aqui Android Custom Setter
NOTA
Não se esqueça de ativar a ligação de dados no seu arquivo Gradle
Inclua seus arquivos de layout em
<layout>
tagsfonte
fonte