Jelly Bean DatePickerDialog - existe uma maneira de cancelar?

148

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

DatePickerDialogfoi 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 OnDateSetListenerouvinte. 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 DatePickerFragmentnão importa. Simplifiquei o problema para você, mas já testei.
davidcsb
fonte
Parabéns, você parece ter encontrado um bug no Android. Você pode denunciá-lo aqui .
Michael Hampton
1
Relatório de erro muito bem escrito. Eu posso entendê-lo completamente sem precisar executar o código.
Cheok Yan Cheng
6
Peço a todos que votem nesta questão! Edição 34833
Bradley
Você não pode simplesmente substituir a função do botão para agir como se tivesse sido descartada devido ao toque fora da caixa de diálogo?
Karthik Balakrishnan
2
O bug ainda está aberto depois de 2 anos ... inacreditável.
Erdomester 12/12/14

Respostas:

115

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:

  1. Faça o download desta aula.
  2. Implemente OnDateSetListenerem sua atividade (ou mude a classe para atender às suas necessidades).
  3. Acione a caixa de diálogo com este código (neste exemplo, eu o uso dentro de a Fragment):

    Bundle b = new Bundle();
    b.putInt(DatePickerDialogFragment.YEAR, 2012);
    b.putInt(DatePickerDialogFragment.MONTH, 6);
    b.putInt(DatePickerDialogFragment.DATE, 17);
    DialogFragment picker = new DatePickerDialogFragment();
    picker.setArguments(b);
    picker.show(getActivity().getSupportFragmentManager(), "frag_date_picker");

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

private void tryNotifyDateSet() {
    if (mCallBack != null) {
        mDatePicker.clearFocus();
        mCallBack.onDateSet(mDatePicker, mDatePicker.getYear(),
                mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
    }
}

@Override
protected void onStop() {
    tryNotifyDateSet();
    super.onStop();
}

Eu acho que poderia ter sido:

@Override
protected void onStop() {
    // instead of the full tryNotifyDateSet() call:
    if (mCallBack != null) mDatePicker.clearFocus();
    super.onStop();
}

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 doDatePickerDialog.java na edição de lá.

Conceito para evitar o bug

Defina o ouvinte como nullno construtor e crie seu próprio BUTTON_POSITIVEbotã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:

    /**
 * @param context The context the dialog is to run in.
 * @param callBack How the parent is notified that the date is set.
 * @param year The initial year of the dialog.
 * @param monthOfYear The initial month of the dialog.
 * @param dayOfMonth The initial day of the dialog.
 */
public DatePickerDialog(Context context,
        OnDateSetListener callBack,
        int year,
        int monthOfYear,
        int dayOfMonth) {
    this(context, 0, callBack, year, monthOfYear, dayOfMonth);
}

    /**
 * @param context The context the dialog is to run in.
 * @param theme the theme to apply to this dialog
 * @param callBack How the parent is notified that the date is set.
 * @param year The initial year of the dialog.
 * @param monthOfYear The initial month of the dialog.
 * @param dayOfMonth The initial day of the dialog.
 */
public DatePickerDialog(Context context,
        int theme,
        OnDateSetListener callBack,
        int year,
        int monthOfYear,
        int dayOfMonth) {
    super(context, theme);

    mCallBack = callBack;
    // ... rest of the constructor.
}

Portanto, o truque é fornecer um nullouvinte 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):

    DatePickerDialog picker = new DatePickerDialog(
        this,
        null, // instead of a listener
        2012, 6, 15);
    picker.setCancelable(true);
    picker.setCanceledOnTouchOutside(true);
    picker.setButton(DialogInterface.BUTTON_POSITIVE, "OK",
        new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.d("Picker", "Correct behavior!");
            }
        });
    picker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", 
        new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.d("Picker", "Cancel!");
            }
        });
picker.show();

Agora ele funcionará devido à possível correção que eu postei acima.

E como DatePickerDialog.javaverifica a nullsempre 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:

class YourActivity extends SherlockFragmentActivity implements OnDateSetListener

// ...

Bundle b = new Bundle();
b.putInt(DatePickerDialogFragment.YEAR, 2012);
b.putInt(DatePickerDialogFragment.MONTH, 6);
b.putInt(DatePickerDialogFragment.DATE, 17);
DialogFragment picker = new DatePickerDialogFragment();
picker.setArguments(b);
picker.show(getActivity().getSupportFragmentManager(), "fragment_date_picker");
David Cesarino
fonte
11
Trabalho impressionante na pesquisa! Triste que fosse necessário, mas incrível, no entanto.
CommonsWare
1
Muito agradável! Estive martelando minha cabeça contra a parede nesse problema que estava atualizando meus campos de texto e chamando / não chamando os retornos de chamada corretos. Obrigado! Gostaria de saber se a solução sugerida é "prova do futuro" ou se você acha que causará problemas quando o bug for corrigido?
extensão
1
@RomainGuidoux veja a resposta atualizada no final. A classe no link tem a inteligência de chamar esse método apenas no Jelly Bean. Em qualquer coisa abaixo, isso ignorará e usará a implementação do sistema padrão, roteando a chamada do sistema para o conjunto de dados para a sua atividade. É apenas que, no Jelly Bean, são necessárias medidas adicionais (evitando o bug) antes de rotear o retorno de chamada, e isso envolve chamar esse método honeycomb +. Mas, novamente, apenas em JB.
Davidcsb
1
Trabalho impressionante aqui. Não tenho palavras para descrever quão ridículo é esse problema.
Bill Phillips
5
E o TimePickerDialog? Parece que TimePickerDialog não tem um getTimePicker ()
lokoko
16

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

public class FixedDatePickerDialog extends DatePickerDialog {
  //I use a Calendar object to initialize it, but you can revert to Y,M,D easily
  public FixedDatePickerDialog(Calendar dateToShow, Context context, OnDateSetListener callBack) {
    super(context, null, dateToShow.get(YEAR), dateToShow.get(MONTH), dateToShow.get(DAY_OF_MONTH));
    initializePicker(callBack);
  }

  public FixedDatePickerDialog(Calendar dateToShow, Context context, int theme,
    OnDateSetListener callBack) {
    super(context, theme, null, dateToShow.get(YEAR), dateToShow.get(MONTH), dateToShow.get(DAY_OF_MONTH));
    initializePicker(callBack);
  }

  private void initializePicker(final OnDateSetListener callback) {
    try {
      //If you're only using Honeycomb+ then you can just call getDatePicker() instead of using reflection
      Field pickerField = DatePickerDialog.class.getDeclaredField("mDatePicker");
      pickerField.setAccessible(true);
      final DatePicker picker = (DatePicker) pickerField.get(this);
      this.setCancelable(true);
      this.setButton(DialogInterface.BUTTON_NEGATIVE, getContext().getText(android.R.string.cancel), (OnClickListener) null);
      this.setButton(DialogInterface.BUTTON_POSITIVE, getContext().getText(android.R.string.ok),
          new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                picker.clearFocus(); //Focus must be cleared so the value change listener is called
                callback.onDateSet(picker, picker.getYear(), picker.getMonth(), picker.getDayOfMonth());
              }
          });
    } catch (Exception e) { /* Reflection probably failed*/ }
  }
}
dmon
fonte
Lembre-se: se você estiver conectando os nomes dos botões para ok e cancelar, provavelmente é melhor usar o padrão android.R.string.oke os android.R.string.cancelcampos, em vez dos do usuário. E obrigado pela resposta.
Davidcsb
Ah, legal, eu não notei que eles forneceram OK e Cancelar texto. Obrigado!
dmon 10/09/12
obtendo a exceção nullpointer em super (contexto, tema, nulo, dateToShow.get (YEAR), dateToShow.get (MONTH), dateToShow.get (DAY_OF_MONTH));
Kishore
Errr ... você está passando um nulo dateToShow? O outro nulo existe na verdade a "correção", para que ela deva estar lá. Em qual versão você está?
DMON
você pode me informar para qual importvocê tinha Field? Estou tendo 6 opções e nenhuma delas funcionou.
Adil Malik
8

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;

    final TimePicker timePicker = new TimePicker(this);
    timePicker.setIs24HourView(true);
    timePicker.setCurrentHour(20);
    timePicker.setCurrentMinute(15);

    new AlertDialog.Builder(this)
            .setTitle("Test")
            .setPositiveButton(android.R.string.ok, new OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Log.d("Picker", timePicker.getCurrentHour() + ":"
                            + timePicker.getCurrentMinute());
                }
            })
            .setNegativeButton(android.R.string.cancel,
                    new OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog,
                                int which) {
                            Log.d("Picker", "Cancelled!");
                        }
                    }).setView(timePicker).show();

Alterar DatePickerDialog com;

    final DatePicker datePicker = new DatePicker(this);
    datePicker.init(2012, 10, 5, null);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        datePicker.setCalendarViewShown(false);
    }

    new AlertDialog.Builder(this)
            .setTitle("Test")
            .setPositiveButton(android.R.string.ok, new OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Log.d("Picker", datePicker.getYear() + " "
                            + (datePicker.getMonth() + 1) + " "
                            + datePicker.getDayOfMonth());
                }
            })
            .setNegativeButton(android.R.string.cancel,
                    new OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog,
                                int which) {
                            Log.d("Picker", "Cancelled!");
                        }
                    }).setView(datePicker).show();
cirit
fonte
4

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

import android.app.TimePickerDialog;
import android.app.TimePickerDialog.OnTimeSetListener;

estender a classe pai,

... implements OnDateSetListener, OnTimeSetListener

Implemento

 @Override
 public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
 ...
 }

exemplo de chamada

    Calendar cal = Calendar.getInstance();
    int hour = cal.get(Calendar.HOUR_OF_DAY);
    int minute = cal.get(Calendar.MINUTE);


    Bundle b = new Bundle();
    b.putInt(TimePickerDialogFragment.HOUR, hour);
    b.putInt(TimePickerDialogFragment.MINUTE, minute);

    DialogFragment picker = new TimePickerDialogFragment();
    picker.setArguments(b);
    picker.show(getSupportFragmentManager(), "frag_time_picker");

(Atualizado para lidar com o cancelamento)

public class TimePickerDialogFragment extends DialogFragment {

    public static final String HOUR = "Hour";
    public static final String MINUTE = "Minute";

    private boolean isCancelled = false; //Added to handle cancel
    private TimePickerDialog.OnTimeSetListener mListener;

    //Added to handle parent listener
    private TimePickerDialog.OnTimeSetListener mTimeSetListener = new TimePickerDialog.OnTimeSetListener() {
        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
            if (!isCancelled)
            {
                mListener.onTimeSet(view,hourOfDay,minute);
            }
        }
    };
    //
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        this.mListener = (TimePickerDialog.OnTimeSetListener) activity;
    }

    @Override
    public void onDetach() {
        this.mListener = null;
        super.onDetach();
    }

    @TargetApi(11)
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Bundle b = getArguments();
        int h = b.getInt(HOUR);
        int m = b.getInt(MINUTE);

        final TimePickerDialog picker = new TimePickerDialog(getActivity(), getConstructorListener(), h, m,DateFormat.is24HourFormat(getActivity()));

        //final TimePicker timePicker = new TimePicker(getBaseContext());
        if (hasJellyBeanAndAbove()) {
            picker.setButton(DialogInterface.BUTTON_POSITIVE,
                    getActivity().getString(android.R.string.ok),
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            isCancelled = false; //Cancel flag, used in mTimeSetListener
                        }
                    });
            picker.setButton(DialogInterface.BUTTON_NEGATIVE,
                    getActivity().getString(android.R.string.cancel),
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            isCancelled = true; //Cancel flag, used in mTimeSetListener
                        }
                    });
        }
        return picker;
    }
    private boolean hasJellyBeanAndAbove() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
    }

    private TimePickerDialog.OnTimeSetListener getConstructorListener() {
        return hasJellyBeanAndAbove() ? mTimeSetListener : mListener; //instead of null, mTimeSetListener is returned.
    }
}
Tejasvi Hegde
fonte
Ele não está funcionando para mim quando se tenta-lo em s3 API 18
AbdelHady
3

Caso alguém queira uma solução rápida, eis o código que eu usei:

public void showCustomDatePicker () {

final DatePicker mDatePicker = (DatePicker) getLayoutInflater().
        inflate(R.layout.date_picker_view, null);
//Set an initial date for the picker
final Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
//Set the date now
mDatePicker.updateDate(year, month, day);

//create the dialog
AlertDialog.Builder mBuilder = new Builder(this);
//set the title
mBuilder.setTitle(getString(R.string.date_picker_title))
    //set our date picker
    .setView(mDatePicker)
    //set the buttons 
.setPositiveButton(android.R.string.ok, new OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        //whatever method you choose to handle the date changes
            //the important thing to know is how to retrieve the data from the picker
        handleOnDateSet(mDatePicker.getYear(), 
                mDatePicker.getMonth(), 
                mDatePicker.getDayOfMonth());
    }
})
.setNegativeButton(android.R.string.cancel, new OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        dialog.dismiss();
    }
})
//create the dialog and show it.
.create().show();

}

Onde layout.date_picker_view é um recurso de layout simples com um DatePicker, pois é seu único elemento:

<!xml version="1.0" encoding="utf-8">
<DatePicker xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/date_picker"
android:layout_width="fill_parent"   
android:spinnersShown="true" 
android:calendarViewShown="false"
android:layout_height="fill_parent"/>

Aqui está o tutorial completo , caso você esteja interessado.

daniel_c05
fonte
Isso funcionou maravilhosamente para mim. Fácil de entender, fácil de implementar! Testado em 4.4
erdomester
3

Minha solução simples. Quando quiser fazê-lo disparar novamente, basta executar "resetFired" (por exemplo, ao abrir a caixa de diálogo novamente).

private class FixedDatePickerDialogListener implements DatePickerDialog.OnDateSetListener{
    private boolean fired;

    public void resetFired(){
        fired = false;
    }

    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        if (fired) {
            Log.i("DatePicker", "Double fire occurred.");
            return;//ignore and return.
        } 
        //put your code here to handle onDateSet
        fired = true;//first time fired 
    }
}
Daniel Ryan
fonte
@AbdelHady sua edição de código estava incorreta, mas eu a editei para torná-la mais clara. Não há necessidade de adicionar um método "isJellyBeanOrAbove ()", não há vantagem, apenas adiciona complexidade desnecessária. Ligar para resetFired é barato.
Daniel Ryan
o código aqui está incorreto, porque onDateSetserá 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,
AbdelHady 7/11
No que diz respeito 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 encanto
AbdelHady
Não tenho certeza se isso vale a pena. Eu publiquei isso há 2 anos, isso está em nosso aplicativo comercial desde então, funcionando muito bem. Nenhum bug relatado por nossas equipes de controle de qualidade ou por milhares de usuários. Isso pretende ser uma resposta simples que as pessoas possam estender. Chame "resetFired" quando quiser que seja acionado novamente.
Daniel Ryan
Em nosso aplicativo "isJellyBeanOrAbove" não é necessário. O aplicativo funcionará bem em todas as versões se você chamar "resetFired" nas áreas corretas.
Daniel Ryan
3

De acordo com a brilhante resposta de Ankur Chaudhary sobre uma TimePickerDialogquestão semelhante , se checarmos onDateSetse 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:

public void onDateSet(DatePicker view, int year, int month, int day) {
    if (view.isShown()) {
        // read the date here :)
    }
}

e, é claro, o mesmo pode ser feito onTimeSetconforme a resposta de Ankur

AbdelHady
fonte
1
Melhor resposta dentre todas as outras!
hiew1
2

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.

public class DatePickerDialogFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener {

    private boolean cancelDialog = false;
    private int year;
    private int month;
    private int day;

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        DatePickerDialog dpd = new DatePickerDialog(getActivity(), this, year, month, day);
        return dpd;
    }

    public void setDatePickerDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        super.onCancel(dialog);
        cancelDialog = true;
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        if (!cancelDialog) {
          #put the code you want to execute if the user clicks the done button
        }
    }

    @Override
    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        setDatePickerDate(year, monthOfYear, dayOfMonth);
    }
}
Alvaro
fonte
1

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

public class myDatePickerDialog extends DatePickerDialog {

public myDatePickerDialog(Context context, OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
    super(context, callBack, year, monthOfYear, dayOfMonth);
}

@Override
protected void onStop() {
    // Replacing tryNotifyDateSet() with nothing - this is a workaround for Android bug https://android-review.googlesource.com/#/c/61270/A

    // Would also like to clear focus, but we cannot get at the private members, so we do nothing.  It seems to do no harm...
    // mDatePicker.clearFocus();

    // Now we would like to call super on onStop(), but actually what we would mean is super.super, because
    // it is super.onStop() that we are trying NOT to run, because it is buggy.  However, doing such a thing
    // in Java is not allowed, as it goes against the philosophy of encapsulation (the Creators never thought
    // that we might have to patch parent classes from the bottom up :)
    // However, we do not lose much by doing nothing at all, because in Android 2.* onStop() in androd.app.Dialog //actually
    // does nothing and in 4.* it does:
    //      if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); 
    // which is not essential for us here because we use no action bar... QED
    // So we do nothing and we intend to keep this workaround forever because of users with older devices, who might
    // run Android 4.1 - 4.3 for some time to come, even if the bug is fixed in later versions of Android.
}   

}

Wojtek Jarosz
fonte
Mesmo se um ActionBar for usado, isso pode ser uma solução aceitável se você nunca ocultar / mostrar o ActionBar. Para tornar as coisas ainda mais seguras, você pode substituir o onStart para não fazer nada também (as chamadas para a animação não serão atendidas com segurança). E mesmo que você oculte / mostre, é apenas a animação que está desativada.
Daniel
0


Experimente os conceitos abaixo.

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


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

   static int counter=0;       //Counter will be declared globally.

    DatePickerDialog picker = new DatePickerDialog(
            this,
            new OnDateSetListener() {
                @Override
                public void onDateSet(DatePicker v, int y, int m, int d) {

                   counter++;
                   if(counter==1) return;
                   counter=0;
                   //Do the operations here

                }
            },
            2012, 6, 15);
    picker.show();



Para cancelar o datepicker, você está trabalhando para mim.

DialogInterface.OnClickListener dialogOnClickListener=new DialogInterface.OnClickListener()
        {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                // TODO Auto-generated method stub

                if(which==Dialog.BUTTON_NEGATIVE)
                {
                    Log.i(tagName, "dialog negative button clicked");
                    dialog.dismiss();
                }

            }

        };

        mDatePickerDialog.setButton(Dialog.BUTTON_NEGATIVE, "Cancel", dialogOnClickListener);


Se você está procurando um emulador de realidade virtual para Android, este é o seu problema.

SIVAKUMAR.J
fonte
0

Uma solução simples seria usar um booleano para pular a segunda execução

boolean isShow = false; // define global variable


// when showing time picker
TimePickerDialog timeDlg = new TimePickerDialog( this, new OnTimeSetListener()
            {

                @Override
                public void onTimeSet( TimePicker view, int hourOfDay, int minute )
                {
                    if ( isShow )
                    {
                        isShow = false;
                        // your code
                    }

                }
            }, 8, 30, false );

timeDlg.setButton( TimePickerDialog.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick( DialogInterface dialog, int which )
                {
                    isShow = false;
                }
            } );
timeDlg.setButton( TimePickerDialog.BUTTON_POSITIVE, "Set", new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick( DialogInterface dialog, int which )
                {
                    isShow = true;
                }
            } );

timeDlg.show();
chamikaw
fonte
Mais uma vez, assim como eu disse ao outro cara antes de você, isso não resolve o erro. Não quero parecer grosseiro, mas vocês devem testar sua própria solução antes de postar aqui ... apenas para provar meu ponto, use seu código e deixe o usuário pressionar para cancelar a caixa de diálogo e entender o que quero dizer. A causa do bug está onStopchamando o método quando não deveria ... disparar duas vezes é uma consequência do bug.
Davidcsb
Se eu não estava suficientemente claro, permita-me: pressionando novamente para cancelar as chamadas de diálogo onDateSet. Assim, quebrado.
Davidcsb
Obrigado por mostrar o erro. Eu edito a resposta para que funcione com o botão Voltar. Sua resposta está funcionando, mas DatePicker dp = picker.getDatePicker (); não está funcionando com TimePickers, pois o método getTimePicker () não foi adicionado. Portanto, esta seria uma resposta válida
chamikaw
Como estamos tentando pular a segunda execução? !!, Eu tentei várias vezes, ao fechar o diálogo por qualquer meio onDateSetserá 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 correta
AbdelHady
0

Você 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.

 DatePickerDialog mDPD = new DatePickerDialog(
                      getActivity(), mOnDateSetListener, mYear, mMonth, mDay);
 mDPD.setOnCancelListener(new OnCancelListener() {
    @Override
    public void onCancel(DialogInterface dialog) {
        // do something onCancek
        setDate = false;
    }
 });

 mDPD.setOnDismissListener(new OnDismissListener() {
    @Override
    public void onDismiss(DialogInterface arg0) {
        // do something onDismiss
        setDate = false;
    }
});

mDPD.setButton(DatePickerDialog.BUTTON_POSITIVE, "Finish", new DatePickerDialog.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        // user set new date
        setDate = true;
    }
});

em seguida, verifique setDate:

public void onDateSet(DatePicker view, int year, int month, int day) {
    if(setDate){
        //do something with new date
    }
}
Markus Rubey
fonte
0

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:

new FixedDatePickerDialog(this,
            new FixedOnDateSetListener() {

                @Override
                public void onDateSet(DatePicker view, int year,
                        int monthOfYear, int dayOfMonth) {
                    if (isOkSelected()) {
                        // when DONE button is clicked
                    }
                }

            }, year, month, day).show();

Classe:

public class FixedDatePickerDialog extends DatePickerDialog {
private final FixedOnDateSetListener fixedCallback;
public FixedDatePickerDialog(Context context,
        FixedOnDateSetListener callBack, int year, int monthOfYear,
        int dayOfMonth) {
    super(context, callBack, year, monthOfYear, dayOfMonth);
    fixedCallback = callBack;
    this.setButton(DialogInterface.BUTTON_NEGATIVE,
            context.getString(R.string.cancel), this);
    this.setButton(DialogInterface.BUTTON_POSITIVE,
            context.getString(R.string.done), this);
}

@Override
public void onClick(DialogInterface dialog, int which) {
    if (which == BUTTON_POSITIVE) {
        fixedCallback.setOkSelected(true);
    } else {
        fixedCallback.setOkSelected(false);
    }
    super.onClick(dialog, which);
}

public abstract static class FixedOnDateSetListener implements
        OnDateSetListener {
    private boolean okSelected = false;

    @Override
    abstract public void onDateSet(DatePicker view, int year,
            int monthOfYear, int dayOfMonth);

    public void setOkSelected(boolean okSelected) {
        this.okSelected = okSelected;
    }

    public boolean isOkSelected() {
        return okSelected;
    }
}

}

Loc Phan
fonte
0

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:

public int interimValue;
public int finalValue;

public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
    this.interimValue = newVal;
}

public void onDismiss(DialogInterface dialog) {
    super.onDismiss(dialog);
    this.finalValue = this.interimValue;
}

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:

public int interimValue;
public int finalValue;
public boolean saveButtonClicked;

public void setup() {
    picker.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.BUTTON_SAVE), new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            picker.onClick(dialog, which); // added for Android 5.0
            onButtonClicked(true);
        }
    });
    picker.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.BUTTON_CANCEL), new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            picker.onClick(dialog, which); // added for Android 5.0
            onButtonClicked(false);
        }
    });
}

public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
    this.interimValue = newVal;
}

public void onButtonClicked(boolean save) {
    this.saveButtonClicked = save;
}

public void onDismiss(DialogInterface dialog) {
    super.onDismiss(dialog);
    if (this.saveButtonClicked) {
        // save
        this.finalValue = this.interimValue;
    } else {
        // cancel
    }
}

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.

arlomedia
fonte
0

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:

package snappy_company_name_here;

import android.content.Context;
import android.content.DialogInterface;
import android.widget.DatePicker;

/**
 * This is a modified version of DatePickerDialog that correctly handles cancellation behavior since it's broken on jellybean and
 * kitkat date pickers.
 *
 * Here is the bug: http://code.google.com/p/android/issues/detail?id=34833
 * Here is an SO post with a bunch of details: http://stackoverflow.com/questions/11444238/jelly-bean-datepickerdialog-is-there-a-way-to-cancel
 *
 * @author stuckj, created on 5/5/15.
 */
public class DatePickerDialog extends android.app.DatePickerDialog implements DialogInterface.OnClickListener
{
    final CallbackHelper callbackHelper;

    // NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
    private static class CallbackHelper implements OnDateSetListener
    {
        private final OnDateSetListener callBack;
        private boolean dialogButtonPressHandled = false; // To prevent setting the date when the dialog is dismissed...

        // NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
        public CallbackHelper(final OnDateSetListener callBack)
        {
            this.callBack = callBack;
        }

        @Override
        public void onDateSet(final DatePicker view, final int year, final int monthOfYear, final int dayOfMonth)
        {
            if (!dialogButtonPressHandled && (callBack != null))
            {
                callBack.onDateSet(view, year, monthOfYear, dayOfMonth);
            }
        }
    }

    /**
     * Sets the positive and negative buttons to use the dialog callbacks we define.
     */
    private void setButtons(final Context context)
    {
        setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), this);
        setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), this);
    }

    @Override
    public void onClick(final DialogInterface dialog, final int which)
    {
        // ONLY call the super method in the positive case...
        if (which == DialogInterface.BUTTON_POSITIVE)
        {
            super.onClick(dialog, which);
        }

        callbackHelper.dialogButtonPressHandled = true;
    }

    @Override
    public void onBackPressed()
    {
        getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
    }

    // Need this so we can both pass callbackHelper to the super class and save it off as a variable.
    private DatePickerDialog(final Context context,
                             final OnDateSetListener callBack,
                             final int year,
                             final int monthOfYear,
                             final int dayOfMonth,
                             final CallbackHelper callbackHelper)
    {
        super(context, callbackHelper, year, monthOfYear, dayOfMonth);
        this.callbackHelper = callbackHelper;
        setButtons(context);
    }

    /**
     * @param context The context the dialog is to run in.
     * @param callBack How the parent is notified that the date is set.
     * @param year The initial year of the dialog.
     * @param monthOfYear The initial month of the dialog.
     * @param dayOfMonth The initial day of the dialog.
     */
    public DatePickerDialog(final Context context,
                            final OnDateSetListener callBack,
                            final int year,
                            final int monthOfYear,
                            final int dayOfMonth)
    {
        this(context, callBack, year, monthOfYear, dayOfMonth, new CallbackHelper(callBack));
    }

    // Need this so we can both pass callbackHelper to the super class and save it off as a variable.
    private DatePickerDialog(final Context context, final int theme, final OnDateSetListener listener, final int year,
                             final int monthOfYear, final int dayOfMonth, final CallbackHelper callbackHelper)
    {
        super(context, theme, callbackHelper, year, monthOfYear, dayOfMonth);
        this.callbackHelper = callbackHelper;
        setButtons(context);
    }

    /**
     * @param context The context the dialog is to run in.
     * @param theme the theme to apply to this dialog
     * @param listener How the parent is notified that the date is set.
     * @param year The initial year of the dialog.
     * @param monthOfYear The initial month of the dialog.
     * @param dayOfMonth The initial day of the dialog.
     */
    public DatePickerDialog(final Context context, final int theme, final OnDateSetListener listener, final int year,
                            final int monthOfYear, final int dayOfMonth)
    {
        this(context, theme, listener, year, monthOfYear, dayOfMonth, new CallbackHelper(listener));
    }
}

TimePickerDialog:

package snappy_company_name_here;

import android.content.Context;
import android.content.DialogInterface;
import android.widget.TimePicker;

/**
 * This is a modified version of TimePickerDialog that correctly handles cancellation behavior since it's broken on jellybean and
 * kitkat date pickers.
 *
 * Here is the bug: http://code.google.com/p/android/issues/detail?id=34833
 * Here is an SO post with a bunch of details: http://stackoverflow.com/questions/11444238/jelly-bean-datepickerdialog-is-there-a-way-to-cancel
 *
 * @author stuckj, created on 5/5/15.
 */
public class TimePickerDialog extends android.app.TimePickerDialog implements DialogInterface.OnClickListener
{
    final CallbackHelper callbackHelper;

    // NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
    private static class CallbackHelper implements OnTimeSetListener
    {
        private final OnTimeSetListener callBack;
        private boolean dialogButtonPressHandled = false; // To prevent setting the date when the dialog is dismissed...

        // NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
        public CallbackHelper(final OnTimeSetListener callBack)
        {
            this.callBack = callBack;
        }

        @Override
        public void onTimeSet(final TimePicker view, final int hourOfDay, final int minute)
        {
            if (!dialogButtonPressHandled && (callBack != null))
            {
                callBack.onTimeSet(view, hourOfDay, minute);
            }
        }
    }

    /**
     * Sets the positive and negative buttons to use the dialog callbacks we define.
     */
    private void setButtons(final Context context)
    {
        setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), this);
        setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), this);
    }

    @Override
    public void onClick(final DialogInterface dialog, final int which)
    {
        // ONLY call the super method in the positive case...
        if (which == DialogInterface.BUTTON_POSITIVE)
        {
            super.onClick(dialog, which);
        }

        callbackHelper.dialogButtonPressHandled = true;
    }

    @Override
    public void onBackPressed()
    {
        getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
    }

    // Need this so we can both pass callbackHelper to the super class and save it off as a variable.
    private  TimePickerDialog(final Context context,
                              final OnTimeSetListener callBack,
                              final int hourOfDay, final int minute, final boolean is24HourView, final CallbackHelper callbackHelper)
    {
        super(context, callbackHelper, hourOfDay, minute, is24HourView);
        this.callbackHelper = callbackHelper;
        setButtons(context);
    }

    /**
     * @param context Parent.
     * @param callBack How parent is notified.
     * @param hourOfDay The initial hour.
     * @param minute The initial minute.
     * @param is24HourView Whether this is a 24 hour view, or AM/PM.
     */
    public TimePickerDialog(final Context context,
                            final OnTimeSetListener callBack,
                            final int hourOfDay, final int minute, final boolean is24HourView)
    {
        this(context, callBack, hourOfDay, minute, is24HourView, new CallbackHelper(callBack));
    }

    // Need this so we can both pass callbackHelper to the super class and save it off as a variable.
    private TimePickerDialog(final Context context, final int theme, final OnTimeSetListener callBack, final int hourOfDay,
                            final int minute, final boolean is24HourView, final CallbackHelper callbackHelper)
    {
        super(context, theme, callbackHelper, hourOfDay, minute, is24HourView);
        this.callbackHelper = callbackHelper;
        setButtons(context);
    }

    /**
     * @param context Parent.
     * @param theme the theme to apply to this dialog
     * @param callBack How parent is notified.
     * @param hourOfDay The initial hour.
     * @param minute The initial minute.
     * @param is24HourView Whether this is a 24 hour view, or AM/PM.
     */
    public TimePickerDialog(final Context context, final int theme, final OnTimeSetListener callBack, final int hourOfDay,
                            final int minute, final boolean is24HourView)
    {
        this(context, theme, callBack, hourOfDay, minute, is24HourView, new CallbackHelper(callBack));
    }
}
stuckj
fonte
Isso é bastante semelhante à resposta do The Sea acima.
stuckj
0

Minha versão de trabalho com o ClearButton usando expressões Lambda:

public class DatePickerFragment extends DialogFragment {
    private OnDateSelectListener dateSelectListener;
    private OnDateClearListener dateClearListener;

    public void setDateSelectListener(OnDateSelectListener dateSelectListener) {
        this.dateSelectListener = dateSelectListener;
    }

    public void setDateClearListener(OnDateClearListener dateClearListener) {
        this.dateClearListener = dateClearListener;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the current date as the default date in the picker
        final Calendar c = Calendar.getInstance();
        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH);
        int day = c.get(Calendar.DAY_OF_MONTH);

        // Create a new instance of DatePickerDialog and return it
        DatePickerDialog dialog = new DatePickerDialog(getActivity(), null, year, month, day);
        dialog.setCancelable(true);
        dialog.setCanceledOnTouchOutside(true);
        dialog.setTitle("Select Date");
        dialog.setButton(BUTTON_POSITIVE, ("Done"), (dialog1, which) -> {
            DatePicker dp = dialog.getDatePicker();
            dialog.dismiss();
            dateSelectListener.onDateSelect(dp.getYear(), dp.getMonth(), dp.getDayOfMonth());
        });
        dialog.setButton(BUTTON_NEUTRAL, ("Clear"), (dialog1, which) -> {
            dialog.dismiss();
            dateClearListener.onDateClear();
        });
        dialog.setButton(BUTTON_NEGATIVE, ("Cancel"), (dialog1, which) -> {
            if (which == DialogInterface.BUTTON_NEGATIVE) {
                dialog.cancel();
            }
        });
        dialog.getDatePicker().setCalendarViewShown(false);
        return dialog;
    }


    public interface OnDateClearListener {
        void onDateClear();
    }

    public interface OnDateSelectListener {
        void onDateSelect(int year, int monthOfYear, int dayOfMonth);
    }
}
Vasiliy Prokopishin
fonte
0

Para TimePickerDialog, a solução alternativa pode ser a seguinte:

TimePickerDialog createTimePickerDialog(Context context, int themeResId, TimePickerDialog.OnTimeSetListener orignalListener,
                                                         int hourOfDay, int minute, boolean is24HourView) {
        class KitKatTimeSetListener implements TimePickerDialog.OnTimeSetListener {
            private int hour;
            private int minute;

            private KitKatTimeSetListener() {
            }

            @Override
            public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
                this.hour = hourOfDay;
                this.minute = minute;
            }

            private int getHour() { return hour; }
            private int getMinute() {return minute; }
        };

        KitKatTimeSetListener kitkatTimeSetListener = new KitKatTimeSetListener();
        TimePickerDialog timePickerDialog = new TimePickerDialog(context, themeResId, kitkatTimeSetListener, hourOfDay, minute, is24HourView);

        timePickerDialog.setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), (dialog, which) -> {
            timePickerDialog.onClick(timePickerDialog, DialogInterface.BUTTON_POSITIVE);
            orignalListener.onTimeSet(new TimePicker(context), kitkatTimeSetListener.getHour(), kitkatTimeSetListener.getMinute());
            dialog.cancel();
        });
        timePickerDialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), (dialog, which) -> {
            dialog.cancel();
        });

        return timePickerDialog;
    }

Eu deleguei todos os eventos para o wrapper KitKatSetTimeListener e somente aciono novamente o OnTimeSetListener original, no caso de clicar em BUTTON_POSITIVE.

Southerton
fonte
0

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:

datePickerDialog = new DatePickerDialog(getContext(), null, dateSearch.get(Calendar.YEAR), dateSearch.get(Calendar.MONTH), dateSearch.get(Calendar.DAY_OF_MONTH));
    datePickerDialog.setCancelable(false);
    datePickerDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            Log.d("Debug", "Correct");
            onDateSearchSetListener.onDateSet(datePickerDialog.getDatePicker(), datePickerDialog.getDatePicker().getYear(), datePickerDialog.getDatePicker().getMonth(), datePickerDialog.getDatePicker().getDayOfMonth());
        }
    });
    datePickerDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            Log.d("Debug", "Cancel");
            dialog.dismiss();
        }
    });
Birk Bonnicksen
fonte
-1

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,

private void setTime(){
final Calendar c = Calendar.getInstance();
int hour = c.get(Calendar.HOUR_OF_DAY);
int minute = c.get(Calendar.MINUTE);

final TimePickerDialog timepicker = new TimePickerDialog(this.getActivity(),
        timePickerListener,
        hour, 
        minute, 
        DateFormat.is24HourFormat(getActivity()));

timepicker.setButton(DialogInterface.BUTTON_POSITIVE, "Print", new    
    android.content.DialogInterface.OnClickListener(){
        @Override
        public void onClick(DialogInterface dialog,int which) {
            print = true;
            timepicker.dismiss();           
        }
});

timepicker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new 
    android.content.DialogInterface.OnClickListener(){
        @Override
        public void onClick(DialogInterface dialog,int which){
            print = false;
            timepicker.dismiss();       
        }
});

timepicker.setCancelable(false);
timepicker.show();
}
Crocodilo
fonte
Puro e simples, não funciona da maneira que você diz. timePickerListenerainda é 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 definir null, tryNotifyTimeSet()chamará o ouvinte onTimeSet()tanto no seu onClick()como no onStop().
Davidcsb 13/04
Em outras palavras, não permita que a classe nativa mantenha uma referência ao seu ouvinte (ou seja, passe-a no construtor). Como você lida com os botões é irrelevante.
Davidcsb
Oi David, Você não testou isso e supôs que não funcionaria. Este é o código exato que estou usando no meu aplicativo e funciona como um campeão.
Crocodile
1) Eu nunca disse que não funcionava. Eu disse que não funcionou como você disse. Por favor, leia novamente. 2) você violou LSP e SRP, desperdiçando horas-homem para desnecessariamente mudar toda a sua inteira lógica de cliente que não precisa quaisquer mudanças para começar. 3) sua resposta aborda a questão, sim, caso contrário, eu a sinalizaria para remoção como "não uma resposta", mas 4) sua resposta ainda é (desculpe-me) muito ineficiente e não aborda a questão fundamental do design (o ouvinte chamado ), portanto, apenas o voto negativo. 6) você desativou novamente e transformou em uma janela modal para forçar o booleano. Então, 6 problemas sérios lá!
Davidcsb