果冻豆约会对话——-有办法取消吗?

今天(7月15日) ,我注意到有人已经面临这个问题。但我不确定这是否合适,关闭作为一个副本,因为我认为我提供了一个更好的解释这个问题。我不确定我是否应该编辑另一个问题并把这个内容粘贴到那里,但是我不喜欢太多地改变别人的问题。 —— -

我这里有些 真奇怪的东西。

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

问题 # 1: 缺省情况下的不一致性

DatePickerDialog改变(?)现在只提供了一个 成交按钮。以前的版本包括一个 取消按钮,这可能会影响用户体验(不一致,肌肉记忆从以前的 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();

预计: 取消按钮将出现在对话框中。

当前: 没有出现 取消按钮。

截图: 4.0.3(OK)和 4.1.1(可能错了?)。

问题2: 错误的解雇行为

对话框调用它确实应该调用的侦听器,然后 一直都是调用 OnDateSetListener侦听器。取消仍然调用 set 方法,并将其设置为调用该方法两次。

复制: 使用 # 1代码,但在下面添加代码(您将看到这个解决方案 # 1,但仅在视觉上/UI) :

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

预期:

  • 按 BACK 键或在对话框外单击应为 什么都别做
  • 按“取消”应打印 Picker Cancel!
  • 按“设置”应打印 挑选机!

目前:

  • 按 BACK 键或单击对话框外部将打印 挑选机!
  • 按“取消”键将打印 Picker Cancel!,然后打印 挑选机!
  • 按“设置”打印 挑选机!,然后 挑选机!

显示行为的日志行:

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上并不重要。我为你简化了这个问题,但是我已经测试过了。
29421 次浏览

注意: < strong > 固定的棒棒糖来源在这里。自动化的 用于客户端的类(兼容所有 Android 版本)也更新了。

DR: 1-2-3个非常简单的全球解决方案步骤:

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

And that's all it takes! The reason I still keep my answer as "accepted" is because I still prefer my solution since it has a very small footprint in client code, it addresses the fundamental issue (the listener being called in the framework class), works fine across config changes and it routes the code logic to the default implementation in previous Android versions not plagued by this bug (see class source).

Original answer (kept for historical and didactic reasons):

Bug source

OK, looks like it's indeed a bug and someone else already filled it. Issue 34833.

I've found that the problem is possibly in DatePickerDialog.java. Where it reads:

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 提交补丁/bug 报告,我将非常乐意。同时,我提出了一个可能的修复(简单)作为附加版本的 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在读取 mCallback时会检查 null(从 API 3/1.5开始——-当然不能检查蜂巢) ,因此它不会触发异常。考虑到 Lollipop 修复了这个问题,我不打算深究: 只是使用默认实现(在我提供的类中包含)。

一开始我还担心不能拨打 clearFocus(),但是我在这里测试过,日志线路是干净的。所以我提出的那条路线也许根本就没必要,但我不知道。

与以前 API 级别的兼容性

正如我在下面的评论中指出的,这是一个概念,你可以 从我的 Google Drive 帐户下载我正在使用的类。我使用的方法是,默认的系统实现用于不受 bug 影响的版本。

我采取了一些假设(按钮名称等) ,适合我的需要,因为我想减少样板代码在客户端类到最低限度。完整用法示例:

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

我将在 David Cesarino 发布的解决方案上添加我自己的重复段落,如果你没有使用片段,并想要一个简单的方法来修复它在所有版本(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 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 ()方法调用两次(如果你正在检查 Simulator.it 调用两次。如果正在使用真实设备,它将单次正确调用。如果你正在使用模拟器,那么使用 counter。如果你正在使用真实设备,那么忽略 counter 变量。对于真正的设备,它为我工作。)
当用户单击 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);


它为我工作的一个真正的设备。但是对于模拟器来说,它不能正常工作。我认为这是一个安卓模拟器的错误。

我的简单解决方案。当你想让它再次激活只需运行“ 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
}
}

在修复 bug 之前,我建议不要使用 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();

我知道这篇文章已经发表了将近一年,但是我认为我应该发表我的发现。 您仍然可以保留侦听器(而不是将其设置为 mull) ,并按照预期进行此工作。关键是隐式设置“确定”或(和)“取消”按钮。我测试了一下,它对我很有效。听众不会被解雇两次。

看看这个例子,

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

如果有人想要一个快速的解决方案,以下是我使用的代码:

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

如果你感兴趣的话,这是 完整教程

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

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 可以知道用户希望设置一个新的日期。

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

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

OnCancel 只有在用户触及对话框外部或后退按钮时才会被调用。 解散总是会被叫到

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

如果您的应用程序不使用操作栏,那么有一个非常简单的解决方案。顺便说一下,有些应用程序依赖于这个功能来工作,因为取消日期选择器有一个特殊的含义(例如,它将日期字段清除为一个空字符串,对于一些应用程序来说,这是一个有效的和有意义的输入类型) ,并且使用布尔标志来防止日期被设置两次在 OK 上不会在这种情况下帮助你。

Re.实际的修复,您不必创建新的按钮或您自己的对话框。关键是要兼容这两个版本,旧版本的 Android,有缺陷的版本(4。当然,后者是不可能确定的。 请注意,在 Android 2中。 ,android.app 的 onStop ()。对话框什么也不做,4。* it does mActionBar.setShowHideAnimationEnable (false) ,只有当你的应用程序有一个操作栏时,这才是重要的。DatePickerDialog 中的 onStop ()继承自 Dialog,它只提供 mDatePicker.clearFocus ()(最新的 Android 源代码4.3的补丁) ,这似乎并不重要。译注:

因此,在很多情况下,用一个什么都不做的方法替换 onStop ()应该可以修复应用程序,并确保它在可预见的将来保持这种状态。因此,只需用您自己的方法扩展 DatePickerDialog 类并使用一个虚方法重写 onStop ()。您还必须根据需要提供一个或两个构造函数。 还要注意的是,不要试图通过直接使用活动栏来修复这个问题,因为这只会限制你对最新版本 Android 的兼容性。还要注意的是,如果能够为 DatePicker 的 onStop ()调用 super 会更好,因为 bug 只存在于 DatePickerDialog 本身的 onStop ()中,而不存在于 DatePickerDialog 的 super 类中。然而,这需要您从您的定制类中调用 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.
}

}

基于 David Cesarino,“ TL; DR: 全球解决方案的1-2-3个非常简单的步骤”解决方案的 TimePicker

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

下面是我为 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;
}
}

}

我用的是日期选择器,时间选择器和数字选择器。每当用户选择一个数字时,在选择器被解除之前,数字选择器就会调用 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;
}

我扩展了这个函数来为我的按钮设置自定义 onClickListener,并使用一个参数来查看单击了哪个按钮。现在我可以在设置最终值之前检查哪个按钮被点击了:

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 回调。这个 bug 在 Android 5.0中已经被正式修复了。我的方法只适用于存在 bug 的设备,因为我的自定义按钮没有调用对话框的 onClick 处理程序,而 onDateSet 和 onTimeSet 是在 bug 不存在时唯一被调用的地方。我更新了上面的代码来调用对话框的 onClick,所以现在无论是否存在 bug,它都可以工作。

根据 Ankur Chaudhary 关于类似 TimePickerDialog问题的出色的 回答,如果我们检查 onDateSet内部是否给定的视图 isShown(),它将以最小的努力解决整个问题,而不需要扩展选择器或检查一些可怕的标志周围的代码,甚至检查操作系统版本,只需要做以下事情:

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

当然,根据安库尔的回答,对 onTimeSet也可以这样做

我喜欢 David Cesarino 上面的回答,但是我想要一些东西来代替中断的对话框,并且可以处理任何可能丢失的取消/有不正确的取消行为的对话框。下面是 DatePickerDialog/TimePickerDialog 的派生类,它们应该作为置换操作。这些不是自定义视图。它使用系统对话框,但只是更改取消/回退按钮的行为,以按预期工作。

这应该可以在 API 级别3及更高级别上工作。所以,基本上任何版本的 Android (我特别在糖豆和棒棒糖上测试过)。

日期选择器对话框:

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

对话框:

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

我使用 Lambda 表达式使用 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,并且只有在单击 BUTTON _ POSITIVE 时才返回原始 OnTimeSetListener。

在测试了这里提出的一些建议之后,我个人认为这个解决方案是最简单的。 我在 DatePickerDialog 构造函数中传递“ null”作为侦听器,然后当我单击“ OK”按钮时,我调用 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();
}
});