Jelly Bean DatePickerDialog - чи є спосіб скасувати?


148

--- Примітка для модераторів: Сьогодні (15 липня), я помітив , що хтось - то вже стикався з цією проблемою тут . Але я не впевнений, чи доречно закрити це як дублікат, оскільки я думаю, що я надав набагато краще пояснення цього питання. Я не впевнений, чи варто мені редагувати інше запитання і вставляти цей вміст туди, але мені не зручно занадто сильно змінювати чуже питання. ---

У мене тут щось дивне .

Я не думаю, що проблема залежить від того, проти якого SDK ви будуєте. Важлива версія версії ОС пристрою.

Проблема №1: непослідовність за замовчуванням

DatePickerDialogбуло змінено (?) в Jelly Bean і тепер надає лише кнопку Готово . Попередні версії включали кнопку Скасувати , і це може вплинути на досвід користувача (непослідовність, м'язова пам'ять попередніх версій Android).

Повторити: Створіть базовий проект. Помістіть цеonCreate:

DatePickerDialog picker = new DatePickerDialog(
        this,
        new OnDateSetListener() {
            @Override
            public void onDateSet(DatePicker v, int y, int m, int d) {
                Log.d("Picker", "Set!");
            }
        },
        2012, 6, 15);
picker.show();

Очікуваний: Скасувати кнопку з'явиться в діалоговому вікні.

Струм: Скасування кнопки не відображається.

Знімки екрана: 4.0.3 (гаразд) та 4.1.1 (можливо, неправильно?)

Завдання №2: неправильна поведінка відхилення

Діалогове виклик будь-якого слухача він дійсно повинен дзвонити, а потім завжди викликає OnDateSetListenerслухача. Скасування все ще викликає встановлений метод, а його встановлення викликає метод двічі.

Повторити: Використовуйте код №1, але додайте код нижче (ви побачите це рішення №1, але лише візуально / інтерфейс користувача):

picker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", 
        new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.d("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не має значення. Я спростив проблему для вас, але я її протестував.

Вітаємо, начебто ви знайшли помилку в Android. Ви можете повідомити про це тут .
Майкл Хемптон

1
Дуже добре написаний звіт про помилку. Я можу це зрозуміти повністю без необхідності тестувати запуск коду.
Cheok Yan Cheng

6
Я закликаю всіх проголосувати за це питання! Випуск 34833
Бредлі

Не могли б ви просто змінити функцію кнопки, щоб діяти так, ніби вона була відхилена через дотик поза діалоговим вікном?
Karthik Balakrishnan

2
Помилка все ще відкрита через 2 роки ... неймовірно.
erdomester

Відповіді:


115

Примітка: Виправлено на Lollipop , джерело тут . Також оновлено автоматизований клас для використання у клієнтах (сумісний із усіма версіями Android).

TL; DR: 1-2-3 легких кроки для глобального рішення:

  1. Завантажте цей клас.
  2. Реалізуйте OnDateSetListenerу своїй діяльності (або змініть клас відповідно до ваших потреб).
  3. Запустіть діалогове вікно з цим кодом (у цьому прикладі я його використовую всередині a Fragment):

    Bundle b = new Bundle();
    b.putInt(DatePickerDialogFragment.YEAR, 2012);
    b.putInt(DatePickerDialogFragment.MONTH, 6);
    b.putInt(DatePickerDialogFragment.DATE, 17);
    DialogFragment picker = new DatePickerDialogFragment();
    picker.setArguments(b);
    picker.show(getActivity().getSupportFragmentManager(), "frag_date_picker");

І це все, що потрібно! Причина, по якій я все ще зберігаю свою відповідь як "прийнята", полягає в тому, що я все ж віддаю перевагу своєму рішенню, оскільки він має дуже невеликий слід у клієнтському коді, він вирішує основну проблему (слухача викликають у рамках фреймворку), працює добре під час зміни конфігурації і він спрямовує логіку коду до реалізації за замовчуванням у попередніх версіях Android, які не зазнають цієї помилки (див. джерело класу).

Оригінальна відповідь (зберігається з історичних та дидактичних причин):

Джерело помилок

Гаразд, схоже, що це справді помилка, і хтось уже її заповнив. Випуск 34833 .

Я виявив, що проблема, можливо, в DatePickerDialog.java. Де він читається:

private void tryNotifyDateSet() {
    if (mCallBack != null) {
        mDatePicker.clearFocus();
        mCallBack.onDateSet(mDatePicker, mDatePicker.getYear(),
                mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
    }
}

@Override
protected void onStop() {
    tryNotifyDateSet();
    super.onStop();
}

Я б здогадався, що це могло бути:

@Override
protected void onStop() {
    // instead of the full tryNotifyDateSet() call:
    if (mCallBack != null) mDatePicker.clearFocus();
    super.onStop();
}

Тепер, якщо хтось може сказати мені, як я можу запропонувати звіт про виправлення / помилку для Android, я буду радий. Тим часом я запропонував можливе виправлення (просте) як додану версію DatePickerDialog.javaвипуску там.

Концепція, щоб уникнути помилок

Встановіть слухача nullв конструктор і створіть свою власну BUTTON_POSITIVEкнопку згодом . Це все, деталі нижче.

Проблема виникає через те DatePickerDialog.java, що , як ви бачите у джерелі, викликає глобальну змінну ( mCallBack), яка зберігає слухача, переданого в конструкторі:

    /**
 * @param context The context the dialog is to run in.
 * @param callBack How the parent is notified that the date is set.
 * @param year The initial year of the dialog.
 * @param monthOfYear The initial month of the dialog.
 * @param dayOfMonth The initial day of the dialog.
 */
public DatePickerDialog(Context context,
        OnDateSetListener callBack,
        int year,
        int monthOfYear,
        int dayOfMonth) {
    this(context, 0, callBack, year, monthOfYear, dayOfMonth);
}

    /**
 * @param context The context the dialog is to run in.
 * @param theme the theme to apply to this dialog
 * @param callBack How the parent is notified that the date is set.
 * @param year The initial year of the dialog.
 * @param monthOfYear The initial month of the dialog.
 * @param dayOfMonth The initial day of the dialog.
 */
public DatePickerDialog(Context context,
        int theme,
        OnDateSetListener callBack,
        int year,
        int monthOfYear,
        int dayOfMonth) {
    super(context, theme);

    mCallBack = callBack;
    // ... rest of the constructor.
}

Отже, хитрість полягає в тому, щоб забезпечити nullзбереження слухача як слухача, а потім прокрутити власний набір кнопок (нижче - початковий код №1, оновлений):

    DatePickerDialog picker = new DatePickerDialog(
        this,
        null, // instead of a listener
        2012, 6, 15);
    picker.setCancelable(true);
    picker.setCanceledOnTouchOutside(true);
    picker.setButton(DialogInterface.BUTTON_POSITIVE, "OK",
        new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.d("Picker", "Correct behavior!");
            }
        });
    picker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", 
        new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.d("Picker", "Cancel!");
            }
        });
picker.show();

Тепер він буде працювати через можливу корекцію, яку я розмістив вище.

А оскільки DatePickerDialog.javaперевіряє наявність nullкожного разу, коли він зчитується mCallback( починаючи з днів API 3 / 1.5, здається, --- не може перевірити стільник), це не призведе до виключення. Зважаючи на те, що Lollipop виправив проблему, я не збираюся її вивчати: просто використовуйте реалізацію за замовчуванням (розглянуто в наданому мені класі).

Спочатку я боявся не зателефонувати clearFocus(), але я протестував тут і лінії Журналу були чистими. Тож ця запропонована мною лінія може навіть не знадобитися, але я не знаю.

Сумісність з попередніми рівнями API (відредаговано)

Як я зазначив у коментарі нижче, це було поняттям, і ви можете завантажити клас, який я використовую, з мого облікового запису Диска Google . Як я використовував, за замовчуванням реалізація системи використовується для версій, на які не впливає помилка.

Я взяв кілька припущень (назви кнопок тощо), які підходять для моїх потреб, тому що я хотів звести код котла в класах клієнтів до мінімуму. Повний приклад використання:

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

11
Дивовижна робота з дослідження! Сумно, що це було потрібно, але все-таки дивовижно.
CommonsWare

1
Дуже хороша! Я стукнув головою об стіну з цього питання, що оновлював мої текстові поля та закликав / не викликав правильних зворотних зворотних дзвінків. Дякую! Цікаво, чи запропонований спосіб вирішення є "доказом у майбутньому" чи, на вашу думку, це призведе до проблем, коли помилка виправлена?
проміжок

1
@RomainGuidoux див. Оновлену відповідь наприкінці. Клас у посиланні має розумний виклик цього методу лише в квасолі. У будь-якому, що нижче, це обійде це рішення і використовувати системну реалізацію за замовчуванням, перенаправляючи системний виклик для того, щоб перейти до вашої діяльності. Просто в желейному квасолі вживаються додаткові заходи (уникаючи помилок) перед маршрутизацією зворотного дзвінка, і це включає виклик цього методу соти +. Але знову ж таки, лише в JB.
davidcsb

1
Дивовижна робота тут. Я не маю слів про те, наскільки смішним є це питання.
Білл Філліпс

5
Як щодо TimePickerDialog? Схоже, у TimePickerDialog немає getTimePicker ()
lokoko

16

Я додам свій власний риф на рішення, яке розміщував Девід Сезаріно, якщо ви не використовуєте фрагменти, і хочете простий спосіб виправити його у всіх версіях (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.okі android.R.string.cancelполя, а не власні. І дякую за відповідь.
davidcsb

Ах, приємно, я не помітив, що вони надали ОК та Скасувати текст. Дякую!
dmon

отримання виключення nullpointer у супер (контекст, тема, null, dateToShow.get (YEAR), dateToShow.get (MONTH), dateToShow.get (DAY_OF_MONTH));
Кішор

Errr ... ти передаєш нуль dateToShow? Інша нуль насправді є "виправити", так що має бути там. На якій версії ви працюєте?
dmon

чи можете ви, будь ласка, дайте мені знати, для кого importце було Field? У мене є 6 варіантів, і жоден з них не працював.
Аділ Малик

8

Поки помилка не буде виправлена, я пропоную не використовувати 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 на основі рішення Девіда Сезаріно, "TL; DR: 1-2-3 мертвих легких кроки для глобального рішення"

TimePickerDialog не забезпечує такі функції, як DatePickerDialog.getDatePicker. Отже, слухача OnTimeSetListener потрібно забезпечити. Тільки щоб зберегти подібність з рішенням вирішення проблеми DatePicker, я підтримував стару концепцію mlistener. Ви можете змінити його, якщо потрібно.

Виклик і слухач - це те саме, що і оригінальне рішення. Просто включіть

import android.app.TimePickerDialog;
import android.app.TimePickerDialog.OnTimeSetListener;

розширити батьківський клас,

... implements OnDateSetListener, OnTimeSetListener

Реалізація

 @Override
 public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
 ...
 }

Приклад дзвінка

    Calendar cal = Calendar.getInstance();
    int hour = cal.get(Calendar.HOUR_OF_DAY);
    int minute = cal.get(Calendar.MINUTE);


    Bundle b = new Bundle();
    b.putInt(TimePickerDialogFragment.HOUR, hour);
    b.putInt(TimePickerDialogFragment.MINUTE, minute);

    DialogFragment picker = new TimePickerDialogFragment();
    picker.setArguments(b);
    picker.show(getSupportFragmentManager(), "frag_time_picker");

(Оновлено для обробки скасування)

public class TimePickerDialogFragment extends DialogFragment {

    public static final String HOUR = "Hour";
    public static final String MINUTE = "Minute";

    private boolean isCancelled = false; //Added to handle cancel
    private TimePickerDialog.OnTimeSetListener mListener;

    //Added to handle parent listener
    private TimePickerDialog.OnTimeSetListener mTimeSetListener = new TimePickerDialog.OnTimeSetListener() {
        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
            if (!isCancelled)
            {
                mListener.onTimeSet(view,hourOfDay,minute);
            }
        }
    };
    //
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        this.mListener = (TimePickerDialog.OnTimeSetListener) activity;
    }

    @Override
    public void onDetach() {
        this.mListener = null;
        super.onDetach();
    }

    @TargetApi(11)
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Bundle b = getArguments();
        int h = b.getInt(HOUR);
        int m = b.getInt(MINUTE);

        final TimePickerDialog picker = new TimePickerDialog(getActivity(), getConstructorListener(), h, m,DateFormat.is24HourFormat(getActivity()));

        //final TimePicker timePicker = new TimePicker(getBaseContext());
        if (hasJellyBeanAndAbove()) {
            picker.setButton(DialogInterface.BUTTON_POSITIVE,
                    getActivity().getString(android.R.string.ok),
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            isCancelled = false; //Cancel flag, used in mTimeSetListener
                        }
                    });
            picker.setButton(DialogInterface.BUTTON_NEGATIVE,
                    getActivity().getString(android.R.string.cancel),
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            isCancelled = true; //Cancel flag, used in mTimeSetListener
                        }
                    });
        }
        return picker;
    }
    private boolean hasJellyBeanAndAbove() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
    }

    private TimePickerDialog.OnTimeSetListener getConstructorListener() {
        return hasJellyBeanAndAbove() ? mTimeSetListener : mListener; //instead of null, mTimeSetListener is returned.
    }
}

Це не працює для мене при спробі його на s3 API 18
AbdelHady

3

У випадку, якщо хтось хоче швидкого вирішення, ось код, який я використав:

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

Ось повний підручник на випадок, коли вас цікавить.


Це чудово працювало для мене. Легко зрозуміти, легко здійснити! Перевірено 4.4
erdomester

3

Моє просте рішення. Коли ви хочете знову запустити його, просто запустіть "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, немає помилки, про яку йдеться у всьому цьому питанні, і зважаючи на те, що ми хочемо прийняти другий виклик, код не запуститься, якщо ми не зробимо цю перевірку, повірте, я спробував код на Емуляторах та реальних пристроях (з різними версіями) кілька разів, і він працює як шарм
AbdelHady

Я не впевнений, чи варто це зробити головою. Я опублікував це 2 роки тому, це було в нашому комерційному додатку з тих пір працює просто чудово. Жодних помилок, про які повідомляли наші команди з питань якості та наші тисячі користувачів. Це має бути простою відповіддю, яку люди можуть поширити. Викличте "resetFired", коли ви хочете, щоб він знову запустився.
Даніель Райан

У нашому додатку "isJellyBeanOrAbove" не потрібен. Додаток буде просто працювати у всіх версіях, якщо ви викличете "resetFired" у правильних областях.
Даніель Райан

3

Відповідно до блискучої відповіді Анкура Чаддхарі на подібне TimePickerDialogпитання, якщо ми перевірили всередині, onDateSetчи надано подання isShown()чи ні, воно вирішить усе питання мінімальними зусиллями, не потребуючи розширення вибору або перевірки на якісь огидні прапори навколо коду або навіть перевірка версії ОС, просто виконайте наступне:

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

і, звичайно, можна зробити те onTimeSetсаме, що відповідає відповіді Анкура


1
Найкраща відповідь з усіх інших!
hiew1

2

Як я впорався з цією ситуацією, використовував прапор і переосмислив методи 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);
    }
}

1

Існує дуже просте вирішення, якщо ваша програма не використовує панель дій. Зауважте, до речі, що деякі додатки покладаються на цю функціональність, тому що скасування вибору дати має особливе значення (наприклад, воно очищає поле дати в порожній рядок, що для деяких додатків є правильним і значущим типом введення ) та використання булевих прапорів для запобігання встановленню дати в два рази на OK не допоможе вам у цьому випадку.

Re. фактичне виправлення, вам не потрібно створювати нові кнопки чи власне діалогове вікно. Сенс у тому, щоб бути сумісним як із старішими версіями Android, так і з баггі (4. ) та будь-якими майбутніми, хоча в останньому, звичайно, неможливо бути впевненим. Зауважте, що в Android 2. онStop () для android.app.Dialog взагалі нічого не робить, а в 4. * це робить mActionBar.setShowHideAnimationEnabled (помилково), що важливо лише у тому випадку, якщо у вашому додатку є панель дій. OnStop () у DatePickerDialog, який успадковується від Dialog, лише сприяє mDatePicker.clearFocus () (станом на останню версію до джерел Android 4.3), що не здається істотним.

Тому заміна OnStop () методом, який нічого не робить, повинен у багатьох випадках виправляти вашу програму та гарантувати, що вона залишиться такою в осяжному майбутньому. Таким чином, просто розширіть клас DatePickerDialog власним і перезавантажте onStop () методом манекена. Вам також доведеться надати одного або двох конструкторів, відповідно до ваших вимог. Зауважте також, що не варто спокушатися намагатися перестаратися з цим виправленням, наприклад, намагаючись зробити щось із панелі активності безпосередньо, оскільки це обмежить вашу сумісність лише для останніх версій Android. Також зауважте, що було б непогано мати можливість зателефонувати на супер для DatePicker's onStop (), оскільки помилка є лише у OnStop () у самому DatePickerDialog, але не у суперкласі DatePickerDialog. Однак для цього потрібно зателефонувати super.super.onStop () зі свого користувацького класу, що Java не дозволить вам робити, оскільки це суперечить філософії інкапсуляції :) Нижче наведено мій маленький клас, який я використовував для того, щоб перечитати DatePickerDialog. Сподіваюся, цей коментар комусь буде корисний. Войтек Ярош

public class myDatePickerDialog extends DatePickerDialog {

public myDatePickerDialog(Context context, OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
    super(context, callBack, year, monthOfYear, dayOfMonth);
}

@Override
protected void onStop() {
    // Replacing tryNotifyDateSet() with nothing - this is a workaround for Android bug https://android-review.googlesource.com/#/c/61270/A

    // Would also like to clear focus, but we cannot get at the private members, so we do nothing.  It seems to do no harm...
    // mDatePicker.clearFocus();

    // Now we would like to call super on onStop(), but actually what we would mean is super.super, because
    // it is super.onStop() that we are trying NOT to run, because it is buggy.  However, doing such a thing
    // in Java is not allowed, as it goes against the philosophy of encapsulation (the Creators never thought
    // that we might have to patch parent classes from the bottom up :)
    // However, we do not lose much by doing nothing at all, because in Android 2.* onStop() in androd.app.Dialog //actually
    // does nothing and in 4.* it does:
    //      if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); 
    // which is not essential for us here because we use no action bar... QED
    // So we do nothing and we intend to keep this workaround forever because of users with older devices, who might
    // run Android 4.1 - 4.3 for some time to come, even if the bug is fixed in later versions of Android.
}   

}


Навіть якщо використовується ActionBar, це може бути прийнятним рішенням, якщо ви ніколи не ховаєте / не показуєте ActionBar. Щоб зробити речі надзвичайно безпечними, ви можете змінити функцію onStart, щоб нічого не робити (тоді дзвінки для анімації були б безпечно зняті). І навіть якщо ви ховаєте / показуєте це, анімація просто відключена.
Даніель

0


Спробуйте наведені нижче поняття.

DatePickerDialog picker = new DatePickerDialog(
        this,
        new OnDateSetListener() {
            @Override
            public void onDateSet(DatePicker v, int y, int m, int d) {
                Log.d("Picker", "Set!");
            }
        },
        2012, 6, 15);
picker.show();


метод onDateSet () викликає двічі (якщо ви перевіряєте emulator.it дзвінки двічі. Якщо ви використовуєте реальний пристрій, то він зателефонує правильно один раз. Якщо ви використовуєте емулятор, тоді використовуйте counter. якщо ви працюєте в реальному пристрої, то ігноруйте лічильник змінної. Для реального пристрою працює для мене.),
коли користувач натискає кнопку в DatePickerDialog.
для цього вам слід підтримувати значення лічильника і нічого не робити, коли mothod викликає перший раз і виконує операцію, коли метод викликає другий раз.
Перегляньте наведені нижче фрагменти кодування

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


Він працює для мене на справжньому пристрої. Але для емулятора він працює не правильно. Я думаю, що його андроїд-емулятор помилка.


0

Простим рішенням буде використання булевого сигналу, щоб пропустити другий запуск

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виклик методу , коли він не повинен ... він стріляє двічі є наслідком помилки.
davidcsb

Якщо я був недостатньо зрозумілий, дозвольте мені: натиснути назад, щоб скасувати діалогові дзвінки onDateSet. Таким чином, зламаний.
davidcsb

Дякуємо, що показали помилку. Я редагую відповідь, щоб вона працювала з кнопкою "назад". Ваша відповідь працює, але DatePicker dp = picker.getDatePicker (); не працює з TimePickers, оскільки метод getTimePicker () не доданий. Отже, це була б
вагома

Як ми намагаємось пропустити другий запуск? !!, я пробував це кілька разів, при закритті діалогового вікна будь-якими способами onDateSetбуде викликатися один раз, але при виборі "зроблено" або "встановити", тоді він буде викликаний двічі. Для цього нам потрібно пропустити лише перший, тому, якщо він викликається двічі, то & тільки тоді у нас є правильна дата
AbdelHady

0

Ви можете замінити 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
    }
}

0

Ось мій обхідний спосіб для 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;
    }
}

}


0

Я використовую підбирачі дат, підбирачі часу та вибральники номерів. Вибір номерів викликає 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 діалогового вікна, тому тепер він працює, чи є помилка чи ні.


0

Мені сподобалась відповідь Девіда Сезаріно вище, але я хотів щось, що було краплею заміни для розірваного діалогового діалогу і працював би на будь-якому діалоговому вікні, у якому може бути відсутнє скасування / неправильне поведінка скасування. Тут виведені класи для DatePickerDialog / TimePickerDialog, які повинні працювати як заміни. Це не власні перегляди. Він використовує системне діалогове вікно, але просто змінює поведінку кнопки скасування / повернення, щоб вона працювала так, як очікувалося.

Це повинно працювати на рівні 3 і вище API. Отже, в основному будь-яка версія Android (я тестував її на медузах та льодяниках спеціально).

DatePickerDialog:

package snappy_company_name_here;

import android.content.Context;
import android.content.DialogInterface;
import android.widget.DatePicker;

/**
 * This is a modified version of DatePickerDialog that correctly handles cancellation behavior since it's broken on jellybean and
 * kitkat date pickers.
 *
 * Here is the bug: http://code.google.com/p/android/issues/detail?id=34833
 * Here is an SO post with a bunch of details: http://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));
    }
}

Це досить схоже на відповідь моря вище.
stuckj

0

Моя робоча версія з ClearButton за допомогою Lambda Expressions:

public class DatePickerFragment extends DialogFragment {
    private OnDateSelectListener dateSelectListener;
    private OnDateClearListener dateClearListener;

    public void setDateSelectListener(OnDateSelectListener dateSelectListener) {
        this.dateSelectListener = dateSelectListener;
    }

    public void setDateClearListener(OnDateClearListener dateClearListener) {
        this.dateClearListener = dateClearListener;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the current date as the default date in the picker
        final Calendar c = Calendar.getInstance();
        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH);
        int day = c.get(Calendar.DAY_OF_MONTH);

        // Create a new instance of DatePickerDialog and return it
        DatePickerDialog dialog = new DatePickerDialog(getActivity(), null, year, month, day);
        dialog.setCancelable(true);
        dialog.setCanceledOnTouchOutside(true);
        dialog.setTitle("Select Date");
        dialog.setButton(BUTTON_POSITIVE, ("Done"), (dialog1, which) -> {
            DatePicker dp = dialog.getDatePicker();
            dialog.dismiss();
            dateSelectListener.onDateSelect(dp.getYear(), dp.getMonth(), dp.getDayOfMonth());
        });
        dialog.setButton(BUTTON_NEUTRAL, ("Clear"), (dialog1, which) -> {
            dialog.dismiss();
            dateClearListener.onDateClear();
        });
        dialog.setButton(BUTTON_NEGATIVE, ("Cancel"), (dialog1, which) -> {
            if (which == DialogInterface.BUTTON_NEGATIVE) {
                dialog.cancel();
            }
        });
        dialog.getDatePicker().setCalendarViewShown(false);
        return dialog;
    }


    public interface OnDateClearListener {
        void onDateClear();
    }

    public interface OnDateSelectListener {
        void onDateSelect(int year, int monthOfYear, int dayOfMonth);
    }
}

0

Для TimePickerDialog рішення може бути таким:

TimePickerDialog createTimePickerDialog(Context context, int themeResId, TimePickerDialog.OnTimeSetListener orignalListener,
                                                         int hourOfDay, int minute, boolean is24HourView) {
        class KitKatTimeSetListener implements TimePickerDialog.OnTimeSetListener {
            private int hour;
            private int minute;

            private KitKatTimeSetListener() {
            }

            @Override
            public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
                this.hour = hourOfDay;
                this.minute = minute;
            }

            private int getHour() { return hour; }
            private int getMinute() {return minute; }
        };

        KitKatTimeSetListener kitkatTimeSetListener = new KitKatTimeSetListener();
        TimePickerDialog timePickerDialog = new TimePickerDialog(context, themeResId, kitkatTimeSetListener, hourOfDay, minute, is24HourView);

        timePickerDialog.setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), (dialog, which) -> {
            timePickerDialog.onClick(timePickerDialog, DialogInterface.BUTTON_POSITIVE);
            orignalListener.onTimeSet(new TimePicker(context), kitkatTimeSetListener.getHour(), kitkatTimeSetListener.getMinute());
            dialog.cancel();
        });
        timePickerDialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), (dialog, which) -> {
            dialog.cancel();
        });

        return timePickerDialog;
    }

Я делегую всі події для обгортки KitKatSetTimeListener, і повертаюсь лише до оригінального OnTimeSetListener у випадку, якщо натиснуто BUTTON_POSITIVE.


0

Після тестування деяких розміщених тут пропозицій я особисто вважаю, що це рішення є найпростішим. Я передаю "null" моїм слухачем у конструкторі DatePickerDialog, а потім, коли натискаю кнопку "ОК", я закликаю свого onDateSearchSetListener:

datePickerDialog = new DatePickerDialog(getContext(), null, dateSearch.get(Calendar.YEAR), dateSearch.get(Calendar.MONTH), dateSearch.get(Calendar.DAY_OF_MONTH));
    datePickerDialog.setCancelable(false);
    datePickerDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            Log.d("Debug", "Correct");
            onDateSearchSetListener.onDateSet(datePickerDialog.getDatePicker(), datePickerDialog.getDatePicker().getYear(), datePickerDialog.getDatePicker().getMonth(), datePickerDialog.getDatePicker().getDayOfMonth());
        }
    });
    datePickerDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            Log.d("Debug", "Cancel");
            dialog.dismiss();
        }
    });

-1

Я знаю, що ця посада тут вже майже рік, але я вважав, що повинен розмістити свої висновки. Ви все ще можете утримувати слухача (замість того, щоб він налаштовував звук) і все одно мати цю роботу як слід. Ключ полягає в тому, щоб неявно встановити кнопки «ОК» або (і) «скасувати». Я перевірив це, і він працює вдячно для мене. Слухач не звільняється двічі.

Подивіться на цей приклад,

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все ще називається незалежно від того, що ви робите в діалоговому вікні. Мені навіть не потрібно тестувати, щоб це знати, вам просто потрібно подивитися джерела : якщо ви не встановите це null, tryNotifyTimeSet()зателефонуйте слухачам onTimeSet()і в його, onClick()і в onStop().
davidcsb

Іншими словами, не дозволяйте рідному класу мати посилання на свого слухача (тобто передаючи його в конструктор). Те, як ти поводишся з кнопками, не має значення.
davidcsb

Привіт, Девіде, ти цього не тестував, і ти вважав, що це не буде спрацьовувати. Це точний фрагмент коду, який я зараз використовую у своєму додатку, і він працює як чемпіон.
Крокодил

1) Я ніколи не говорив, що це не працює. Я сказав, що це не працює так, як ти сказав. Будь ласка, прочитайте ще раз. 2) ви порушили LSP і SRP, витрачати людино-годин без необхідності змінити всю вашу всю клієнтську логіку , яка не вимагає ніяких змін , щоб почати с. 3) ваша відповідь стосується питання, так, інакше я б позначив це для видалення як "не відповідь", але 4) ваша відповідь все ще (вибачте за це) дуже неефективна і не стосується принципового питання дизайну (слухач закликав ), отже, лише потік. 6) ви відключили назад і зробили його модальним вікном, щоб примусити булеву. Отже, 6 серйозних питань там!
davidcsb
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.