果冻豆DatePickerDialog – 有没有办法取消?

主持人注意:今天(7月15日),我注意到有人已经遇到这个问题了 。 但是我不确定是否合适这样做是重复的,因为我认为我提供了一个更好的解释。 我不确定是否应该编辑其他问题并粘贴这些内容,但是我不太乐意改变别人的问题。 —

我在这里有一些奇怪的东西。

我不认为这个问题取决于你构build哪个SDK。 设备操作系统版本是重要的。

问题#1:默认情况下不一致

DatePickerDialog在Jelly Bean中被更改(?),现在只提供一个完成button。 以前的版本包括取消button,这可能会影响用户体验(不一致,从以前的Android版本肌肉记忆)。

复制:创build一个基本的项目。 把这个放在创build:

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

预期: 取消button出现在对话框中。

当前: 取消button不会出现。

屏幕截图: 4.0.3 (OK)和4.1.1 (可能错误?)。

问题2:错误的解雇行为

Dialog调用它应该调用的任何一个监听器,然后总是调用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键或在对话框外单击应该什么也不做
  • 按“取消”应打印取消取消!
  • 按“Set”应打印Picker Set!

当前:

  • 按下BACK键或在对话框外单击打印Picker Set!
  • 按“取消”打印选取器取消! 然后Picker 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周围并不重要。 我简化了这个问题,但是我已经testing过了。

注: 固定为棒棒糖 , 在这里来源 。 用于客户端的自动化类 (与所有Android版本兼容)也已更新。

TL; DR:1-2-3死亡简单的步骤为全球解决scheme:

  1. 下载这个课程。
  2. 在您的活动中实现OnDateSetListener (或更改类以满足您的需要)。
  3. 用这个代码触发对话框(在这个例子中,我用它在一个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"); 

而这一切都需要! 我仍然保持我的答案为“接受”的原因是因为我仍然更喜欢我的解决scheme,因为它在客户端代码中的占用空间非常小,它解决了基本问题(在框架类中调用监听器),在configuration更改并将代码逻辑路由到以前的Android版本的默认实现,而不受此bug的困扰(请参阅类源代码)。

原始答案(由于历史和教导的原因):

Bug来源

好吧,看起来确实是一个错误,而其他人已经填充了它。 问题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 ,稍后创build您自己的BUTTON_POSITIVEbutton 。 就是这样,细节如下。

发生这个问题是因为DatePickerDialog.java ,就像你在源代码中看到的那样,调用一个全局variables( 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监听器作为监听器,然后滚动你自己的一组button(下面是从#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每当它读取mCallback时都会检查null ( 因为API 3 / 1.5的日子似乎 —当然不能检查Honeycomb),它不会触发exception。 考虑到棒棒糖解决了这个问题,我不打算研究它:只使用默认实现(在我提供的类中覆盖)。

起初我害怕没有调用clearFocus() ,但是我已经在这里testing过了,Log线是干净的。 所以我build议的这条线可能根本就没有必要,但我不知道。

与先前API级别的兼容性(已编辑)

正如我在下面的评论中指出的那样,这是一个概念,您可以从我的Google云端硬盘帐户下载我正在使用的课程 。 我使用的方式是在不受bug影响的版本上使用默认的系统实现。

我采取了一些适合我需求的假设(button名称等),因为我想将客户端类中的样板代码降到最低。 完整的使用示例:

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

我要添加我自己的解决schemeDavid Cesarino发布的解决scheme,以防你不使用碎片,并想要一个简单的方法来解决它的所有版本(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*/ } } } 

直到错误将被修复,我build议不要使用DatePickerDialog或TimePickerDialog。 在TimePicker / DatePicker小部件中使用自定义的AlertDialog;

更改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基于David Cesarino的解决scheme“TL; DR:1-2-3死亡简单步骤为全球解决scheme”

TimePickerDialog不提供像DatePickerDialog.getDatePicker这样的function。 所以, OnTimeSetListener监听器必须被提供。 只是为了保持与DatePicker解决scheme的相似性,我保持了旧的mListener概念。 你可以改变它,如果你需要。

呼叫和监听器与原始解决scheme相同。 只包括

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

万一任何人想要一个快速的解决方法,这里是我使用的代码:

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

如果你有兴趣的话,这里有完整的教程 。

我简单的解决scheme 当你想让它再次触发时,只需运行“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 } } 

我pipe理这种情况的方式是使用一个标志并覆盖onCancel和onDismiss方法。

只有在用户触摸对话框或后退button时才会调用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); } } 

根据Ankur Chaudhary在类似的TimePickerDialog问题上的出色答案 ,如果我们在onDateSet检查了给定视图isShown() ,它将以最小的努力解决整个问题,不需要扩展select器或检查一些可怕的标记遍历代码甚至检查操作系统版本,只需执行以下操作:

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

当然,按照Ankur的回答, onTimeSet也可以做到这onTimeSet

如果您的应用程序不使用操作栏,则有一个非常简单的解决方法。 请注意,有些应用程序依靠此function来工作,因为取消dateselect器有一个特殊的含义(例如它将date字段清除为空string,对于某些应用程序来说,这是一个有效和有意义的inputtypes)和使用布尔标志来防止date被设置两次确定不会帮助你在这种情况下。

回覆。 实际的修复,你不必创build新的button或自己的对话框。 关键是兼容Android的旧版本,越野车版本(4. )以及任何未来的版本,当然,后者是不可能确定的。 请注意,在Android 2.中 ,android.app.Dialog的onStop()完全不起作用,而在4. *中,它只有在应用程序具有操作栏时才重要。 DatePickerDialog中的onStop()inheritance自Dialog,只贡献mDatePicker.clearFocus()(截至Android 4.3的最新修复版本),这似乎不重要。

因此用一个什么都不做的方法replaceonStop()应该在很多情况下修复你的应用程序,并确保在可预见的将来它将保持这样的状态。 因此,只需使用您自己的方法扩展DatePickerDialog类,并使用虚方法覆盖onStop()。 根据您的要求,您还必须提供一个或两个构造函数。 还要注意的是,不应该试图通过例如尝试直接对活动条执行某些操作来尝试过度修复此问题,因为这会将您的兼容性限制为仅适用于最新版本的Android。 另外请注意,能够为DatePicker的onStop()调用super将会很好,因为这个bug只是在DatePickerDialog本身的onStop()中,而不是在DatePickerDialog的超类中。 但是,这将需要你从你的自定义类中调用super.super.onStop(),Java不会让你这么做,因为它违背了封装理念:)下面是我用来实现DatePickerDialog的小类。 我希望这个评论对某个人有用。 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. } 

}

尝试下面的概念。

 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调用两次。如果正在使用真正的设备,那么它会正确调用一次。如果你正在使用模拟器然后使用counter.if你正在真正的设备工作,然后忽略计数器variables。对于真实的设备它为我工作。)
当用户点击DatePickerDialog中的button时。
为此,您应该保留一个计数器值,并且在方法第一次调用时执行操作时不执行任何操作。
请参阅下面的代码片段

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

取消datepicker dilalog它为我工作。对于模拟器它不是炒作

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

它为我工作一个真正的device.But模拟器不能正常工作。我认为它是一个Android模拟器的错误。

一个简单的解决scheme是使用布尔值来跳过第二次运行

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

您可以覆盖onCancel()并使用setOnDismissListener()来检测负面的用户操作。 用DatePickerDialog.BUTTON_POSITIVE你知道用户想要设置一个新的date。

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

这里是我取消button上的DatePickerDialog的解决方法,也可以通过后退button来放弃。 以DatePickerDialog的风格复制和使用(因为侦听器是有状态的,所以我们必须在创build时使用新的实例,否则需要更多的代码才能使它工作)

使用:

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

}

我使用dateselect器,时间选取器和号码选取器。 数字select器在用户select一个数字之前调用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; } 

I extended this to set custom onClickListeners for my buttons, with an argument to see which button was clicked. Now I can check which button was tapped before I set my final value:

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

And then I extended that to work with the date and time types for date and time pickers as well as the int type for number pickers.

I posted this because I thought it was simpler than some of the solutions above, but now that I've included all the code, I guess it's not much simpler! But it fit nicely into the structure I already had.

Update for Lollipop: Apparently this bug doesn't happen on all Android 4.1-4.4 devices, because I received a few reports from users whose date and time pickers weren't calling the onDateSet and onTimeSet callbacks. And the bug was officially fixed in Android 5.0. My approach only worked on devices where the bug is present, because my custom buttons didn't call the dialog's onClick handler, which is the only place that onDateSet and onTimeSet are called when the bug is not present. I updated my code above to call the dialog's onClick, so now it works whether or not the bug is present.

I liked David Cesarino's answer above, but wanted something that was a drop in replacement for the broken dialog and would work on any dialog that might be missing cancel / have incorrect cancel behavior. Here are derived classes for DatePickerDialog / TimePickerDialog that should work as drop in replacements. These are not custom views. It uses the system dialog, but just changes the cancel / back button behavior to work as expected.

This should work on API level 3 and higher. So, basically any version of Android (I tested it on jellybean and lollipop specifically).

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

My working version with ClearButton using 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); } } 

For TimePickerDialog the workaround can be as follows:

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

I delegate all events to wrapper KitKatSetTimeListener, and only fire back to original OnTimeSetListener in case BUTTON_POSITIVE is clicked.

After testing some of the sugestions posted here, I personally think this solution is the most simple. I pass "null" as my listener in the DatePickerDialog constructor, and then when I click the "OK" button I call my 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(); } }); 

I know this post has been here for almost a year but I thought I should post my findings. You could still keep the listener(instead of setting it to mull) and still have this work as expected. The key is to implicitly set the "OK" or(and) the "cancel" buttons. I tested it and it works gratefully for me. The listener does not get fired twice.

Look at this example,

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