Jelly Bean DatePickerDialog - gibt es eine Möglichkeit abzubrechen?


148

--- Hinweis Moderatoren: Heute (15. Juli), ich habe bemerkt , dass jemand bereits mit diesem Problem konfrontiert hier . Ich bin mir jedoch nicht sicher, ob es angemessen ist, dies als Duplikat zu schließen, da ich denke, dass ich das Problem viel besser erklärt habe. Ich bin mir nicht sicher, ob ich die andere Frage bearbeiten und diesen Inhalt dort einfügen soll, aber ich fühle mich nicht wohl, wenn ich die Frage eines anderen zu sehr ändere. --- ---.

Ich habe hier etwas Seltsames .

Ich glaube nicht, dass das Problem davon abhängt, gegen welches SDK Sie bauen. Auf die Betriebssystemversion des Geräts kommt es an.

Problem Nr. 1: Standardmäßig Inkonsistenz

DatePickerDialogwurde in Jelly Bean geändert (?) und bietet jetzt nur noch eine Schaltfläche " Fertig" . Frühere Versionen enthielten eine Schaltfläche Abbrechen. Dies kann sich auf die Benutzererfahrung auswirken (Inkonsistenz, Muskelgedächtnis aus früheren Android-Versionen).

Replizieren: Erstellen Sie ein Basisprojekt. Geben Sie dies einonCreate:

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

Erwartet: EineSchaltfläche Abbrechen wird im Dialogfeld angezeigt.

Aktuell: EineSchaltfläche Abbrechen wird nicht angezeigt.

Screenshots: 4.0.3 (OK) und 4.1.1 (möglicherweise falsch?).

Problem Nr. 2: Falsches Entlassungsverhalten

Der Dialog ruft den Listener auf, den er tatsächlich aufrufen soll, und ruft dann immer den OnDateSetListenerListener an. Beim Abbrechen wird die set-Methode weiterhin aufgerufen, und beim Festlegen wird die Methode zweimal aufgerufen.

Replizieren: Verwenden Sie den Code Nr. 1, fügen Sie jedoch den folgenden Code hinzu (Sie werden sehen, dass dies den ersten Code löst, jedoch nur visuell / Benutzeroberfläche):

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

Erwartet:

  • Das Drücken der Taste ZURÜCK oder das Klicken außerhalb des Dialogfelds sollte nichts bewirken .
  • Durch Drücken von "Abbrechen" wird Picker Abbrechen gedruckt ! .
  • Durch Drücken von "Set" sollte Picker Set gedruckt werden ! .

Aktuell:

  • Durch Drücken der BACK-Taste oder Klicken außerhalb des Dialogfelds wird Picker Set! .
  • Durch Drücken von "Abbrechen" wird die Auswahl abgebrochen. Abbrechen! und dann Picker Set! .
  • Durch Drücken von "Set" wird das Picker Set gedruckt ! und dann Picker Set! .

Protokollzeilen, die das Verhalten zeigen:

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!

Andere Notizen und Kommentare

  • Es DatePickerFragmentspielt keine Rolle, es um ein zu wickeln. Ich habe das Problem für Sie vereinfacht, aber ich habe es getestet.

Herzlichen Glückwunsch, Sie scheinen einen Fehler in Android gefunden zu haben. Sie können es hier melden .
Michael Hampton

1
Sehr gut geschriebener Fehlerbericht. Ich kann es vollständig verstehen, ohne den Code testen zu müssen.
Cheok Yan Cheng

6
Ich fordere alle auf, dieses Thema abzustimmen! Ausgabe 34833
Bradley

Könnten Sie die Tastenfunktion nicht einfach überschreiben, um so zu wirken, als wäre sie aufgrund einer Berührung außerhalb des Dialogfelds geschlossen worden?
Karthik Balakrishnan

2
Bug ist nach 2 Jahren noch offen ... unglaublich.
Erdomester

Antworten:


115

Hinweis: Ab Lollipop behoben , Quelle hier . Automatisierte Klasse zur Verwendung in Clients (kompatibel mit allen Android-Versionen) ebenfalls aktualisiert.

TL; DR: 1-2-3 absolut einfache Schritte für eine globale Lösung:

  1. Laden Sie diese Klasse herunter .
  2. Implementieren Sie OnDateSetListenerin Ihrer Aktivität (oder ändern Sie die Klasse entsprechend Ihren Anforderungen).
  3. Lösen Sie den Dialog mit diesem Code aus (in diesem Beispiel verwende ich ihn in 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");
    

Und das ist alles was es braucht! Der Grund, warum ich meine Antwort immer noch als "akzeptiert" behalte, ist, dass ich meine Lösung immer noch bevorzuge, da sie einen sehr geringen Platzbedarf im Clientcode hat, das grundlegende Problem behebt (der Listener wird in der Framework-Klasse aufgerufen) und bei Konfigurationsänderungen einwandfrei funktioniert und es leitet die Codelogik an die Standardimplementierung in früheren Android-Versionen weiter, die nicht von diesem Fehler betroffen sind (siehe Klassenquelle).

Ursprüngliche Antwort (aus historischen und didaktischen Gründen aufbewahrt):

Fehlerquelle

OK, es sieht tatsächlich so aus, als wäre es tatsächlich ein Fehler, und jemand anderes hat ihn bereits gefüllt. Ausgabe 34833 .

Ich habe festgestellt, dass das Problem möglicherweise in liegt DatePickerDialog.java. Wo es liest:

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

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

Ich würde vermuten, dass es gewesen sein könnte:

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

Wenn mir jetzt jemand sagen kann, wie ich Android einen Patch- / Fehlerbericht vorschlagen kann, würde ich mich freuen. In der Zwischenzeit schlug ich eine mögliche Lösung (einfach) als angehängte Version der DatePickerDialog.javadortigen Ausgabe vor.

Konzept, um den Fehler zu vermeiden

Stellen Sie den Listener nullim Konstruktor auf ein und erstellen Sie BUTTON_POSITIVEspäter Ihre eigene Schaltfläche . Das war's, Details unten.

Das Problem tritt auf, weil DatePickerDialog.java, wie Sie in der Quelle sehen können, eine globale Variable ( mCallBack) aufgerufen wird, die den Listener speichert, der im Konstruktor übergeben wurde:

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

Der Trick besteht also darin, einen nullListener bereitzustellen, der als Listener gespeichert werden soll, und dann Ihre eigenen Schaltflächen zu rollen (unten ist der Originalcode von Nr. 1, aktualisiert):

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

Jetzt wird es aufgrund der möglichen Korrektur funktionieren, die ich oben gepostet habe.

Und da DatePickerDialog.javabei nulljedem Lesen nach a gesucht wird mCallback( seit den Tagen von API 3 / 1.5 scheint es, dass Honeycomb natürlich nicht überprüft werden kann), wird die Ausnahme nicht ausgelöst. In Anbetracht der Tatsache, dass Lollipop das Problem behoben hat, werde ich mich nicht damit befassen: Verwenden Sie einfach die Standardimplementierung (die in der von mir bereitgestellten Klasse behandelt wird).

Zuerst hatte ich Angst, das nicht anzurufen clearFocus(), aber ich habe hier getestet und die Protokollleitungen waren sauber. Diese Linie, die ich vorgeschlagen habe, ist vielleicht doch nicht nötig, aber ich weiß es nicht.

Kompatibilität mit früheren API-Levels (bearbeitet)

Wie ich im Kommentar unten ausgeführt habe, war dies ein Konzept, und Sie können die von mir verwendete Klasse von meinem Google Drive-Konto herunterladen . So wie ich es verwendet habe, wird die Standardsystemimplementierung für Versionen verwendet, die vom Fehler nicht betroffen sind.

Ich habe einige Annahmen (Schaltflächennamen usw.) getroffen, die für meine Anforderungen geeignet sind, weil ich den Code für Boilerplates in Clientklassen auf ein Minimum reduzieren wollte. Beispiel für die vollständige Verwendung:

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
Super Arbeit bei der Recherche! Traurig, dass es notwendig war, aber trotzdem großartig.
CommonsWare

1
Sehr schön! Ich habe bei diesem Problem, bei dem meine Textfelder aktualisiert und die richtigen Rückrufe aufgerufen / nicht aufgerufen wurden, meinen Kopf gegen die Wand gehämmert. Danke dir! Ich frage mich, ob die vorgeschlagene Problemumgehung "zukunftssicher" ist oder ob Sie glauben, dass sie Probleme verursachen wird, wenn der Fehler behoben ist.
Spanne

1
@RomainGuidoux siehe aktualisierte Antwort am Ende. Die Klasse im Link hat die Intelligenz, diese Methode nur in Jelly Bean aufzurufen. Bei allen folgenden Schritten wird dies umgangen und die Standardsystemimplementierung verwendet, wobei der Systemaufruf für das Datumssatz an Ihre Aktivität weitergeleitet wird. Es ist nur so, dass in Jelly Bean zusätzliche Maßnahmen ergriffen werden (um den Fehler zu vermeiden), bevor der Rückruf weitergeleitet wird, und dazu muss diese Honeycomb + -Methode aufgerufen werden. Aber auch hier nur in JB.
Davidcsb

1
Tolle Arbeit hier. Ich habe keine Worte dafür, wie lächerlich dieses Problem ist.
Bill Phillips

5
Wie wäre es mit TimePickerDialog? Es scheint, dass TimePickerDialog keinen getTimePicker () hat
lokoko

16

Ich werde mein eigenes Riff zu der von David Cesarino veröffentlichten Lösung hinzufügen, falls Sie keine Fragmente verwenden, und eine einfache Möglichkeit suchen, dies in allen Versionen (2.1 bis 4.1) zu beheben:

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

Denken Sie nur daran: Wenn Sie die Schaltflächennamen fest verdrahten, um sie in Ordnung zu bringen und abzubrechen, ist es wahrscheinlich besser, den Standard android.R.string.okund die android.R.string.cancelFelder anstelle der eigenen zu verwenden. Und danke für die Antwort.
Davidcsb

Ah, schön, ich habe nicht bemerkt, dass sie OK bereitgestellt und Text abgebrochen haben. Vielen Dank!
dmon

Nullpointer-Ausnahme bei Super abrufen (Kontext, Thema, Null, DatumToShow.get (JAHR), DatumToShow.get (MONAT), DatumToShow.get (DAY_OF_MONTH));
Kishore

Ähm ... übergeben Sie eine Null dateToShow? Die andere Null dort ist eigentlich das "Update", also sollte das da sein. Auf welcher Version bist du?
dmon

Kannst du mich bitte wissen lassen, wofür importdu hattest Field? Ich habe 6 Optionen und keine davon hat funktioniert.
Adil Malik

8

Bis der Fehler behoben ist, empfehle ich, DatePickerDialog oder TimePickerDialog nicht zu verwenden. Verwenden Sie einen benutzerdefinierten AlertDialog mit dem TimePicker / DatePicker-Widget.

Ändern Sie TimePickerDialog mit;

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

Ändern Sie DatePickerDialog mit;

    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

Die für TimePicker basierend auf der Lösung von David Cesarino, "TL; DR: 1-2-3 absolut einfache Schritte für eine globale Lösung"

TimePickerDialog bietet keine Funktionen wie DatePickerDialog.getDatePicker. Daher muss der OnTimeSetListener- Listener bereitgestellt werden. Um die Ähnlichkeit mit der DatePicker-Problemumgehungslösung beizubehalten, habe ich das alte mListener-Konzept beibehalten. Sie können es bei Bedarf ändern.

Calling and Listener entspricht der ursprünglichen Lösung. Einfach einschließen

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

Elternklasse erweitern,

... implements OnDateSetListener, OnTimeSetListener

Implementieren

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

Beispiel für einen Anruf

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

(Aktualisiert, um Abbrechen zu behandeln)

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

Es funktioniert nicht für mich, wenn ich es auf s3 API 18
AbdelHady

3

Für den Fall, dass jemand eine schnelle Problemumgehung wünscht, ist hier der Code, den ich verwendet habe:

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

}}

Wobei layout.date_picker_view eine einfache Layoutressource mit einem DatePicker als einzigem Element ist:

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

Hier ist das vollständige Tutorial, falls Sie interessiert sind.


Das hat bei mir wunderbar funktioniert. Leicht zu verstehen, leicht zu implementieren! Getestet am 4.4
erdomester

3

Meine einfache Lösung. Wenn Sie es erneut auslösen möchten, führen Sie einfach "resetFired" aus (z. B. beim erneuten Öffnen des Dialogfelds).

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 Ihre Code-Bearbeitung war falsch, aber ich habe sie bearbeitet, um es klarer zu machen. Es ist nicht erforderlich, eine "isJellyBeanOrAbove ()" - Methode hinzuzufügen. Es gibt keinen Vorteil, sondern nur unnötige Komplexität. ResetFired aufzurufen ist billig.
Daniel Ryan

Der Code hier ist falsch, da er onDateSeteinmal aufgerufen wird, wenn der Dialog auf irgendeine Weise geschlossen wird. Wenn dieser Mittelwert die Zeit einstellen sollte, wird er erneut ausgelöst, sodass wir den zweiten Anruf abfangen müssen, nicht den ersten wie Sie,
AbdelHady

In Bezug auf isJellyBeanOrAbove()Versionen, die niedriger als Jellybean sind, gibt es nicht den Fehler, um den es bei dieser ganzen Frage geht. Wenn wir den zweiten Aufruf abfangen möchten, wird der Code erst ausgeführt, wenn wir diese Prüfung durchführen. Glauben Sie mir, ich habe es versucht Code auf Emulatoren & realen Geräten (mit verschiedenen Versionen) mehrmals & es funktioniert wie ein Zauber
AbdelHady

Ich bin mir nicht sicher, ob das eine Abwertung wert ist. Ich habe dies vor 2 Jahren gepostet, dies funktioniert seitdem in unserer kommerziellen Anwendung einwandfrei. Keine von unseren QA-Teams oder unseren Tausenden von Benutzern gemeldeten Fehler. Dies soll eine einfache Antwort sein, die Menschen erweitern können. Rufen Sie "resetFired" auf, wenn Sie möchten, dass es erneut ausgelöst wird.
Daniel Ryan

In unserer App wird "isJellyBeanOrAbove" nicht benötigt. Die App funktioniert in allen Versionen einwandfrei, wenn Sie in den richtigen Bereichen "resetFired" aufrufen.
Daniel Ryan

3

Laut Ankur Chaudharys brillanter Antwort zu diesem ähnlichen TimePickerDialogProblem wird das gesamte Problem mit minimalem Aufwand gelöst , wenn wir nach innen schauen, onDateSetob die angegebene Ansicht isShown()vorliegt oder nicht, ohne dass die Auswahl erweitert oder nach abscheulichen Flaggen gesucht werden muss, die den Code umgehen oder suchen Sie nach der Betriebssystemversion, gehen Sie einfach wie folgt vor:

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

und natürlich kann das Gleiche onTimeSetgemäß Ankur's Antwort getan werden


1
Beste Antwort von allen anderen!
hiew1

2

Die Art und Weise, wie ich mit dieser Situation umgegangen bin, bestand darin, ein Flag zu verwenden und die Methoden onCancel und onDismiss zu überschreiben.

onCancel wird nur aufgerufen, wenn der Benutzer außerhalb des Dialogfelds oder der Zurück-Schaltfläche berührt. onDismiss wird immer aufgerufen

Das Setzen eines Flags in der onCancel-Methode kann dazu beitragen, in der onDismiss-Methode die Absicht des Benutzers zu filtern: Aktion abbrechen oder Aktion ausführen. Unten ein Code, der die Idee zeigt.

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

Es gibt eine sehr einfache Problemumgehung, wenn Ihre Anwendung die Aktionsleiste nicht verwendet. Beachten Sie übrigens, dass einige Apps auf diese Funktion angewiesen sind, da das Abbrechen der Datumsauswahl eine besondere Bedeutung hat (z. B. wird das Datumsfeld durch eine leere Zeichenfolge gelöscht, was für einige Apps eine gültige und aussagekräftige Art der Eingabe ist ) und die Verwendung von Booleschen Flags, um zu verhindern, dass das Datum zweimal auf OK gesetzt wird, hilft Ihnen in diesem Fall nicht weiter.

Re. Für die eigentliche Korrektur müssen Sie keine neuen Schaltflächen oder einen eigenen Dialog erstellen. Es geht darum, sowohl mit den älteren Versionen von Android als auch mit den fehlerhaften (4. ) und zukünftigen Versionen kompatibel zu sein, wobei letzteres natürlich nicht sicher ist. Beachten Sie, dass in Android 2. onStop () für android.app.Dialog überhaupt nichts tut und in 4. * mActionBar.setShowHideAnimationEnabled (false), was nur wichtig ist, wenn Ihre App über eine Aktionsleiste verfügt. Das onStop () in DatePickerDialog, das von Dialog erbt, trägt nur mDatePicker.clearFocus () bei (Stand der neuesten Korrektur für Android-Quellen 4.3), was nicht unbedingt erforderlich erscheint.

Daher sollte das Ersetzen von onStop () durch eine Methode, die nichts bewirkt, in vielen Fällen Ihre App reparieren und sicherstellen, dass dies auf absehbare Zeit so bleibt. Erweitern Sie daher einfach die DatePickerDialog-Klasse mit Ihrer eigenen und überschreiben Sie onStop () mit einer Dummy-Methode. Sie müssen auch einen oder zwei Konstruktoren gemäß Ihren Anforderungen bereitstellen. Beachten Sie auch, dass Sie nicht versucht sein sollten, dieses Update zu übertreiben, indem Sie beispielsweise versuchen, etwas direkt mit der Aktivitätsleiste zu tun, da dies Ihre Kompatibilität nur mit den neuesten Versionen von Android einschränken würde. Beachten Sie auch, dass es schön wäre, das Super für DateStickers onStop () aufrufen zu können, da der Fehler nur in onStop () in DatePickerDialog selbst, nicht jedoch in DatePickerDialogs Superklasse auftritt. Dies würde jedoch erfordern, dass Sie super.super.onStop () aus Ihrer benutzerdefinierten Klasse aufrufen. was Java nicht zulässt, da es gegen die Kapselungsphilosophie verstößt :) Unten ist meine kleine Klasse, mit der ich DatePickerDialog verifiziert habe. Ich hoffe, dieser Kommentar wäre für jemanden nützlich. 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.
}   

}}


Selbst wenn eine ActionBar verwendet wird, kann dies eine akzeptable Problemumgehung sein, wenn Sie die ActionBar niemals ausblenden / einblenden. Um die Sicherheit zu erhöhen, können Sie onStart überschreiben, um auch nichts zu tun (dann würden die Aufrufe für die Animation sicher ausgehängt). Und selbst wenn Sie es ausblenden / einblenden, ist nur die Animation deaktiviert.
Daniel

0


Probieren Sie die folgenden Konzepte aus.

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


Die onDateSet () -Methode ruft zweimal auf (wenn Sie emulator einchecken. Es wird zweimal aufgerufen. Wenn Sie ein reales Gerät verwenden, wird es einmal korrekt aufgerufen. Wenn Sie einen Emulator verwenden, verwenden Sie den Zähler. Wenn Sie in einem realen Gerät arbeiten, dann
Zählervariable ignorieren. Für echte Geräte funktioniert es für mich.) Wenn der Benutzer auf die Schaltfläche in DatePickerDialog klickt.
Dazu sollten Sie einen Zählerwert beibehalten und nichts tun, wenn die Motte das erste Mal aufruft, und die Operation ausführen, wenn die Methode das zweite Mal aufruft.
Beachten Sie die folgenden Codierungsausschnitte

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



Zum Abbrechen des Datepicker-Dilalogs funktioniert es für mich. Für den Emulator ist es kein Wokring

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


Es funktioniert für mich für ein echtes Gerät. Aber für den Emulator funktioniert es nicht richtig. Ich denke, es ist ein Android-Emulator-Fehler.


0

Eine einfache Lösung wäre die Verwendung eines Booleschen Werts, um den zweiten Lauf zu überspringen

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

Wieder einmal, so wie ich den anderen Kerl gesagt , bevor Sie, bedeutet dies nicht den Fehler lösen. Ich möchte nicht unhöflich klingen, aber ihr solltet eure eigene Lösung testen, bevor ihr hier postet ... nur um meinen Standpunkt zu beweisen, verwendet euren Code und lasst den Benutzer zurück drücken, um den Dialog abzubrechen und zu sehen, was ich meine. Die Ursache des Fehlers ist das onStopAufrufen der Methode, wenn dies nicht der Fall sein sollte. Das zweimalige Auslösen ist eine Folge des Fehlers.
Davidcsb

Wenn ich nicht klar genug war, lassen Sie mich sein: Zurück drücken, um die Dialogaufrufe abzubrechen onDateSet. Also kaputt.
Davidcsb

Vielen Dank, dass Sie den Fehler angezeigt haben. Ich bearbeite die Antwort so, dass sie mit der Zurück-Schaltfläche funktioniert. Ihre Antwort funktioniert, aber DatePicker dp = picker.getDatePicker (); funktioniert nicht mit TimePickers, da die Methode getTimePicker () nicht hinzugefügt wurde. Das wäre also eine gültige Antwort
Chamikaw

Wie wir versuchen, den zweiten Lauf zu überspringen? !!, ich habe es mehrmals versucht, wenn das Schließen des Dialogs auf irgendeine Weise onDateSeteinmal aufgerufen wird, aber wenn Sie "erledigt" oder "setzen" wählen, wird es zweimal aufgerufen. Daher müssen wir nur das erste überspringen. Wenn es also zweimal aufgerufen wird, haben wir nur dann das richtige Datum
AbdelHady

0

Sie können onCancel () überschreiben und setOnDismissListener () verwenden, um negative Benutzeraktionen zu erkennen. Und mit einem DatePickerDialog.BUTTON_POSITIVE wissen Sie, dass der Benutzer ein neues Datum festlegen möchte.

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

dann auf setDate prüfen:

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

0

Hier ist meine Problemumgehungsklasse für DatePickerDialog auf der Schaltfläche Abbrechen sowie auf der Schaltfläche Zurück. Kopieren und Verwenden im Stil von DatePickerDialog (Da der Listener statusbehaftet ist, müssen wir bei der Verwendung eine neue Instanz erstellen, da sonst mehr Code erforderlich ist, damit es funktioniert.)

Verwenden:

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

Klasse:

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

Ich benutze Datums-, Zeit- und Nummernwähler. Die Nummernwähler rufen onValueChanged immer dann auf, wenn der Benutzer eine Nummer auswählt, bevor der Picker entlassen wird. Daher hatte ich bereits eine solche Struktur, um nur dann etwas mit dem Wert zu tun, wenn der Picker entlassen wird:

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

Ich habe dies erweitert, um benutzerdefinierte onClickListener für meine Schaltflächen festzulegen, mit einem Argument, um zu sehen, auf welche Schaltfläche geklickt wurde. Jetzt kann ich überprüfen, welche Schaltfläche getippt wurde, bevor ich meinen Endwert einstelle:

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

Und dann habe ich das erweitert, um mit den Datums- und Zeittypen für Datums- und Zeitauswahl sowie dem Int-Typ für Nummernauswahl zu arbeiten.

Ich habe dies gepostet, weil ich dachte, es sei einfacher als einige der oben genannten Lösungen, aber jetzt, wo ich den gesamten Code eingefügt habe, ist es wohl nicht viel einfacher! Aber es passte gut in die Struktur, die ich bereits hatte.

Update für Lollipop: Anscheinend tritt dieser Fehler nicht auf allen Android 4.1-4.4-Geräten auf, da ich einige Berichte von Benutzern erhalten habe, deren Datums- und Zeitauswahl die Rückrufe onDateSet und onTimeSet nicht aufgerufen hat. Und der Fehler wurde offiziell in Android 5.0 behoben. Mein Ansatz funktionierte nur auf Geräten, auf denen der Fehler vorliegt, da meine benutzerdefinierten Schaltflächen den onClick-Handler des Dialogfelds nicht aufriefen. Dies ist der einzige Ort, an dem onDateSet und onTimeSet aufgerufen werden, wenn der Fehler nicht vorhanden ist. Ich habe meinen obigen Code aktualisiert, um den onClick des Dialogfelds aufzurufen. Jetzt funktioniert es, ob der Fehler vorliegt oder nicht.


0

Ich mochte die Antwort von David Cesarino oben, wollte aber etwas, das den defekten Dialog ersetzt und bei jedem Dialog funktioniert, bei dem möglicherweise ein Abbruch fehlt oder ein falsches Abbruchverhalten vorliegt. Hier sind abgeleitete Klassen für DatePickerDialog / TimePickerDialog, die als Drop-in-Ersetzungen funktionieren sollen. Dies sind keine benutzerdefinierten Ansichten. Es verwendet den Systemdialog, ändert jedoch nur das Verhalten der Schaltfläche Abbrechen / Zurück, um wie erwartet zu funktionieren.

Dies sollte auf API-Ebene 3 und höher funktionieren. Also im Grunde jede Version von Android (ich habe es speziell auf Jellybean und Lollipop getestet).

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

Dies ist der obigen Antwort von The Sea ziemlich ähnlich.
Stuckj

0

Meine Arbeitsversion mit ClearButton mit 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

Für TimePickerDialog kann die Problemumgehung wie folgt aussehen:

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

Ich delegiere alle Ereignisse an den Wrapper KitKatSetTimeListener und feuere nur dann auf den ursprünglichen OnTimeSetListener zurück, wenn auf BUTTON_POSITIVE geklickt wird.


0

Nachdem ich einige der hier veröffentlichten Vorschläge getestet habe, denke ich persönlich, dass diese Lösung die einfachste ist. Ich übergebe "null" als Listener im DatePickerDialog-Konstruktor. Wenn ich dann auf die Schaltfläche "OK" klicke, rufe ich meinen onDateSearchSetListener auf:

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

Ich weiß, dass dieser Beitrag seit fast einem Jahr hier ist, aber ich dachte, ich sollte meine Ergebnisse veröffentlichen. Sie können den Listener weiterhin behalten (anstatt ihn auf Mull zu setzen) und diese Arbeit wie erwartet ausführen. Der Schlüssel besteht darin, implizit die Tasten "OK" oder (und) "Abbrechen" zu setzen. Ich habe es getestet und es funktioniert dankbar für mich. Der Hörer wird nicht zweimal gefeuert.

Schauen Sie sich dieses Beispiel an:

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

Schlicht und einfach, es funktioniert nicht so, wie Sie es sagen. timePickerListenerwird immer noch aufgerufen, unabhängig davon, was Sie in Ihrem Dialog tun. Ich muss nicht einmal testen, um das zu wissen, Sie müssen sich nur die Quellen ansehen : Wenn Sie es nicht einstellen null, tryNotifyTimeSet()wird der Hörer onTimeSet()sowohl in seiner onClick()als auch in seiner Position aufgerufen onStop().
Davidcsb

Mit anderen Worten, nicht lassen die native Klasse einen Verweis auf Ihre Zuhörer halten (dh es in den Konstruktor übergeben). Wie Sie mit Schaltflächen umgehen, spielt keine Rolle.
Davidcsb

Hallo David, du hast das nicht getestet und angenommen, dass es nicht funktioniert. Dies ist genau der Code, den ich derzeit in meiner App verwende und der wie ein Champion funktioniert.
Krokodil

1) Ich habe nie gesagt, dass es nicht funktioniert. Ich sagte, es hat nicht so funktioniert, wie du es gesagt hast. Bitte lesen Sie noch einmal. 2) verletzt Sie LSP und SRP, verschwenden Mannstunden zu unnötig ändern alle Ihre gesamte Logik - Client, der keine Änderungen brauchte zu beginnen. 3) Ihre Antwort adressiert die Frage, ja, andernfalls würde ich sie zum Entfernen als "keine Antwort" kennzeichnen, aber 4) Ihre Antwort ist immer noch (sorry) sehr ineffizient und behandelt nicht das grundlegende Designproblem (Listener angerufen) ), daher nur die Gegenstimme. 6) Sie haben zurück deaktiviert und es zu einem modalen Fenster gemacht, um den Booleschen Wert zu erzwingen. Also 6 ernsthafte Probleme!
Davidcsb
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.