--- Nota para moderadores: Hoje (15 de julho), notei que alguém já enfrentou esse problema aqui . Mas não tenho certeza se é apropriado encerrar isso como duplicado, pois acho que forneci uma explicação muito melhor do problema. Não tenho certeza se devo editar a outra pergunta e colar esse conteúdo lá, mas não me sinto confortável em mudar muito a pergunta de outra pessoa. ---
Eu tenho algo estranho aqui.
Eu não acho que o problema depende de qual SDK você constrói. A versão do sistema operacional do dispositivo é o que importa.
Problema # 1: inconsistência por padrão
DatePickerDialog
foi alterado (?) no Jelly Bean e agora fornece apenas um botão Concluído . As versões anteriores incluíam um botão Cancelar e isso pode afetar a experiência do usuário (inconsistência, memória muscular de versões anteriores do Android).
Replicar: Crie um projeto básico. Coloque isso emonCreate
:
DatePickerDialog picker = new DatePickerDialog(
this,
new OnDateSetListener() {
@Override
public void onDateSet(DatePicker v, int y, int m, int d) {
Log.d("Picker", "Set!");
}
},
2012, 6, 15);
picker.show();
Esperado: Umbotão Cancelar para aparecer na caixa de diálogo.
Atual: Umbotão Cancelar não aparece.
Capturas de tela: 4.0.3 (OK) e 4.1.1 (possivelmente errado?).
Problema # 2: comportamento de descartar errado
A caixa de diálogo chama o ouvinte que ele realmente deve chamar e sempre chama o OnDateSetListener
ouvinte. O cancelamento ainda chama o método set e a configuração chama o método duas vezes.
Replicar: use o código nº 1, mas adicione o código abaixo (você verá isso resolve o nº 1, mas apenas visualmente / interface do usuário):
picker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d("Picker", "Cancel!");
}
});
Esperado:
- Pressionar a tecla VOLTAR ou clicar fora da caixa de diálogo não deve fazer nada .
- Pressionar "Cancelar" deve imprimir o Selecionador Cancelar! .
- Pressionar "Set" deve imprimir o Picker Set! .
Atual:
- Pressionar a tecla VOLTAR ou clicar fora da caixa de diálogo imprimirá o Picker Set! .
- Pressionar "Cancelar" imprime o Selecionador Cancelar! e depois Picker Set! .
- Pressionar "Set" imprime o Picker Set! e depois Picker Set! .
Linhas de log mostrando o comportamento:
07-15 12:00:13.415: D/Picker(21000): Set!
07-15 12:00:24.860: D/Picker(21000): Cancel!
07-15 12:00:24.876: D/Picker(21000): Set!
07-15 12:00:33.696: D/Picker(21000): Set!
07-15 12:00:33.719: D/Picker(21000): Set!
Outras notas e comentários
- Envolvê-lo
DatePickerFragment
não importa. Simplifiquei o problema para você, mas já testei.
Respostas:
Nota: Corrigido a partir do pirulito , fonte aqui . Classe automatizada para uso em clientes (compatível com todas as versões do Android) atualizada também.
TL; DR: 1-2-3 etapas fáceis para uma solução global:
OnDateSetListener
em sua atividade (ou mude a classe para atender às suas necessidades).Acione a caixa de diálogo com este código (neste exemplo, eu o uso dentro de a
Fragment
):E é tudo o que é preciso! O motivo pelo qual ainda mantenho minha resposta como "aceita" é porque ainda prefiro minha solução, uma vez que possui uma pegada muito pequena no código do cliente, aborda a questão fundamental (o ouvinte sendo chamado na classe framework), funciona bem nas alterações de configuração e direciona a lógica do código para a implementação padrão nas versões anteriores do Android, não afetadas por esse bug (consulte a fonte da classe).
Resposta original (mantida por razões históricas e didáticas):
Origem do bug
OK, parece que é realmente um bug e alguém já o preencheu. Edição 34833 .
Descobri que o problema é, possivelmente, em
DatePickerDialog.java
. Onde se lê:Eu acho que poderia ter sido:
Agora, se alguém puder me dizer como posso propor um relatório de patch / bug para o Android, ficarei feliz em fazê-lo. Enquanto isso, sugeri uma possível correção (simples) como uma versão anexada do
DatePickerDialog.java
na edição de lá.Conceito para evitar o bug
Defina o ouvinte como
null
no construtor e crie seu próprioBUTTON_POSITIVE
botão posteriormente . É isso aí, detalhes abaixo.O problema ocorre porque
DatePickerDialog.java
, como você pode ver na fonte, chama uma variável global (mCallBack
) que armazena o ouvinte que foi passado no construtor:Portanto, o truque é fornecer um
null
ouvinte para ser armazenado como ouvinte e, em seguida, role seu próprio conjunto de botões (abaixo está o código original de # 1, atualizado):Agora ele funcionará devido à possível correção que eu postei acima.
E como
DatePickerDialog.java
verifica anull
sempre que lêmCallback
( desde os dias da API 3 / 1.5, parece - não é possível verificar o Honeycomb, é claro), isso não acionará a exceção.Considerando que o Lollipop corrigiu o problema, não vou investigar: basta usar a implementação padrão (abordada na classe que forneci).No começo, eu tinha medo de não ligar
clearFocus()
, mas testei aqui e as linhas de log estavam limpas. Portanto, essa linha que propus pode até não ser necessária, mas não sei.Compatibilidade com os níveis anteriores da API (editado)
Como apontei no comentário abaixo, esse era um conceito, e você pode fazer o download da classe que estou usando na minha conta do Google Drive . Do jeito que eu usei, a implementação padrão do sistema é usada em versões não afetadas pelo bug.
Tomei algumas premissas (nomes de botões etc.) adequadas às minhas necessidades, porque queria reduzir ao mínimo o código padrão nas classes de clientes. Exemplo de uso completo:
fonte
Vou adicionar meu próprio riff à solução que David Cesarino postou, caso você não esteja usando o Fragments, e queira uma maneira fácil de corrigi-lo em todas as versões (de 2,1 a 4,1):
fonte
android.R.string.ok
e osandroid.R.string.cancel
campos, em vez dos do usuário. E obrigado pela resposta.dateToShow
? O outro nulo existe na verdade a "correção", para que ela deva estar lá. Em qual versão você está?import
você tinhaField
? Estou tendo 6 opções e nenhuma delas funcionou.Até que o bug seja corrigido, sugiro não usar DatePickerDialog ou TimePickerDialog. Use o AlertDialog personalizado com o widget TimePicker / DatePicker;
Alterar TimePickerDialog com;
Alterar DatePickerDialog com;
fonte
O do TimePicker baseado na solução de David Cesarino, "TL; DR: 1-2-3 etapas fáceis para uma solução global"
TimePickerDialog não fornece a funcionalidade como DatePickerDialog.getDatePicker. Portanto, o ouvinte OnTimeSetListener deve ser fornecido. Apenas para manter a semelhança com a solução alternativa do DatePicker, mantive o antigo conceito mListener. Você pode alterá-lo se precisar.
A chamada e o Ouvinte são iguais à solução original. Basta incluir
estender a classe pai,
Implemento
exemplo de chamada
(Atualizado para lidar com o cancelamento)
fonte
Caso alguém queira uma solução rápida, eis o código que eu usei:
}
Onde layout.date_picker_view é um recurso de layout simples com um DatePicker, pois é seu único elemento:
Aqui está o tutorial completo , caso você esteja interessado.
fonte
Minha solução simples. Quando quiser fazê-lo disparar novamente, basta executar "resetFired" (por exemplo, ao abrir a caixa de diálogo novamente).
fonte
onDateSet
será chamado uma vez quando o diálogo estiver sendo fechado por qualquer meio e, se esse meio for definir o horário, ele será acionado novamente, por isso precisamos atender a segunda chamada, não a primeira como você fez,isJellyBeanOrAbove()
, versões inferiores ao Jellybean não têm o bug de que toda esta questão trata, e considerando que queremos receber a segunda chamada, o código não será executado a menos que façamos essa verificação, acredite, tentei o meu código em emuladores e dispositivos reais (com versões diferentes) várias vezes e ele está trabalhando como um encantoDe acordo com a brilhante resposta de Ankur Chaudhary sobre uma
TimePickerDialog
questão semelhante , se checarmosonDateSet
se a visualização fornecida éisShown()
ou não, ela resolverá toda a questão com o mínimo de esforço, sem a necessidade de estender o selecionador ou verificar se há algumas bandeiras hediondas ao redor do código ou até mesmo verificando a versão do sistema operacional, faça o seguinte:e, é claro, o mesmo pode ser feito
onTimeSet
conforme a resposta de Ankurfonte
A maneira como gerenciei essa situação foi usando um sinalizador e substituindo os métodos onCancel e onDismiss.
O onCancel é chamado apenas quando o usuário toca fora da caixa de diálogo ou no botão Voltar. onDismiss sempre é chamado
Definir um sinalizador no método onCancel pode ajudar a filtrar no método onDismiss a intenção do usuário: cancelar a ação ou ação concluída. Abaixo algum código que mostra a ideia.
fonte
Existe uma solução muito simples, se o seu aplicativo não usar a barra de ação. A propósito, observe que alguns aplicativos dependem dessa funcionalidade para funcionar, porque o cancelamento do selecionador de data tem um significado especial (por exemplo, ele limpa o campo de data para uma sequência vazia, o que para alguns aplicativos é um tipo de entrada válido e significativo ) e o uso de sinalizadores booleanos para impedir que a data seja definida duas vezes em OK não ajudará nesse caso.
Ré. a correção real, você não precisa criar novos botões ou sua própria caixa de diálogo. O objetivo é ser compatível com as versões mais antigas do Android, as de buggy (4. ) e quaisquer futuras, embora a última seja impossível de ter certeza, é claro. Observe que no Android 2. , o onStop () para android.app.Dialog não faz nada e, no 4. *, faz mActionBar.setShowHideAnimationEnabled (false), o que é importante apenas se o aplicativo tiver uma barra de ação. O onStop () no DatePickerDialog, que herda do Dialog, apenas contribui com mDatePicker.clearFocus () (a partir da correção mais recente das fontes do Android 4.3), o que não parece essencial.
Portanto, a substituição de onStop () por um método que não faz nada deve, em muitos casos, corrigir seu aplicativo e garantir que ele permaneça assim no futuro próximo. Assim, simplesmente estenda a classe DatePickerDialog com a sua e substitua onStop () por um método fictício. Você também precisará fornecer um ou dois construtores, conforme seus requisitos. Observe também que não se deve tentar exagerar essa correção, por exemplo, tentando fazer algo com a barra de atividades diretamente, pois isso limitaria sua compatibilidade apenas às versões mais recentes do Android. Observe também que seria bom chamar o super para onStop () do DatePicker porque o bug está apenas no onStop () no próprio DatePickerDialog, mas não na super classe de DatePickerDialog. No entanto, isso exigiria que você chamasse super.super.onStop () da sua classe personalizada, qual Java não deixará, como vai contra a filosofia do encapsulamento :) Abaixo está minha pequena classe que eu costumava verriar DatePickerDialog. Espero que este comentário seja útil para alguém. Wojtek Jarosz
}
fonte
Experimente os conceitos abaixo.
o método onDateSet () chama duas vezes (se você estiver fazendo check-in no emulator.it chama duas vezes. Se você estiver usando um dispositivo real, ele chamará corretamente uma única vez. ignore a variável do contador. Para dispositivos reais, está funcionando para mim.)
quando o usuário clica no botão em DatePickerDialog.
para isso, você deve manter um valor de contador e nada fazer quando o mothod chamar pela primeira vez e executar a operação quando o método chamar pela segunda vez.
Consulte os trechos de codificação abaixo
Para cancelar o datepicker, você está trabalhando para mim.
Se você está procurando um emulador de realidade virtual para Android, este é o seu problema.
fonte
Uma solução simples seria usar um booleano para pular a segunda execução
fonte
onStop
chamando o método quando não deveria ... disparar duas vezes é uma consequência do bug.onDateSet
. Assim, quebrado.onDateSet
será chamado uma vez, mas ao escolher "concluído" ou "definir", ele será chamado duas vezes. Mesmos precisamos pular apenas o primeiro, então se ele é chamado duas vezes em seguida, e só então nós temos a data corretaVocê pode substituir onCancel () e usar setOnDismissListener () para detectar ações negativas do usuário. E com um DatePickerDialog.BUTTON_POSITIVE, você sabe que o usuário deseja definir uma nova data.
em seguida, verifique setDate:
fonte
Aqui está minha classe de solução alternativa para DatePickerDialog no botão Cancelar, além de abandoná-la pelo botão Voltar. Copiar e usar no estilo DatePickerDialog (como o ouvinte é stateful, precisamos criar uma nova instância ao usar, caso contrário, é necessário mais código para fazê-lo funcionar)
Usar:
Classe:
}
fonte
Estou usando selecionadores de data, de hora e de número. Os selecionadores de números chamam onValueChanged sempre que o usuário seleciona um número, antes que o selecionador seja dispensado, então eu já tinha uma estrutura como esta para fazer algo com o valor somente quando o selecionador é dispensado:
Estendi isso para definir onClickListeners personalizados para meus botões, com um argumento para ver qual botão foi clicado. Agora posso verificar qual botão foi pressionado antes de definir meu valor final:
E então estendi isso para trabalhar com os tipos de data e hora dos seletores de data e hora, bem como o tipo int dos seletores de números.
Postei isso porque achei que era mais simples do que algumas das soluções acima, mas agora que incluí todo o código, acho que não é muito mais simples! Mas isso se encaixava perfeitamente na estrutura que eu já tinha.
Atualização para o Lollipop: Aparentemente, esse bug não ocorre em todos os dispositivos Android 4.1-4.4, porque recebi alguns relatórios de usuários cujos selecionadores de data e hora não estavam chamando os retornos de chamada onDateSet e onTimeSet. E o bug foi corrigido oficialmente no Android 5.0. Minha abordagem só funcionava em dispositivos onde o bug está presente, porque meus botões personalizados não chamavam o manipulador onClick da caixa de diálogo, que é o único local em que onDateSet e onTimeSet são chamados quando o bug não está presente. Eu atualizei meu código acima para chamar o onClick da caixa de diálogo, então agora funciona se o bug está presente ou não.
fonte
Gostei da resposta de David Cesarino acima, mas queria algo que substituísse a caixa de diálogo quebrada e funcionaria em qualquer caixa de diálogo que estivesse faltando cancelar / tenha um comportamento incorreto de cancelamento. Aqui estão as classes derivadas para DatePickerDialog / TimePickerDialog que devem funcionar como uma substituição nas substituições. Essas não são visualizações personalizadas. Ele usa a caixa de diálogo do sistema, mas apenas altera o comportamento do botão Cancelar / Voltar para funcionar como esperado.
Isso deve funcionar no nível 3 da API e superior. Então, basicamente, qualquer versão do Android (eu testei especificamente em geléia e pirulito).
DatePickerDialog:
TimePickerDialog:
fonte
Minha versão de trabalho com o ClearButton usando expressões Lambda:
fonte
Para TimePickerDialog, a solução alternativa pode ser a seguinte:
Eu deleguei todos os eventos para o wrapper KitKatSetTimeListener e somente aciono novamente o OnTimeSetListener original, no caso de clicar em BUTTON_POSITIVE.
fonte
Depois de testar algumas das sugestões postadas aqui, pessoalmente acho que essa solução é a mais simples. Passo "nulo" como meu ouvinte no construtor DatePickerDialog e, quando clico no botão "OK", chamo meu onDateSearchSetListener:
fonte
Sei que este post está aqui há quase um ano, mas achei que deveria postar minhas descobertas. Você ainda pode manter o ouvinte (em vez de defini-lo para meditar) e ainda ter esse trabalho conforme o esperado. A chave é definir implicitamente os botões "OK" ou (e) os "cancelar". Eu testei e funciona com gratidão para mim. O ouvinte não é demitido duas vezes.
Veja este exemplo,
fonte
timePickerListener
ainda é chamado, independentemente do que você faz na sua caixa de diálogo. Eu nem preciso testar para saber disso, você só precisa olhar para as fontes : se você não definirnull
,tryNotifyTimeSet()
chamará o ouvinteonTimeSet()
tanto no seuonClick()
como noonStop()
.