上記のDavid Cesarinoの回答が気に入りましたが、壊れたダイアログの代わりにドロップインし、キャンセルが欠落している/キャンセル動作が正しくない可能性のあるダイアログで機能するものが必要でした。以下は、ドロップイン置換として機能する DatePickerDialog / TimePickerDialog の派生クラスです。これらはカスタム ビューではありません。システムダイアログを使用しますが、キャンセル/戻るボタンの動作を期待どおりに変更するだけです。
これは、API レベル 3 以降で機能するはずです。したがって、基本的に Android の任意のバージョン (具体的には、jellybean と lollipop でテストしました)。
日付ピッカー ダイアログ:
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));
}
}