Jelly Bean DatePickerDialog --- есть ли способ отменить?
--- Примечание для модераторов: сегодня (15 июля) я заметил, что кто-то здесь уже сталкивался с этой проблемой. Но я не уверен, уместно ли закрывать это как дубликат, так как я думаю, что предоставил намного лучшее объяснение проблемы. Я не уверен, стоит ли мне редактировать другой вопрос и вставлять туда этот контент, но мне неудобно слишком сильно менять чужой вопрос. ---
У меня здесь что-тостранное.
Я не думаю, что проблема зависит от того, против какого SDK вы строите. Версия ОС устройства имеет значение.
Проблема № 1: несоответствие по умолчанию
DatePickerDialog
был изменен (?) в Jelly Bean и теперь предоставляет только кнопкуГотово. Предыдущие версии включали кнопку Отмена, и это может повлиять на пользовательский опыт (несоответствие, мышечная память из предыдущих версий Android).
Репликация: создание базового проекта. Поместите это в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();
Ожидаемый: кнопкаОтмена, чтобы появиться в диалоге.
Текущий: кнопкаОтмена не появляется.
Скриншоты: 4.0.3 (ОК) и 4.1.1 (возможно, неправильно?).
Проблема № 2: неправильное поведение
Диалог вызывает того слушателя, которому он действительно должен позвонить, а затемвсегда звонит OnDateSetListener
слушатель. Отмена по-прежнему вызывает метод set, а установка его вызывает метод дважды.
Репликация: используйте код № 1, но добавьте код ниже (вы увидите, что это решает № 1, но только визуально / пользовательский интерфейс):
picker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.d("Picker", "Cancel!");
}
});
Ожидаемое:
- Нажатие клавиши BACK или нажатие за пределами диалогового окнаничего не должно делать.
- Нажатие "Отмена" должно напечатать Picker Cancel!,
- Нажатие "Set" должно напечатать Picker Set!,
Текущий:
- Нажатие клавиши НАЗАД или нажатие за пределами диалогового окна выводит на печать комплект выбора!,
- Нажатие "Отмена" печатает Пикер Отмена! а затем комплект поставки!,
- Нажатие "Set" печатает Picker Set! а затем комплект поставки!,
Строки журнала, показывающие поведение:
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!
Другие заметки и комментарии
- Оборачивать вокруг
DatePickerFragment
не имеет значения Я упростил проблему для вас, но я проверил ее.
19 ответов
Примечание: исправлено с Lollipop, источник здесь. Автоматизированный класс для использования в клиентах (совместим со всеми версиями Android) также обновлен.
TL; DR: 1-2-3 простых шага для глобального решения:
- Загрузите этот класс.
- Воплощать в жизнь
OnDateSetListener
в вашей деятельности (или измените класс в соответствии с вашими потребностями). Запустите диалог с этим кодом (в этом примере я использую его внутри
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");
И это все, что нужно! Причина, по которой я все еще сохраняю свой ответ как "принятый", заключается в том, что я все еще предпочитаю свое решение, поскольку оно занимает очень мало места в клиентском коде, оно решает фундаментальную проблему (слушатель вызывается в классе фреймворка), отлично работает при изменениях конфигурации и он направляет логику кода в реализацию по умолчанию в предыдущих версиях Android, не подверженную этой ошибке (см. источник класса).
Оригинальный ответ (сохраняется по историческим и дидактическим причинам):
Источник ошибок
Хорошо, похоже, что это действительно ошибка, и кто-то другой уже заполнил ее. Выпуск 34833.
Я обнаружил, что проблема, возможно, в DatePickerDialog.java
, Где написано:
private void tryNotifyDateSet() {
if (mCallBack != null) {
mDatePicker.clearFocus();
mCallBack.onDateSet(mDatePicker, mDatePicker.getYear(),
mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
}
}
@Override
protected void onStop() {
tryNotifyDateSet();
super.onStop();
}
Я предполагаю, что это могло быть:
@Override
protected void onStop() {
// instead of the full tryNotifyDateSet() call:
if (mCallBack != null) mDatePicker.clearFocus();
super.onStop();
}
Теперь, если кто-нибудь скажет мне, как я могу предложить отчет об исправлении / ошибке для Android, я был бы рад. Тем временем я предложил возможное исправление (простое) в виде прикрепленной версии DatePickerDialog.java
в номере нет.
Концепция, чтобы избежать ошибки
Установите слушателя в null
в конструкторе и создайте свой собственный BUTTON_POSITIVE
Кнопка позже. Вот и все, подробности ниже.
Проблема происходит потому, что DatePickerDialog.java
, как вы можете видеть в источнике, вызывает глобальную переменную (mCallBack
) который хранит слушателя, который был передан в конструктор:
/**
* @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.
}
Итак, хитрость заключается в том, чтобы обеспечить null
слушатель должен быть сохранен как слушатель, а затем прокрутить свой собственный набор кнопок (ниже приведен исходный код из #1, обновлено):
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();
Теперь это будет работать из-за возможной коррекции, которую я выложил выше.
И с тех пор DatePickerDialog.java
проверяет на null
всякий раз, когда он читает mCallback
( поскольку дни API 3/1.5, кажется, --- не могут проверить Honeycomb, конечно), это не вызовет исключения. Учитывая, что Lollipop исправил проблему, я не буду ее рассматривать: просто используйте реализацию по умолчанию (описанную в классе, который я предоставил).
Сначала я боялся не называть clearFocus()
, но я проверил здесь, и строки журнала были чистыми. Так что предложенная мною линия может даже не понадобиться, но я не знаю.
Совместимость с предыдущими уровнями API (отредактировано)
Как я указал в комментарии ниже, это была концепция, и вы можете загрузить используемый мной класс из моей учетной записи Google Drive. В способе, который я использовал, системная реализация по умолчанию используется в версиях, не затронутых этой ошибкой.
Я сделал несколько предположений (названий кнопок и т. Д.), Которые подходят для моих нужд, потому что я хотел свести стандартный код в клиентских классах к минимуму. Пример полного использования:
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");
Я добавлю свой собственный рифф к решению, опубликованному Дэвидом Чезарино, если вы не используете фрагменты и хотите простой способ исправить это во всех версиях (с 2.1 по 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*/ }
}
}
Пока ошибка не будет исправлена, я предлагаю не использовать DatePickerDialog или TimePickerDialog. Используйте пользовательский AlertDialog с виджетом TimePicker/DatePicker;
Изменить TimePickerDialog с помощью;
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();
Изменить DatePickerDialog с помощью;
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();
Один для TimePicker, основанный на решении Дэвида Чезарино, "TL;DR: 1-2-3 простых шага для глобального решения"
TimePickerDialog не предоставляет такую функциональность, как DatePickerDialog.getDatePicker. Итак, слушатель OnTimeSetListener должен быть предоставлен. Просто для того, чтобы сохранить сходство с обходным решением DatePicker, я сохранил старую концепцию mListener. Вы можете изменить его, если вам нужно.
Вызов и слушатель такие же, как оригинальное решение. Просто включите
import android.app.TimePickerDialog;
import android.app.TimePickerDialog.OnTimeSetListener;
расширить родительский класс,
... implements OnDateSetListener, OnTimeSetListener
Воплощать в жизнь
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
...
}
пример вызова
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");
(Обновлено для обработки отмены)
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.
}
}
Согласно блестящему ответу Анкура Чаудхари на подобное TimePickerDialog
вопрос, если мы проверили внутри onDateSet
если данный вид isShown()
или нет, это решит всю проблему с минимальными усилиями, без необходимости расширения средства выбора или проверки на наличие каких-либо отвратительных флагов, идущих вокруг кода или даже проверки версии ОС, просто сделайте следующее:
public void onDateSet(DatePicker view, int year, int month, int day) {
if (view.isShown()) {
// read the date here :)
}
}
и, конечно, то же самое можно сделать для onTimeSet
согласно ответу Анкура
Моё простое решение. Если вы хотите снова запустить его, просто запустите "resetFired" (скажем, при повторном открытии диалога).
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
}
}
В случае, если кто-то хочет быстро обойти, вот код, который я использовал:
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();
}
Где layout.date_picker_view - простой ресурс макета с DatePicker, поскольку он является единственным элементом:
<!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"/>
Вот полный учебник, если вы заинтересованы.
Я справился с этой ситуацией, используя флаг и переопределяя методы onCancel и onDismiss.
OnCancel вызывается только тогда, когда пользователь касается вне диалогового окна или кнопки возврата. onDismiss всегда вызывается
Установка флага в методе onCancel может помочь отфильтровать в методе onDismiss намерение пользователя: отменить действие или выполненное действие. Ниже приведен код, который показывает идею.
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);
}
}
Существует очень простой обходной путь, если ваше приложение не использует панель действий. Кстати, обратите внимание, что некоторые приложения используют эту функцию для работы, потому что отмена выбора даты имеет особое значение (например, оно очищает поле даты до пустой строки, что для некоторых приложений является допустимым и значимым типом ввода) и использование логических флагов для предотвращения повторной установки даты на ОК не поможет вам в этом случае.
Число рейнольдса фактическое исправление, вам не нужно создавать новые кнопки или свой собственный диалог. Дело в том, чтобы быть совместимым как с более старыми версиями Android, так и с ошибками (4. ) и любыми будущими, хотя в последнем, конечно, нельзя быть уверенным. Обратите внимание, что в Android 2. onStop() для android.app.Dialog вообще ничего не делает, а в 4.* он выполняет mActionBar.setShowHideAnimationEnabled(false), что важно, только если в вашем приложении есть панель действий. Функция onStop() в DatePickerDialog, которая наследуется от Dialog, вносит только mDatePicker.clearFocus() (по состоянию на последнее исправление для исходников Android 4.3), что не кажется существенным.
Поэтому замена onStop() методом, который ничего не делает, во многих случаях должна исправить ваше приложение и гарантировать, что оно останется таковым в обозримом будущем. Таким образом, просто расширьте класс DatePickerDialog своим собственным и переопределите onStop() с помощью фиктивного метода. Вы также должны будете предоставить один или два конструктора в соответствии с вашими требованиями. Также обратите внимание, что не следует пытаться переусердствовать с этим исправлением, например, пытаясь что-то сделать с панелью активности напрямую, так как это ограничит вашу совместимость только с последними версиями Android. Также обратите внимание, что было бы неплохо иметь возможность вызывать super для onStop() DatePicker, потому что ошибка есть только в onStop() в самом DatePickerDialog, но не в суперклассе DatePickerDialog. Однако это потребует от вас вызова super.super.onStop() из вашего пользовательского класса, что Java не позволит вам сделать, так как это противоречит философии инкапсуляции:) Ниже приведен мой маленький класс, который я использовал для проверки даты DatePickerDialog. Я надеюсь, что этот комментарий будет полезен для кого-то. Войтек Ярош
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.
}
}
Попробуйте следующие понятия.
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();
метод onDateSet() вызывает дважды (если вы проверяете в emulator.it вызовы дважды. Если вы используете реальное устройство, то оно будет правильно вызываться один раз. Если вы используете эмулятор, тогда используйте счетчик. Если вы работаете в реальном устройстве, тогда игнорировать переменную счетчика. Для реального устройства это работает для меня.)
когда пользователь нажимает кнопку в DatePickerDialog .
для этого вы должны поддерживать значение счетчика и ничего не делать, когда метод вызывает первый раз, и выполнять операцию, когда метод вызывает второй раз.
См. Приведенные ниже фрагменты кода
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();
Для отмены выбора даты дилалог его работает для меня. Для эмулятора его не работает
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);
Это работает для меня для реального устройства. Но для эмулятора это не работает правильно. Я думаю, что это ошибка эмулятора Android.
Я использую даты, время и число. Средство выбора номера вызывает onValueChanged всякий раз, когда пользователь выбирает номер, до того, как средство выбора отклоняется, поэтому у меня уже была такая структура, чтобы делать что-то со значением только тогда, когда средство выбора отклонено:
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;
}
Я расширил это, чтобы установить пользовательские onClickListeners для моих кнопок с аргументом, чтобы увидеть, какая кнопка была нажата. Теперь я могу проверить, какая кнопка была нажата, прежде чем установить окончательное значение:
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
}
}
А затем я расширил это для работы с типами даты и времени для средств выбора даты и времени, а также с типом int для средств выбора чисел.
Я опубликовал это, потому что я думал, что это было проще, чем некоторые из решений выше, но теперь, когда я включил весь код, я думаю, что это не намного проще! Но это хорошо вписывается в структуру, которую я уже имел.
Обновление для Lollipop: по- видимому, эта ошибка возникает не на всех устройствах Android 4.1-4.4, потому что я получил несколько отчетов от пользователей, чьи средства выбора даты и времени не вызывали обратные вызовы onDateSet и onTimeSet. И ошибка была официально исправлена в Android 5.0. Мой подход работал только на устройствах, где присутствует ошибка, потому что мои пользовательские кнопки не вызывали обработчик onClick диалога, который является единственным местом, где onDateSet и onTimeSet вызываются, когда ошибки нет. Я обновил мой код выше, чтобы вызывать диалог onClick, так что теперь он работает независимо от того, присутствует ли ошибка.
Вы можете переопределить onCancel() и использовать setOnDismissListener() для обнаружения негативных действий пользователя. А с DatePickerDialog.BUTTON_POSITIVE вы знаете, что пользователь хочет установить новую дату.
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;
}
});
затем проверьте для setDate:
public void onDateSet(DatePicker view, int year, int month, int day) {
if(setDate){
//do something with new date
}
}
Простое решение будет использовать логическое значение, чтобы пропустить второй запуск
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();
Вот мой класс обходного пути для DatePickerDialog на кнопке отмены так же как отмене его кнопкой возврата. Копирование и использование в стиле DatePickerDialog (так как слушатель с состоянием, мы должны создать новый экземпляр при использовании, в противном случае требуется больше кода, чтобы он работал)
Использование:
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();
Учебный класс:
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;
}
}
}
Моя рабочая версия с ClearButton с использованием лямбда-выражений:
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);
}
}
Для TimePickerDialog обходной путь может быть следующим:
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;
}
Я делегирую все события оболочке KitKatSetTimeListener и запускаю только исходный OnTimeSetListener только в случае нажатия BUTTON_POSITIVE.
После тестирования некоторых предложений, размещенных здесь, я лично считаю, что это решение является наиболее простым. Я передаю "null" как мой слушатель в конструкторе DatePickerDialog, а затем, когда я нажимаю кнопку "ОК", я вызываю мой 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();
}
});
Мне понравился ответ Дэвида Чезарино выше, но я хотел что-то, что было бы заменой неработающего диалога и работало бы над любым диалогом, который мог бы отсутствовать, отмена / неправильное поведение отмены. Вот производные классы для DatePickerDialog / TimePickerDialog, которые должны работать как капли замен. Это не пользовательские представления. Он использует системный диалог, но просто меняет поведение кнопки отмены / возврата, чтобы работать как положено.
Это должно работать на уровне API 3 и выше. Итак, в основном любая версия Android (я тестировал ее специально на желе и леденце).
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://stackru.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://stackru.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));
}
}
Я знаю, что этот пост был здесь почти год, но я подумал, что должен опубликовать свои выводы. Вы могли бы все еще держать слушателя (вместо того, чтобы установить его на отключение звука) и все еще иметь эту работу как ожидалось. Ключ заключается в том, чтобы неявно установить "ОК" или (и) кнопки "отмена". Я проверил это, и это работает с благодарностью для меня. Слушателя не увольняют дважды.
Посмотрите на этот пример,
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();
}