【问题标题】:Jelly Bean DatePickerDialog --- is there a way to cancel?Jelly Bean DatePickerDialog --- 有没有办法取消?
【发布时间】:2012-07-11 18:07:02
【问题描述】:

--- 版主注意:今天(7 月 15 日),我注意到有人已经遇到过这个问题here。但我不确定将其作为副本关闭是否合适,因为我认为我对这个问题提供了更好的解释。我不确定我是否应该编辑其他问题并将此内容粘贴到那里,但我不愿意过多地更改其他人的问题。 ---

我这里有一些奇怪的

我不认为问题取决于您构建的 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(OK)和4.1.1(可能错了?)。

问题 #2:错误的解雇行为

Dialog 调用它确实应该调用的任何监听器,然后 总是 调用 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!
  • 按“Set”应该会打印出Picker Set!

当前:

  • 按 BACK 键或在对话框外单击会打印 Picker Set!
  • 按“取消”会打印 Picker Cancel!,然后是 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!

其他笔记和cmets

  • DatePickerFragment 包裹它并不重要。我为您简化了问题,但我已经对其进行了测试。

【问题讨论】:

  • 恭喜,您似乎发现了 Android 中的一个错误。你可以report it here
  • 写得很好的错误报告。不用试运行代码我就能完全理解。
  • 我敦促大家投票支持这个问题! Issue 34833
  • 难道你不能直接覆盖按钮功能,让它像由于在对话框外触摸而被取消一样?
  • Bug 2 年后仍然存在...难以置信。

标签: android datepicker android-4.2-jelly-bean android-datepicker


【解决方案1】:

注意:Fixed as of Lollipopsource here。自动 class for use in clients(兼容所有 Android 版本)也更新了。

TL;DR:1-2-3 简单的步骤即可获得全局解决方案:

  1. 下载this类。
  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");
    

仅此而已!我仍然将我的答案保持为“已接受”的原因是因为我仍然更喜欢我的解决方案,因为它在客户端代码中占用的空间非常小,它解决了根本问题(在框架类中调用的侦听器),在配置更改时工作正常,它将代码逻辑路由到不受此错误困扰的早期 Android 版本中的默认实现(请参阅类源)。

原始答案(出于历史和教学原因而保留):

错误来源

好的,看起来它确实是一个错误并且其他人已经填充了它。 Issue 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 在读取mCallback 时检查nullsince the days of API 3/1.5 it seems --- 当然不能检查 Honeycomb),所以它不会触发异常。 考虑到 Lollipop 解决了这个问题,我不打算研究它:只使用默认实现(包含在我提供的类中)。

一开始我害怕不打电话给clearFocus(),但我在这里测试过,日志行很干净。所以我提出的那条线可能根本就没有必要,但我不知道。

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

正如我在下面的评论中指出的那样,这是一个概念,您可以download the class I'm using from my Google Drive account。我使用的方式,默认系统实现是用在不受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");

【讨论】:

  • 研究工作真棒!遗憾的是这是必要的,但仍然很棒。
  • 非常好!在更新我的文本字段和调用/不调用正确回调的问题上,我一直在苦苦挣扎。谢谢!我想知道建议的解决方法是否“面向未来”,或者您是否认为修复错误后会导致问题?
  • @RomainGuidoux 在最后看到更新的答案。链接中的类具有仅在果冻豆中调用该方法的智能。在下面的任何内容上,它将绕过它并使用默认系统实现,将 ondateset 的系统调用路由到您的活动。只是在果冻豆中,它在路由回调之前采取了额外的措施(避免错误),这涉及调用该蜂窝+方法。但同样,仅在 JB。
  • 这里的工作很棒。我无法形容这个问题有多荒谬。
  • TimePickerDialog 怎么样?似乎 TimePickerDialog 没有 getTimePicker()
【解决方案2】:

我将在 David Cesarino 发布的解决方案上添加我自己的 riff,以防您不使用 Fragments,并且想要一种简单的方法在所有版本(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*/ }
  }
}

【讨论】:

  • 请记住:如果您将按钮名称硬连接到确定和取消,最好使用标准的android.R.string.okandroid.R.string.cancel 字段,而不是用户自己的字段。并感谢您的回复。
  • 啊,很好,我没有注意到他们提供了 OK 和 Cancel 文本。谢谢!
  • 在 super(context, theme, null, dateToShow.get(YEAR), dateToShow.get(MONTH), dateToShow.get(DAY_OF_MONTH)) 处获取空指针异常;
  • Err... 你传递一个空的dateToShow 吗?另一个 null 实际上是“修复”,所以应该在那里。你是哪个版本的?
  • 你能告诉我你有哪个importField吗?我有 6 个选项,但没有一个有效。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    TimePicker 基于 David Cesarino 的解决方案,“TL;DR: 1-2-3 dead easy steps for a global solution”

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

    【讨论】:

    • 在 s3 API 18 上尝试它对我不起作用
    【解决方案5】:

    如果有人想要快速解决方法,这是我使用的代码:

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

    如果您有兴趣,这里是full tutorial

    【讨论】:

    • 这对我来说非常有用。易于理解,易于实施!在 4.4 上测试
    【解决方案6】:

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

    【讨论】:

    • @AbdelHady 您的代码编辑不正确,但我已对其进行了编辑以使其更清晰。不需要添加“isJellyBeanOrAbove()”方法,没有优势,只是增加了不必要的复杂性。调用 resetFired 很便宜。
    • 这里的代码是错误的,因为onDateSet会在对话框以任何方式关闭时被调用一次,如果那个意思是设置时间那么它会再次被触发,所以我们需要接第二个电话,而不是像您那样接第一个电话,
    • 关于isJellyBeanOrAbove(),低于 Jellybean 的版本没有整个问题所涉及的错误,并且考虑到我们想要捕捉第二个调用,除非我们让代码不会运行这个检查,相信我,我已经在模拟器和真实设备(不同版本)上尝试了我的代码几次,它的工作就像一个魅力
    • 我不确定这是否值得一票。我在 2 年前发布了这个,从那时起它就一直在我们的商业应用程序中工作得很好。我们的 QA 团队或我们的数千名用户没有报告任何错误。这是一个人们可以扩展的简单答案。当您希望它再次触发时调用“resetFired”。
    • 在我们的应用程序中不需要“isJellyBeanOrAbove”。如果您在正确的区域调用“resetFired”,该应用程序将在所有版本中正常工作。
    【解决方案7】:

    根据 Ankur Chaudhary 在类似的TimePickerDialog 问题上的精彩answer,如果我们在onDateSet 内部检查给定视图isShown() 与否,它将以最小的努力解决整个问题,无需要扩展选择器或检查代码周围的一些可怕标志,甚至检查操作系统版本,只需执行以下操作:

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

    当然,根据 Ankur 的回答,onTimeSet 也可以这样做

    【讨论】:

    • 最好的答案!
    【解决方案8】:

    我处理这种情况的方法是使用标志并覆盖 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);
        }
    }
    

    【讨论】:

      【解决方案9】:

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

      回复。实际修复,您不必创建新按钮或您自己的对话框。关键是要兼容旧版本的 Android、有缺陷的 Android 版本 (4.) 和任何未来的版本,当然,后者无法确定。 请注意,在 Android 2. 中,android.app.Dialog 的 onStop() 根本不执行任何操作,而在 4.* 中它执行 mActionBar.setShowHideAnimationEnabled(false),这仅在您的应用具有操作栏时才重要. DatePickerDialog 中的 onStop() 继承自 Dialog,仅贡献 mDatePicker.clearFocus()(截至 Android 源 4.3 的最新修复),这似乎不是必需的。

      因此,在许多情况下,将 onStop() 替换为不执行任何操作的方法应该可以修复您的应用,并确保它在可预见的将来仍然如此。因此,只需使用您自己的扩展 DatePickerDialog 类并使用虚拟方法覆盖 onStop() 。根据您的要求,您还必须提供一两个构造函数。 另请注意,不应试图尝试通过例如过度修复此修复。尝试直接对活动栏进行操作,因为这会将您的兼容性限制为仅适用于最新版本的 Android。另请注意,能够为 DatePicker 的 onStop() 调用 super 会很好,因为该错误仅在 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.
      }   
      

      }

      【讨论】:

      • 即使使用了 ActionBar,如果您从不隐藏/显示 ActionBar,这也可能是一种可接受的解决方法。为了使事情更加安全,您也可以覆盖 onStart 以不执行任何操作(然后对动画的调用将被安全地取消挂钩)。即使你隐藏/显示它,它只是被禁用的动画。
      【解决方案10】:


      试试下面的概念。

      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() 方法调用两次(如果您正在检查模拟器。它会调用两次。如果使用的是真实设备,那么它将正确调用一次。如果您使用的是模拟器,则使用计数器。如果您在真实设备中工作,然后忽略计数器变量。对于真实设备,它对我有用。)
      当用户单击 DatePickerDialog 中的按钮时。
      为此,您应该维护一个计数器值,并且在该方法第一次调用时什么都不做,而在该方法第二次调用时执行该操作。
      参考下面的编码sn-ps

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


      它适用于我的真实设备。但对于模拟器它无法正常工作。我认为它是一个 android 模拟器错误。

      【讨论】:

        【解决方案11】:

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

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

        【讨论】:

        • 再一次,就像我在你之前告诉其他人一样,这解决了这个错误。我不想听起来粗鲁,但是你们应该在发布之前测试自己的解决方案......只是为了证明我的观点,使用您的代码并让用户按下以取消对话框,看看我的意思。该错误的原因onStop在不应该调用该方法时...触发两次是该错误的结果。
        • 如果我还不够清楚,让我说:按下返回以取消对话调用onDateSet。因此,坏了。
        • 感谢您显示错误。我编辑了答案,以便它可以与后退按钮一起使用。您的答案有效,但 DatePicker dp = picker.getDatePicker();由于未添加 getTimePicker() 方法,因此无法使用 TimePickers。所以这将是一个有效的答案
        • 我们如何尝试跳过第二个运行?!!我尝试了几次,在关闭对话框时,任何方式onDateSet将被调用一次,但在选择“完成”时或“设置”,那么它将被调用两次。因此我们只需要跳过第一个,所以如果它被调用两次,那么只有这样我们才有正确的日期
        【解决方案12】:

        您可以覆盖 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
            }
        }
        

        【讨论】:

          【解决方案13】:

          这是我在取消按钮上的 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;
              }
          }
          

          }

          【讨论】:

            【解决方案14】:

            我正在使用日期选择器、时间选择器和数字选择器。每当用户选择一个数字时,数字选择器就会调用 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,所以现在无论是否存在错误,它都可以工作。

            【讨论】:

              【解决方案15】:

              我喜欢上面 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));
                  }
              }
              

              【讨论】:

              • 这与上面 The Sea 的答案非常相似。
              【解决方案16】:

              我使用 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);
                  }
              }
              

              【讨论】:

                【解决方案17】:

                对于 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。

                【讨论】:

                  【解决方案18】:

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

                  【讨论】:

                    【解决方案19】:

                    我知道这篇文章已经发布了将近一年,但我认为我应该发布我的发现。 您仍然可以保留侦听器(而不是将其设置为仔细考虑)并且仍然可以按预期进行这项工作。关键是隐式设置“确定”或(和)“取消”按钮。我对其进行了测试,它对我来说非常有用。侦听器不会被触发两次。

                    看看这个例子,

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

                    【讨论】:

                    • 简单明了,它不像你说的那样工作。无论您在对话中做什么,timePickerListener 仍会被调用。我什至不需要测试就知道,你只需要看看sources:如果你不将它设置为nulltryNotifyTimeSet()会在它的onClick()中调用监听器的onTimeSet()onStop()
                    • 换句话说,不要不要让本地类持有对您的侦听器的引用(即,在构造函数中传递它)。你如何处理按钮无关紧要。
                    • 嗨大卫,你没有测试这个,你认为它不会工作。这是我目前在我的应用程序中使用的确切代码,它的工作原理就像一个冠军。
                    • 1) 我从来没有说过它不起作用。我说它不像你说的那样起作用。请再读一遍。 2) 您违反了 LSP 和 SRP,浪费工时来不必要地更改 all 您的 整个 客户端逻辑,而这些逻辑不需要任何更改。 3)您的答案解决了问题,是的,否则我会将其标记为“不是答案”,但 4)您的答案仍然(很抱歉)非常效率低下并且没有解决基本设计问题(称为侦听器),因此只有反对票。 6)您禁用了 使其成为强制布尔值的模式窗口。所以,那里有 6 个严重的问题!
                    猜你喜欢
                    • 2012-08-22
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-11-03
                    • 1970-01-01
                    • 2012-11-20
                    • 2014-10-07
                    • 2019-10-04
                    • 2019-10-18
                    相关资源
                    最近更新 更多