Jelly Bean DatePickerDialog — gibt es eine Möglichkeit, abzubrechen?
Lesezeit: 14 Minuten
davidcesarino
— Hinweis für Moderatoren: Heute (15.07.) ist mir aufgefallen, dass hier schon jemand dieses Problem hatte. Aber ich bin mir 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 dabei, die Frage eines anderen zu sehr zu ändern. —
ich habe etwas seltsam Hier.
Ich glaube nicht, dass das Problem davon abhängt, gegen welches SDK Sie bauen. Die Betriebssystemversion des Geräts ist entscheidend.
Problem Nr. 1: Inkonsistenz standardmäßig
DatePickerDialog wurde in Jelly Bean geändert (?) und bietet jetzt nur noch a Fertig Taste. Frühere Versionen enthielten a Abbrechen Schaltfläche, und dies kann die Benutzererfahrung beeinträchtigen (Inkonsistenz, Muskelgedächtnis von früheren Android-Versionen).
Replizieren: Erstellen Sie ein Basisprojekt. Gib das ein onCreate:
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: EIN Abbrechen Schaltfläche, die im Dialogfeld angezeigt wird.
Strom: EIN Abbrechen Schaltfläche erscheint nicht.
Screenshots:4.0.3 (OK und 4.1.1 (möglicherweise falsch?).
Problem Nr. 2: falsches Entlassungsverhalten
Dialog ruft den Zuhörer an, den er tatsächlich anrufen soll, und dann stets Anrufe OnDateSetListener Hörer. Das Abbrechen ruft immer noch die set-Methode auf, und das Setzen ruft die Methode zweimal auf.
Replizieren: Verwenden Sie Code Nr. 1, aber fügen Sie Code unten hinzu (Sie werden sehen, dass dies Nr. 1 löst, aber nur visuell/UI):
picker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d("Picker", "Cancel!");
}
});
Erwartet:
Drücken Sie die ZURÜCK-Taste oder klicken Sie außerhalb des Dialogfelds nichts tun.
Durch Drücken von “Abbrechen” sollte gedruckt werden Picker Abbrechen!.
Durch Drücken von “Set” sollte gedruckt werden Picker-Set!.
Strom:
Durch Drücken der ZURÜCK-Taste oder Klicken außerhalb des Dialogfelds wird gedruckt Picker-Set!.
Durch Drücken von „Abbrechen“ wird gedruckt Picker Abbrechen! und dann Picker-Set!.
Durch Drücken von „Einstellen“ wird gedruckt Picker-Set! und dann Picker-Set!.
Wickeln Sie es um a DatePickerFragment spielt keine Rolle. 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 melde es hier.
– Michael Hampton
15. Juli 12 um 15:41 Uhr
Sehr gut geschriebener Fehlerbericht. Ich kann es vollständig verstehen, ohne den Code testen zu müssen.
– Cheok Yan Cheng
10. September 12 um 1:46 Uhr
Ich fordere alle auf, dieses Thema abzustimmen! Ausgabe 34833
– Bradley
27. November 12 um 18:26 Uhr
Könnten Sie nicht einfach die Schaltflächenfunktion überschreiben, damit sie sich so verhält, als wäre sie aufgrund einer Berührung außerhalb des Dialogs geschlossen worden?
– Karthik Balakrishnan
4. Juni 13 um 6:38 Uhr
Bug ist nach 2 Jahren immer noch offen…unglaublich.
Implementieren OnDateSetListener in Ihrer Aktivität (oder ändern Sie die Klasse nach Ihren Bedürfnissen).
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 anspricht (der Listener wird in der Framework-Klasse aufgerufen) und über Konfigurationsänderungen hinweg einwandfrei funktioniert und es leitet die Codelogik an die Standardimplementierung in früheren Android-Versionen weiter, die nicht von diesem Fehler betroffen waren (siehe Klassenquelle).
Originalantwort (aus historischen und didaktischen Gründen aufbewahrt):
Fehlerquelle
OK, sieht so aus, als wäre es tatsächlich ein Fehler und jemand anderes hat ihn bereits ausgefüllt. Ausgabe 34833.
Ich habe festgestellt, dass das Problem möglicherweise darin liegt DatePickerDialog.java. Wo es heißt:
@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 einen Patch/Fehlerbericht für Android vorschlagen kann, würde ich mich freuen. In der Zwischenzeit habe ich eine mögliche Lösung (einfach) als angehängte Version von vorgeschlagen DatePickerDialog.java in der dortigen Ausgabe.
Konzept zur Vermeidung des Fehlers
Stellen Sie den Listener auf ein null im Konstruktor und erstellen Sie Ihre eigenen BUTTON_POSITIVE Taste später. Das war’s, Details unten.
Das Problem tritt auf, weil DatePickerDialog.javawie Sie in der Quelle sehen können, ruft eine globale Variable auf (mCallBack), die den im Konstruktor übergebenen Listener speichert:
/**
* @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, a bereitzustellen null listener, der als Listener gespeichert werden soll, und rollen Sie dann Ihren eigenen Satz von Schaltflächen (unten ist der Originalcode von #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 funktioniert es wegen der möglichen Korrektur, die ich oben gepostet habe.
Und da DatePickerDialog.java prüft auf a null wann immer es liest mCallback (seit den Tagen von API 3/1.5 scheint es — kann Honeycomb natürlich nicht überprüfen), wird die Ausnahme nicht ausgelöst. In Anbetracht der Tatsache, dass Lollipop das Problem behoben hat, werde ich es nicht untersuchen: Verwenden Sie einfach die Standardimplementierung (in der von mir bereitgestellten Klasse behandelt).
Zuerst hatte ich Angst, die nicht anzurufen clearFocus(), aber ich habe hier getestet und die Log-Zeilen waren sauber. Also ist diese Linie, die ich vorgeschlagen habe, vielleicht gar nicht nötig, aber ich weiß es nicht.
Kompatibilität mit früheren API-Ebenen (bearbeitet)
Da ich Boilerplate-Code in Client-Klassen auf ein Minimum reduzieren wollte, habe ich ein paar Annahmen (Button-Bezeichnungen etc.) getroffen, die für meine Bedürfnisse geeignet sind. Vollständiges Anwendungsbeispiel:
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");
Tolle Arbeit bei der Recherche! Traurig, dass es notwendig war, aber trotzdem großartig.
– CommonsWare
15. Juli 12 um 21:08 Uhr
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 schön! Ich frage mich, ob die vorgeschlagene Problemumgehung “zukunftssicher” ist oder ob Sie glauben, dass sie Probleme verursachen wird, wenn der Fehler behoben ist?
– Spanne
14. August 2012 um 08:09 Uhr
@RomainGuidoux siehe aktualisierte Antwort am Ende. Die Klasse im Link hat die Intelligenz, diese Methode nur in Jelly Bean aufzurufen. Bei allem darunter wird es das umgehen und die Standard-Systemimplementierung verwenden und den Systemaufruf für ondateset an Ihre Aktivität weiterleiten. 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 gehört der Aufruf dieser Honeycomb+-Methode. Aber wieder nur in JB.
– davidcesarino
6. September 12 um 20:42 Uhr
Tolle Arbeit hier. Mir fehlen die Worte dafür, wie lächerlich dieses Problem ist.
– Bill Philips
18. September 12 um 5:31 Uhr
Wie wäre es mit TimePickerDialog? Es scheint, als hätte TimePickerDialog kein getTimePicker()
– Lokoko
30. August 13 um 13:45 Uhr
dmon
Ich werde mein eigenes Riff zu der von David Cesarino geposteten Lösung hinzufügen, falls Sie keine Fragmente verwenden und eine einfache Möglichkeit suchen, das Problem 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*/ }
}
}
Nur zur Erinnerung: Wenn Sie die Schaltflächennamen fest auf OK und Abbrechen verdrahten, ist es wahrscheinlich besser, den Standard zu verwenden android.R.string.ok und android.R.string.cancel Felder anstelle der eigenen des Benutzers. Und danke für die Antwort.
– davidcesarino
10. September 2012 um 16:30 Uhr
Ah, schön, ich habe nicht bemerkt, dass sie OK- und Abbruch-Text bereitgestellt haben. Danke!
– dmon
10. September 12 um 17:28 Uhr
Nullzeiger-Ausnahme bei super(context, theme, null, dateToShow.get(YEAR), dateToShow.get(MONTH), dateToShow.get(DAY_OF_MONTH)) erhalten;
– Kishore
6. Dezember 12 um 11:16 Uhr
Errr… übergibst du eine Null dateToShow? Die andere Null dort ist eigentlich das “Fix”, also sollte es da sein. Auf welcher Version bist du?
– dmon
7. Dezember 12 um 15:07 Uhr
kannst du mir bitte sagen welche import hattest du für Field? Ich habe 6 Optionen und keine davon hat funktioniert.
– Adil Malik
20. Februar 13 um 16:02 Uhr
Bis der Fehler behoben ist, schlage ich vor, DatePickerDialog oder TimePickerDialog nicht zu verwenden. Verwenden Sie den 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();
Tejasvi Hegde
Die für TimePicker basierend auf der Lösung von David Cesarino, „TL;DR: 1-2-3 kinderleichte Schritte für eine globale Lösung“
TimePickerDialog bietet nicht die Funktionalität wie DatePickerDialog.getDatePicker. So, OnTimeSetListener Zuhörer muss gestellt werden. Nur um die Ähnlichkeit mit der DatePicker-Workaround-Lösung beizubehalten, habe ich das alte mListener-Konzept beibehalten. Sie können es bei Bedarf ändern.
Anrufen und Zuhören sind die gleichen wie bei der ursprünglichen Lösung. Einfach einbeziehen
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
...
}
Beispiel Aufruf
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 handhaben)
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.
}
}
daniel_c05
Falls jemand eine schnelle Problemumgehung wünscht, hier ist 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:
Das hat bei mir wunderbar funktioniert. Einfach zu verstehen, einfach umzusetzen! Getestet am 4.4
– Erdomester
12. Oktober 14 um 14:58 Uhr
Meine einfache Lösung. Wenn Sie möchten, dass es wieder ausgelöst wird, 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
}
}
Das hat bei mir wunderbar funktioniert. Einfach zu verstehen, einfach umzusetzen! Getestet am 4.4
– Erdomester
12. Oktober 14 um 14:58 Uhr
Gemeinschaft
Laut Ankur Chaudharys brillanter Antwort auf ähnliches TimePickerDialog Problem, wenn wir nach innen schauten onDateSet wenn die gegebene Ansicht isShown() oder nicht, es wird das ganze Problem mit minimalem Aufwand lösen, ohne dass der Picker erweitert oder nach einigen abscheulichen Flags gesucht werden muss, die den Code umgehen, oder sogar nach der Betriebssystemversion gesucht wird. 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 für getan werden onTimeSet gemäß Ankurs Antwort
Beste Antwort von allen anderen!
– ansicht1
3. Oktober 15 um 17:54 Uhr
.
8211800cookie-checkJelly Bean DatePickerDialog — gibt es eine Möglichkeit, abzubrechen?yes
Herzlichen Glückwunsch, Sie scheinen einen Fehler in Android gefunden zu haben. Sie können melde es hier.
– Michael Hampton
15. Juli 12 um 15:41 Uhr
Sehr gut geschriebener Fehlerbericht. Ich kann es vollständig verstehen, ohne den Code testen zu müssen.
– Cheok Yan Cheng
10. September 12 um 1:46 Uhr
Ich fordere alle auf, dieses Thema abzustimmen! Ausgabe 34833
– Bradley
27. November 12 um 18:26 Uhr
Könnten Sie nicht einfach die Schaltflächenfunktion überschreiben, damit sie sich so verhält, als wäre sie aufgrund einer Berührung außerhalb des Dialogs geschlossen worden?
– Karthik Balakrishnan
4. Juni 13 um 6:38 Uhr
Bug ist nach 2 Jahren immer noch offen…unglaublich.
– Erdomester
12. Oktober 14 um 6:36 Uhr