Jelly Bean DatePickerDialog - c'è un modo per annullare?


148

--- Nota per i moderatori: oggi (15 luglio), ho notato che qualcuno ha già affrontato questo problema qui . Ma non sono sicuro se sia appropriato chiudere questo come duplicato, poiché penso di aver fornito una spiegazione molto migliore del problema. Non sono sicuro se dovrei modificare l'altra domanda e incollare questo contenuto lì, ma non mi sento a mio agio nel cambiare troppo la domanda di qualcun altro. ---

Ho qualcosa di strano qui.

Non credo che il problema dipenda dall'SDK su cui si basa. La versione del sistema operativo del dispositivo è ciò che conta.

Problema n. 1: incoerenza per impostazione predefinita

DatePickerDialogè stato modificato (?) in Jelly Bean e ora fornisce solo un pulsante Fine . Le versioni precedenti includevano un pulsante Annulla e ciò potrebbe influire sull'esperienza dell'utente (incoerenza, memoria muscolare rispetto alle precedenti versioni di Android).

Replica: crea un progetto di base. Metti questo inonCreate:

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

Previsto: unpulsante Annulla verrà visualizzato nella finestra di dialogo.

Attuale: non viene visualizzatounpulsante Annulla .

Screenshot: 4.0.3 (OK) e 4.1.1 (forse sbagliato?).

Problema n. 2: comportamento di licenziamento errato

La finestra di dialogo chiama qualunque ascoltatore dovrebbe effettivamente chiamare e quindi chiama sempreOnDateSetListener listener. L'annullamento chiama ancora il metodo set e impostandolo chiama il metodo due volte.

Replica: usa il codice n. 1, ma aggiungi il codice di seguito (vedrai che risolve il problema n. 1, ma solo visivamente / UI):

picker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", 
        new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.d("Picker", "Cancel!");
            }
        });

Previsto:

  • Premendo il tasto INDIETRO o facendo clic all'esterno della finestra di dialogo non si dovrebbe fare nulla .
  • Premere "Annulla" per stampare Picker Annulla! .
  • Premendo "Set" si dovrebbe stampare Set di selezione! .

Attuale:

  • Premendo il tasto INDIETRO o facendo clic all'esterno della finestra di dialogo, si stampa Picker Set! .
  • Premendo "Annulla" si stampa Selettore Annulla! e poi Picker Set! .
  • Premendo "Set" si stampa Set di selezione! e poi Picker Set! .

Linee di registro che mostrano il 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!

Altre note e commenti

  • Avvolgerlo attorno a DatePickerFragmentnon ha importanza. Ho semplificato il problema per te, ma l'ho provato.

Congratulazioni, sembra che tu abbia trovato un bug in Android. Puoi segnalarlo qui .
Michael Hampton,

1
Segnalazione di bug scritta molto bene. Riesco a capirlo completamente senza dover testare il codice.
Cheok Yan Cheng,

6
Invito tutti a votare questa questione! Numero 34833
Bradley,

Non potresti semplicemente ignorare la funzione del pulsante per comportarti come se fosse stata ignorata a causa del tocco esterno alla finestra di dialogo?
Karthik Balakrishnan,

2
Il bug è ancora aperto dopo 2 anni ... incredibile.
Erdomester,

Risposte:


115

Nota: risolto a partire da Lollipop , fonte qui . Aggiornato anche il corso automatizzato per l'uso nei client (compatibile con tutte le versioni di Android).

TL; DR: 1-2-3 semplici passaggi morti per una soluzione globale:

  1. Scarica questa lezione.
  2. Implementa la OnDateSetListenertua attività (o cambia la classe in base alle tue esigenze).
  3. Attiva la finestra di dialogo con questo codice (in questo esempio, lo uso all'interno di 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 questo è tutto ciò che serve! Il motivo per cui continuo a mantenere la mia risposta come "accettata" è perché preferisco ancora la mia soluzione poiché ha un footprint molto ridotto nel codice client, risolve il problema fondamentale (l'ascoltatore viene chiamato nella classe framework), funziona bene attraverso le modifiche di configurazione e instrada la logica del codice all'implementazione predefinita nelle precedenti versioni di Android non afflitte da questo bug (vedi sorgente della classe).

Risposta originale (conservata per motivi storici e didattici):

Fonte di bug

OK, sembra che sia davvero un bug e qualcun altro lo abbia già riempito. Numero 34833 .

Ho scoperto che il problema potrebbe essere DatePickerDialog.java. Dove si legge:

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

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

Immagino che avrebbe potuto essere:

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

Ora, se qualcuno può dirmi come posso proporre un rapporto patch / bug ad Android, sarei felice di farlo. Nel frattempo, ho suggerito una possibile correzione (semplice) come una versione allegata del DatePickerDialog.javaproblema lì.

Concetto per evitare il bug

Imposta il listener su nullnel costruttore e crea il tuo BUTTON_POSITIVEpulsante in seguito . Questo è tutto, i dettagli di seguito.

Il problema si verifica perché DatePickerDialog.java, come puoi vedere nell'origine, chiama una variabile globale ( mCallBack) che memorizza il listener passato nel costruttore:

    /**
 * @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.
}

Quindi, il trucco è fornire un nullascoltatore da archiviare come ascoltatore e quindi ruotare il proprio set di pulsanti (di seguito è riportato il codice originale dal numero 1, aggiornato):

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

Ora funzionerà a causa della possibile correzione che ho pubblicato sopra.

E poiché DatePickerDialog.javacontrolla nullogni volta che legge mCallback( poiché i giorni di API 3 / 1.5 sembra --- non può controllare Honeycomb ovviamente), non attiverà l'eccezione. Considerando che Lollipop ha risolto il problema, non ho intenzione di esaminarlo: basta usare l'implementazione predefinita (coperta nella classe che ho fornito).

All'inizio avevo paura di non chiamare il clearFocus(), ma ho provato qui e le linee di registro erano pulite. Quindi quella linea che ho proposto potrebbe non essere nemmeno necessaria dopo tutto, ma non lo so.

Compatibilità con i precedenti livelli API (modificato)

Come ho indicato nel commento qui sotto, quello era un concetto e puoi scaricare la classe che sto usando dal mio account Google Drive . Il modo in cui ho usato, l'implementazione di sistema predefinita viene utilizzata su versioni non interessate dal bug.

Ho preso alcuni presupposti (nomi dei pulsanti, ecc.) Adatti alle mie esigenze perché volevo ridurre al minimo il codice del boilerplate nelle classi client. Esempio di utilizzo 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");

11
Ottimo lavoro sulla ricerca! Triste che fosse necessario, ma comunque fantastico.
CommonsWare,

1
Molto bella! Ho martellato la testa contro il muro su questo problema che stava aggiornando i miei campi di testo e chiamando / non chiamando i callback corretti. Grazie! Mi chiedo se la soluzione suggerita sia "a prova di futuro" o se pensi che causerà problemi quando il bug è stato risolto?
durata

1
@RomainGuidoux vedere la risposta aggiornata alla fine. La classe nel collegamento ha gli smart per chiamare quel metodo solo in jelly bean. Su qualsiasi cosa sotto lo ignorerà e utilizzerà l'implementazione di sistema predefinita, instradando la chiamata di sistema per ondateset alla tua attività. È solo che in Jelly Bean prende ulteriori misure (evitando il bug) prima di instradare il callback, e ciò comporta la chiamata di quel metodo a nido d'ape +. Ma di nuovo, solo in JB.
davidcsb,

1
Ottimo lavoro qui. Non ho le parole per quanto sia ridicolo questo problema.
Bill Phillips,

5
Che ne dici di TimePickerDialog? Sembra che TimePickerDialog non abbia getTimePicker ()
lokoko

16

Aggiungerò il mio riff sulla soluzione postata da David Cesarino, nel caso in cui non utilizzi Fragments e desideri un modo semplice per risolverlo in tutte le versioni (dalla 2.1 alla 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*/ }
  }
}

Ricorda solo: se stai cablando i nomi dei pulsanti per ok e per annullare, probabilmente è meglio usare lo standard android.R.string.oke i android.R.string.cancelcampi, anziché quelli dell'utente. E grazie per la risposta.
davidcsb,

Ah, bello, non ho notato che hanno fornito il testo OK e Annulla. Grazie!
dmon

ottenere l'eccezione nullpointer su super (contesto, tema, null, dateToShow.get (YEAR), dateToShow.get (MONTH), dateToShow.get (DAY_OF_MONTH));
Kishore,

Errr ... stai passando un null dateToShow? L'altro null è in realtà la "correzione", quindi dovrebbe essere lì. In quale versione sei?
Dmon

puoi per favore farmi sapere per cosa importavevi Field? Sto avendo 6 opzioni e nessuno di loro ha funzionato.
Adil Malik,

8

Fino a quando il bug non verrà corretto, suggerisco di non utilizzare DatePickerDialog o TimePickerDialog. Usa AlertDialog personalizzato con il widget TimePicker / DatePicker;

Cambia TimePickerDialog con;

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

Cambia DatePickerDialog con;

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

4

Quello per TimePicker basato sulla soluzione di David Cesarino, "TL; DR: 1-2-3 passi facili e morti per una soluzione globale"

TimePickerDialog non fornisce funzionalità come DatePickerDialog.getDatePicker. Pertanto, è necessario fornire il listener OnTimeSetListener . Solo per mantenere la somiglianza con la soluzione alternativa di DatePicker, ho mantenuto il vecchio concetto di mListener. Puoi cambiarlo se necessario.

Calling and Listener è uguale alla soluzione originale. Includi solo

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

estendere la classe genitore,

... implements OnDateSetListener, OnTimeSetListener

Strumento

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

esempio di chiamata

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

(Aggiornato per gestire l'annullamento)

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

Non funziona per me quando lo
provo

3

Nel caso qualcuno voglia una soluzione rapida, ecco il codice che ho usato:

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

}

Dove layout.date_picker_view è una semplice risorsa di layout con un DatePicker in quanto è solo un 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"/>

Ecco il tutorial completo nel caso ti interessi.


Questo ha funzionato meravigliosamente per me. Facile da capire, facile da implementare! Testato il 4.4
erdomester il

3

La mia semplice soluzione. Quando vuoi riaccenderlo, esegui "resetFired" (ad esempio quando apri di nuovo la finestra di dialogo).

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

@AbdelHady la modifica del codice non è corretta ma l'ho modificata per renderlo più chiaro. Non è necessario aggiungere un metodo "isJellyBeanOrAbove ()", non vi è alcun vantaggio, aggiunge solo complessità non necessaria. Chiamare resetFired è economico.
Daniel Ryan,

il codice qui non è corretto, perché onDateSetverrà chiamato una volta quando la finestra di dialogo viene chiusa in qualsiasi modo, e se quel mezzo doveva impostare l'ora, verrà attivato di nuovo, quindi dobbiamo prendere la seconda chiamata, non la prima come hai fatto tu,
AbdelHady,

Per quanto riguarda isJellyBeanOrAbove(), le versioni precedenti a Jellybean non hanno il bug che riguarda l'intera questione, e considerando che vogliamo catturare la seconda chiamata, il codice non verrà eseguito se non eseguiamo questo controllo, credimi, ho provato il mio codice su emulatori e dispositivi reali (con versioni diverse) più volte e funziona come un incantesimo
AbdelHady

Non sono sicuro che valga la pena di votare. Ho pubblicato questo 2 anni fa, questo è stato nella nostra applicazione commerciale da allora funzionava bene. Nessun bug segnalato dai nostri team di QA o dalle nostre migliaia di utenti. Questa vuole essere una risposta semplice che le persone possono estendere. Chiamare "resetFired" quando si desidera che si attivi nuovamente.
Daniel Ryan,

Nella nostra app "isJellyBeanOrAbove" non è necessario. L'app funzionerà perfettamente in tutte le versioni se chiami "resetFired" nelle aree corrette.
Daniel Ryan,

3

Secondo la brillante risposta di Ankur Chaudhary sul TimePickerDialogproblema simile , se avessimo verificato onDateSetse la vista data isShown()o no, avrebbe risolto l'intero problema con il minimo sforzo, senza la necessità di estendere il selettore o di verificare alcune orribili bandiere che giravano intorno al codice o anche controllando la versione del sistema operativo, procedi come segue:

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

e ovviamente lo stesso si può fare per onTimeSetla risposta di Ankur


1
La migliore risposta tra tutte le altre!
hiew1,

2

Il modo in cui ho gestito questa situazione è stato l'utilizzo di una bandiera e l'override dei metodi onCancel e onDismiss.

onCancel viene chiamato solo quando l'utente tocca al di fuori della finestra di dialogo o il pulsante Indietro. onDismiss viene sempre chiamato

L'impostazione di un flag nel metodo onCancel può aiutare a filtrare nel metodo onDismiss l'intenzione dell'utente: annullare l'azione o l'azione eseguita. Di seguito un codice che mostra l'idea.

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

1

Esiste una soluzione molto semplice, se l'applicazione non utilizza la barra delle azioni. Si noti che alcune app si basano su questa funzionalità per funzionare, poiché l'annullamento del selettore della data ha un significato speciale (ad esempio cancella il campo della data in una stringa vuota, che per alcune app è un tipo di input valido e significativo ) e l'utilizzo di flag booleani per impedire che la data venga impostata due volte su OK non ti aiuterà in questo caso.

Ri. la correzione effettiva, non è necessario creare nuovi pulsanti o la propria finestra di dialogo. Il punto è essere compatibili con entrambe, le versioni precedenti di Android, quelle buggy (4. ) e quelle future, anche se quest'ultima è impossibile esserne certi, ovviamente. Nota che in Android 2. , onStop () per android.app.Dialog non fa nulla, e in 4. * fa mActionBar.setShowHideAnimationEnabled (false) che è importante solo se la tua app ha una barra di azione. OnStop () in DatePickerDialog, che eredita da Dialog, contribuisce solo con mDatePicker.clearFocus () (come l'ultima correzione per Android 4.3), che non sembra essenziale.

Pertanto, la sostituzione di onStop () con un metodo che non fa nulla dovrebbe in molti casi riparare l'app e assicurarsi che rimanga tale per il prossimo futuro. Quindi estendi semplicemente la classe DatePickerDialog con il tuo e sostituisci onStop () con un metodo fittizio. Dovrai anche fornire uno o due costruttori, secondo le tue esigenze. Si noti inoltre che non si dovrebbe essere tentati di provare a esagerare con questa correzione, ad esempio tentando di fare qualcosa direttamente con la barra delle attività, poiché ciò limiterebbe la compatibilità solo alle ultime versioni di Android. Inoltre, sarebbe bello poter chiamare il super per onStop () di DatePicker perché il bug si trova solo su onStop () in DatePickerDialog stesso, ma non nella superclasse di DatePickerDialog. Tuttavia, ciò richiederebbe di chiamare super.super.onStop () dalla tua classe personalizzata, che Java non ti lascerà fare, in quanto va contro la filosofia dell'incapsulamento :) Di seguito è la mia piccola classe che ho usato per mostrare DatePickerDialog. Spero che questo commento sia utile per qualcuno. 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.
}   

}


Anche se viene utilizzata una ActionBar, questa potrebbe essere una soluzione accettabile se non si nasconde / mostra mai la ActionBar. Per rendere le cose ancora più sicure, puoi ignorare OnStart per non fare nulla (anche le chiamate per l'animazione verrebbero sganciate in modo sicuro). E anche se lo nascondi / lo mostri, è solo l'animazione che è disabilitata.
Daniel,

0


Prova i concetti seguenti.

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


il metodo onDateSet () chiama due volte (se stai effettuando il check in emulator.it chiama due volte. Se stai usando un dispositivo reale, chiamerà correttamente una volta sola. Se stai usando l'emulatore, usa il contatore. Se stai lavorando in un dispositivo reale, allora ignora contatore variabile.Per il dispositivo reale funziona per me.)
quando l'utente fa clic sul pulsante in DatePickerDialog.
per questo dovresti mantenere un valore contatore e nulla fare quando il mothod chiama la prima volta ed eseguire l'operazione quando il metodo chiama la seconda volta.
Consulta i seguenti frammenti di codifica

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



Per aver annullato il dilalog di datepicker funziona per me. Per l'emulatore non funziona

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


Funziona per me su un dispositivo reale, ma per l'emulatore non funziona correttamente. Penso che sia un bug dell'emulatore Android.


0

Una soluzione semplice sarebbe usare un valore booleano per saltare la seconda corsa

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

Ancora una volta, proprio come ho detto all'altro ragazzo prima di te, questo non risolve il problema. Non voglio sembrare scortese, ma voi ragazzi dovreste testare la vostra soluzione prima di pubblicare qui ... solo per dimostrare il mio punto, usare il codice e lasciare che l'utente torni indietro per annullare la finestra di dialogo e vedere cosa intendo. La causa del bug sta onStopchiamando il metodo quando non dovrebbe ... sparare due volte è una conseguenza del bug.
davidcsb,

Se non fossi abbastanza chiaro, lasciami essere: premendo indietro per annullare le chiamate di dialogo onDateSet. Quindi, rotto.
davidcsb,

Grazie per aver mostrato l'errore. Modifico la risposta in modo che funzioni con il pulsante Indietro. La tua risposta funziona ma DatePicker dp = picker.getDatePicker (); non funziona con TimePickers poiché il metodo getTimePicker () non è stato aggiunto. Quindi questa sarebbe una risposta valida
Chamikaw,

Come stiamo cercando di saltare la seconda corsa? !!, l'ho provato più volte, quando si chiude la finestra di dialogo con qualsiasi mezzo onDateSetverrà chiamato una volta, ma quando si sceglie "fatto" o "imposta", verrà chiamato due volte. Pertanto dobbiamo saltare solo il primo, quindi se viene chiamato due volte e solo allora abbiamo la data corretta
AbdelHady

0

Puoi sovrascrivere onCancel () e utilizzare setOnDismissListener () per rilevare azioni negative dell'utente. E con un DatePickerDialog.BUTTON_POSITIVE sai che l'utente vuole impostare una nuova 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;
    }
});

quindi controlla setDate:

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

0

Ecco la mia classe di soluzione alternativa per DatePickerDialog sul pulsante Annulla e per abbandonarla con il pulsante Indietro. Copia e usa nello stile di DatePickerDialog (Poiché il listener è stateful, durante l'uso è necessario creare una nuova istanza, altrimenti è necessario più codice per farlo funzionare)

Uso:

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

}


0

Sto usando selettori di data, selettori di tempo e selettori di numeri. I selettori numerici chiamano onValueChanged ogni volta che l'utente seleziona un numero, prima che il selettore venga respinto, quindi avevo già una struttura come questa per fare qualcosa con il valore solo quando il selettore viene respinto:

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

L'ho esteso per impostare onClickListeners personalizzati per i miei pulsanti, con un argomento per vedere quale pulsante è stato fatto clic. Ora posso controllare quale pulsante è stato toccato prima di impostare il mio valore finale:

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 poi l'ho esteso per lavorare con i tipi di data e ora per i selettori di data e ora, nonché il tipo int per i selettori di numeri.

Ho pubblicato questo perché pensavo fosse più semplice di alcune delle soluzioni sopra, ma ora che ho incluso tutto il codice, immagino non sia molto più semplice! Ma si adatta perfettamente alla struttura che già avevo.

Aggiornamento per Lollipop: apparentemente questo errore non si verifica su tutti i dispositivi Android 4.1-4.4, perché ho ricevuto alcuni rapporti dagli utenti i cui selettori di data e ora non chiamavano i callback onDateSet e onTimeSet. E il bug è stato risolto ufficialmente in Android 5.0. Il mio approccio ha funzionato solo su dispositivi in ​​cui è presente il bug, perché i miei pulsanti personalizzati non hanno chiamato il gestore onClick della finestra di dialogo, che è l'unico posto in cui vengono chiamati onDateSet e onTimeSet quando il bug non è presente. Ho aggiornato il mio codice sopra per chiamare la finestra di dialogo onClick, quindi ora funziona indipendentemente dal fatto che il bug sia presente.


0

Mi è piaciuta la risposta di David Cesarino sopra, ma volevo qualcosa che fosse un calo in sostituzione della finestra di dialogo interrotta e avrebbe funzionato su qualsiasi finestra di dialogo che potesse mancare di annullamento / avere un comportamento di annullamento errato. Ecco le classi derivate per DatePickerDialog / TimePickerDialog che dovrebbero funzionare come drop nelle sostituzioni. Queste non sono visualizzazioni personalizzate. Utilizza la finestra di dialogo del sistema, ma modifica semplicemente il comportamento del pulsante Annulla / Indietro per funzionare come previsto.

Questo dovrebbe funzionare a livello di API 3 e superiore. Quindi, praticamente qualsiasi versione di Android (l'ho testata su jellybean e lollipop in particolare).

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

Questo è abbastanza simile alla risposta di The Sea sopra.
Stuckj,

0

La mia versione funzionante con ClearButton usando Lambda Expressions:

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

0

Per TimePickerDialog la soluzione alternativa può essere la seguente:

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

Delego tutti gli eventi al wrapper KitKatSetTimeListener e rispondo all'OnTimeSetListener originale solo nel caso in cui si fa clic su BUTTON_POSITIVE.


0

Dopo aver testato alcune delle sugestioni pubblicate qui, penso personalmente che questa soluzione sia la più semplice. Passo "null" come listener nel costruttore DatePickerDialog e quindi quando faccio clic sul pulsante "OK" chiamo il mio 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();
        }
    });

-1

So che questo post è qui da quasi un anno, ma ho pensato di pubblicare i miei risultati. Potresti comunque mantenere l'ascoltatore (invece di impostarlo su rimuginare) e continuare a lavorare come previsto. La chiave è impostare implicitamente i pulsanti "OK" o (e) "annulla". L'ho provato e funziona con gratitudine per me. L'ascoltatore non viene licenziato due volte.

Guarda questo esempio,

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

Chiaro e semplice, non funziona come dici tu. timePickerListenerviene comunque chiamato indipendentemente da ciò che fai nella finestra di dialogo. Non ho nemmeno bisogno di testare per saperlo, devi solo guardare le fonti : se non lo imposti null, tryNotifyTimeSet()chiamerà l'ascoltatore onTimeSet()sia nel suo onClick()sia onStop().
davidcsb,

In altre parole, non lasciare che la classe nativa mantenga un riferimento al tuo ascoltatore (ovvero, passandolo nel costruttore). Il modo in cui gestisci i pulsanti è irrilevante.
davidcsb,

Ciao David, non l'hai provato e hai pensato che non funzionasse. Questo è esattamente il codice che sto attualmente usando nella mia app e funziona come un campione.
Coccodrillo

1) Non ho mai detto che non funzionasse. Ho detto che non ha funzionato come hai detto tu. Per favore, rileggi. 2) hai violato LSP e SRP, sprecando ore lavorative per modificare inutilmente tutta la logica dell'intero client che non aveva bisogno di modifiche per cominciare. 3) la tua risposta affronta la domanda, sì, altrimenti la contrassegnerei per la rimozione come "non una risposta", ma 4) la tua risposta è ancora (mi dispiace per questo) molto inefficiente e non affronta il problema di progettazione fondamentale (ascoltatore chiamato ), quindi solo il downvote. 6) hai disabilitato e reso una finestra modale per forzare il booleano. Quindi, 6 gravi problemi lì!
davidcsb,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.