Jelly Bean DatePickerDialog - czy jest sposób, aby anulować?

148

--- Uwaga do moderatorów: Dzisiaj (15 lipca), zauważyłem, że ktoś już w obliczu tego problemu tutaj . Ale nie jestem pewien, czy należy zamknąć to jako duplikat, ponieważ myślę, że podałem znacznie lepsze wyjaśnienie problemu. Nie jestem pewien, czy powinienem edytować drugie pytanie i wkleić tam tę zawartość, ale nie czuję się komfortowo, aby zbytnio zmieniać czyjeś pytanie. ---

Mam tu coś dziwnego .

Nie sądzę, że problem zależy od tego, na podstawie którego zestawu SDK tworzysz. Liczy się wersja systemu operacyjnego urządzenia.

Problem 1: domyślna niespójność

DatePickerDialogzostał zmieniony (?) w Jelly Bean i teraz zawiera tylko przycisk Gotowe . Poprzednie wersje zawierały przycisk Anuluj , co może wpływać na wrażenia użytkownika (niespójność, pamięć mięśniowa z poprzednich wersji Androida).

Replikuj: utwórz podstawowy projekt. Umieść to wonCreate:

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

Oczekiwany: Anuluj przycisk, aby pojawić się w oknie dialogowym.

Prąd: Anuluj przycisk nie pojawia.

Zrzuty ekranu: 4.0.3 (OK) i 4.1.1 (prawdopodobnie źle?).

Problem nr 2: złe zachowanie zwalniające

Dialog dzwoni do każdego słuchacza, do którego ma zadzwonić, a następnie zawsze dzwoni do OnDateSetListenersłuchacza. Anulowanie nadal wywołuje metodę set, a ustawienie jej wywołuje metodę dwukrotnie.

Replikuj: użyj kodu nr 1, ale dodaj kod poniżej (zobaczysz, że rozwiązuje to numer 1, ale tylko wizualnie / UI):

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

Spodziewany:

  • Naciśnięcie klawisza WSTECZ lub kliknięcie poza oknem dialogowym nie powinno nic zrobić .
  • Naciśnięcie "Anuluj" powinno wydrukować Picker Anuluj! .
  • Naciśnięcie „Set” powinno wydrukować Picker Set! .

Obecny:

  • Naciśnięcie klawisza BACK lub kliknięcie poza oknem dialogowym powoduje wydrukowanie zestawu Picker Set! .
  • Naciśnięcie przycisku „Anuluj” powoduje wydrukowanie Picker Cancel! a potem Picker Set! .
  • Naciśnięcie "Set" powoduje wydrukowanie Picker Set! a potem Picker Set! .

Wiersze dziennika przedstawiające zachowanie:

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!

Inne uwagi i komentarze

  • Owinięcie go wokół DatePickerFragmentnie ma znaczenia. Uprościłem dla ciebie problem, ale przetestowałem go.
davidcsb
źródło
Gratulacje, wydaje się, że znalazłeś błąd w Androidzie. Możesz to zgłosić tutaj .
Michael Hampton,
1
Bardzo dobrze napisany raport o błędzie. Rozumiem to całkowicie bez konieczności testowania uruchomienia kodu.
Cheok Yan Cheng,
6
Wzywam wszystkich do głosowania w tej sprawie! Wydanie 34833
Bradley
Czy nie mógłbyś po prostu nadpisać funkcji przycisku, aby zachowywała się tak, jakby została odrzucona z powodu dotknięcia poza oknem dialogowym?
Karthik Balakrishnan
2
Bug jest nadal otwarty po 2 latach ... niewiarygodne.
erdomester

Odpowiedzi:

115

Uwaga: Naprawiono od Lollipop , źródło tutaj . Zautomatyzowana klasa do użytku w klientach (kompatybilna ze wszystkimi wersjami Androida) również została zaktualizowana.

TL; DR: 1-2-3 martwe łatwe kroki dla globalnego rozwiązania:

  1. Pobierz klasę.
  2. Zaimplementuj OnDateSetListenerw swojej działalności (lub zmień klasę, aby odpowiadała Twoim potrzebom).
  3. Uruchom okno dialogowe za pomocą tego kodu (w tym przykładzie używam go wewnątrz 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");
    

I to wszystko, czego potrzeba! Powodem, dla którego nadal utrzymuję moją odpowiedź jako „zaakceptowaną”, jest to, że nadal wolę moje rozwiązanie, ponieważ ma bardzo mały ślad w kodzie klienta, rozwiązuje podstawowy problem (wywołanie odbiornika w klasie frameworka), działa dobrze przy zmianach konfiguracji i kieruje logikę kodu do domyślnej implementacji w poprzednich wersjach Androida, które nie są nękane przez ten błąd (zobacz źródło klasy).

Oryginalna odpowiedź (zachowana ze względów historycznych i dydaktycznych):

Źródło błędu

OK, wygląda na to, że to rzeczywiście błąd i ktoś już go wypełnił. Wydanie 34833 .

Odkryłem, że problem prawdopodobnie tkwi w DatePickerDialog.java. Gdzie brzmi:

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

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

Myślę, że mogło to być:

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

Teraz, jeśli ktoś może mi powiedzieć, jak mogę zaproponować raport o poprawce / błędzie do Androida, z przyjemnością. W międzyczasie zasugerowałem możliwą poprawkę (prostą) jako załączoną wersję DatePickerDialog.javaw tamtym numerze.

Koncepcja, aby uniknąć błędu

Ustaw odbiornik nullw konstruktorze na, a BUTTON_POSITIVEpóźniej utwórz własny przycisk . To wszystko, szczegóły poniżej.

Problem DatePickerDialog.javapojawia się, ponieważ , jak widać w źródle, wywołuje zmienną globalną ( mCallBack), która przechowuje odbiornik przekazany w konstruktorze:

    /**
 * @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.
}

Tak więc sztuczka polega na zapewnieniu nullsłuchacza, który będzie przechowywany jako słuchacz, a następnie zwinięcie własnego zestawu przycisków (poniżej znajduje się oryginalny kod z # 1, zaktualizowany):

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

Teraz będzie działać z uwagi na ewentualną korektę, którą zamieściłem powyżej.

A ponieważ DatePickerDialog.javasprawdza, czy za nullkażdym razem, gdy czyta mCallback( od czasów API 3 / 1.5 wydaje się - nie może oczywiście sprawdzić Honeycomb), nie wywoła wyjątku. Biorąc pod uwagę, że Lollipop naprawił problem, nie zamierzam się tym zajmować: po prostu użyj domyślnej implementacji (uwzględnionej w podanej przeze mnie klasie).

Na początku bałam się, że nie zadzwonię clearFocus(), ale testowałem tutaj i linie dziennika były czyste. Więc ta linia, którą zaproponowałem, może mimo wszystko nie być konieczna, ale nie wiem.

Zgodność z poprzednimi poziomami API (edytowana)

Jak wskazałem w komentarzu poniżej, była to koncepcja i możesz pobrać klasę, której używam z mojego konta Dysku Google . Sposób, w jaki użyłem, domyślna implementacja systemu jest używana w wersjach, na które nie ma wpływu błąd.

Przyjąłem kilka założeń (nazwy przycisków itp.), Które są odpowiednie dla moich potrzeb, ponieważ chciałem zredukować standardowy kod w klasach klienta do minimum. Przykład pełnego wykorzystania:

class YourActivity extends SherlockFragmentActivity implements OnDateSetListener

// ...

Bundle b = new Bundle();
b.putInt(DatePickerDialogFragment.YEAR, 2012);
b.putInt(DatePickerDialogFragment.MONTH, 6);
b.putInt(DatePickerDialogFragment.DATE, 17);
DialogFragment picker = new DatePickerDialogFragment();
picker.setArguments(b);
picker.show(getActivity().getSupportFragmentManager(), "fragment_date_picker");
David Cesarino
źródło
11
Świetna praca na badaniach! Smutne, że było to konieczne, ale mimo to niesamowite.
CommonsWare
1
Bardzo dobrze! Uderzałem głową w ścianę w związku z tym problemem, który aktualizował moje pola tekstowe i dzwonił / nie wywoływał poprawnych wywołań zwrotnych. Dziękuję Ci! Zastanawiam się, czy sugerowane obejście jest „odporne na przyszłość”, czy też uważasz, że spowoduje problemy po naprawieniu błędu?
obejmuje
1
@RomainGuidoux zobacz zaktualizowaną odpowiedź na końcu. Klasa w odsyłaczu ma spryt, aby wywoływać tę metodę tylko w Jelly Bean. Na wszystkim poniżej ominie to i użyje domyślnej implementacji systemu, kierując wywołanie systemowe dla ondateset do Twojej aktywności. Po prostu w przypadku Jelly Bean przed skierowaniem wywołania zwrotnego wymagane są dodatkowe środki (unikanie błędu), a to obejmuje wywołanie metody Honeycomb +. Ale znowu tylko w JB.
davidcsb,
1
Świetna praca tutaj. Nie mam słów na określenie, jak absurdalny jest ten problem.
Bill Phillips,
5
A co z TimePickerDialog? Wydaje się TimePickerDialog nie posiada getTimePicker ()
lokoko
16

Dodam swój własny riff do rozwiązania, które opublikował David Cesarino, na wypadek, gdybyś nie korzystał z Fragments i chciałbyś łatwo naprawić to we wszystkich wersjach (2.1 do 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*/ }
  }
}
dmon
źródło
Pamiętaj tylko: jeśli na stałe okablujesz nazwy przycisków na ok i anuluj, prawdopodobnie lepiej będzie użyć standardu android.R.string.oki android.R.string.cancelpól zamiast własnych. Dziękuję za odpowiedź.
davidcsb
Ach, fajnie, nie zauważyłem, że podali tekst OK i Anuluj. Dzięki!
dmon
pobieranie wyjątku nullpointer w super (kontekst, motyw, null, dateToShow.get (YEAR), dateToShow.get (MIESIĄC), dateToShow.get (DAY_OF_MONTH));
Kishore
Errr ... czy przechodzisz przez zero dateToShow? Drugą wartością zerową jest w rzeczywistości „poprawka”, więc powinna tam być. Którą wersję używasz?
dmon
czy możesz mi dać znać, dla którego importmasz Field? Mam 6 opcji i żadna z nich nie zadziałała.
Adil Malik,
8

Do czasu naprawienia błędu sugeruję nie używać DatePickerDialog ani TimePickerDialog. Użyj niestandardowego AlertDialog z widgetem TimePicker / DatePicker;

Zmień TimePickerDialog z;

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

Zmień DatePickerDialog z;

    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();
cirit
źródło
4

Ten dla TimePicker oparty na rozwiązaniu Davida Cesarino, „TL; DR: 1-2-3 martwe łatwe kroki dla globalnego rozwiązania”

TimePickerDialog nie zapewnia funkcjonalności takiej jak DatePickerDialog.getDatePicker. Tak więc odbiornik OnTimeSetListener musi być dostarczony. Aby zachować podobieństwo do rozwiązania obejścia DatePicker, zachowałem starą koncepcję mListener. W razie potrzeby możesz to zmienić.

Dzwonienie i słuchacz jest takie samo jak oryginalne rozwiązanie. Po prostu dołącz

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

rozszerz klasę rodzicielską,

... implements OnDateSetListener, OnTimeSetListener

Wprowadzić w życie

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

przykład dzwonienia

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

(Zaktualizowano w celu obsługi anulowania)

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.
    }
}
Tejasvi Hegde
źródło
Nie działa dla mnie, gdy próbuję go na s3 API 18
AbdelHady
3

Na wypadek, gdyby ktoś chciał szybko obejść, oto kod, którego użyłem:

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

}

Gdzie layout.date_picker_view jest prostym zasobem układu z DatePicker jako jedynym elementem:

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

Oto pełny samouczek, jeśli jesteś zainteresowany.

daniel_c05
źródło
To zadziałało na mnie cudownie. Łatwe do zrozumienia, łatwe do wdrożenia! Testowane 4.4
erdomester
3

Moje proste rozwiązanie. Jeśli chcesz, aby znów działał, po prostu uruchom "resetFired" (powiedz przy ponownym otwieraniu okna).

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 
    }
}
Daniel Ryan
źródło
@AbdelHady Twoja edycja kodu była nieprawidłowa, ale poprawiłem ją, aby była bardziej przejrzysta. Nie ma potrzeby dodawania metody „isJellyBeanOrAbove ()”, nie ma to żadnej korzyści, po prostu dodaje niepotrzebnej złożoności. Dzwonienie do resetFired jest tanie.
Daniel Ryan
kod tutaj jest niepoprawny, ponieważ onDateSetzostanie wywołany raz, gdy okno będzie zamykane w jakikolwiek sposób, a jeśli chodziło o ustawienie czasu, zostanie ponownie uruchomiony, więc musimy złapać drugie połączenie, a nie pierwsze tak jak ty,
AbdelHady,
Jeśli chodzi o isJellyBeanOrAbove()wersje niższe niż Jellybean, nie mają błędu, którego dotyczy całe to pytanie, a biorąc pod uwagę, że chcemy złapać drugie połączenie, kod nie uruchomi się, dopóki nie sprawdzimy tego, uwierz mi, wypróbowałem mój kod na emulatorach i prawdziwych urządzeniach (z różnymi wersjami) kilka razy i działa jak urok
AbdelHady.
Nie jestem pewien, czy warto to zlekceważyć. Opublikowałem to 2 lata temu, jest to w naszej komercyjnej aplikacji od tego czasu działa dobrze. Brak błędów zgłoszonych przez nasze zespoły kontroli jakości lub tysiące naszych użytkowników. To ma być prosta odpowiedź, którą ludzie mogą rozszerzyć. Wywołaj „resetFired”, gdy chcesz, aby ponownie odpalił.
Daniel Ryan
W naszej aplikacji „isJellyBeanOrAbove” nie jest potrzebne. Aplikacja będzie działać dobrze we wszystkich wersjach, jeśli w odpowiednich miejscach zadzwonisz „resetFired”.
Daniel Ryan
3

Zgodnie z genialną odpowiedzią Ankur Chaudhary w podobnej TimePickerDialogsprawie, jeśli sprawdzimy w środku, onDateSetczy dany widok, isShown()czy nie, rozwiąże to cały problem przy minimalnym wysiłku, bez konieczności przedłużania selektora lub sprawdzania, czy w kodzie nie ma okropnych flag. a nawet sprawdzając wersję systemu operacyjnego, wykonaj następujące czynności:

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

i oczywiście to samo można zrobić onTimeSetzgodnie z odpowiedzią Ankura

AbdelHady
źródło
1
Najlepsza odpowiedź spośród wszystkich!
hiew1
2

Sposób, w jaki poradziłem sobie z tą sytuacją, polegał na użyciu flagi i nadpisaniu metod onCancel i onDismiss.

onCancel jest wywoływany tylko wtedy, gdy użytkownik dotyka poza oknem dialogowym lub przyciskiem Wstecz. OnDismiss jest zawsze wywoływany

Ustawienie flagi w metodzie onCancel może pomóc w filtrowaniu w metodzie onDismiss intencji użytkownika: anuluj akcję lub wykonaną akcję. Poniżej kod, który przedstawia pomysł.

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);
    }
}
Alvaro
źródło
1

Istnieje bardzo proste obejście tego problemu, jeśli aplikacja nie korzysta z paska akcji. Zwróć uwagę, że niektóre aplikacje działają na tej funkcjonalności, ponieważ anulowanie selektora dat ma specjalne znaczenie (np. Czyści pole daty do pustego ciągu, który dla niektórych aplikacji jest prawidłowym i znaczącym typem danych wejściowych ) i używanie flag logicznych, aby zapobiec dwukrotnemu ustawieniu daty na OK, nie pomoże w tym przypadku.

Re. rzeczywista poprawka, nie musisz tworzyć nowych przycisków ani własnego okna dialogowego. Chodzi o to, aby być kompatybilnym zarówno ze starszymi wersjami Androida, wadliwymi (4. ), jak iz przyszłymi wersjami , choć tej ostatniej nie można oczywiście mieć pewności. Zauważ, że w Androidzie 2. onStop () dla android.app.Dialog w ogóle nic nie robi, aw 4. * robi mActionBar.setShowHideAnimationEnabled (false), co jest ważne tylko wtedy, gdy aplikacja ma pasek akcji. Metoda onStop () w DatePickerDialog, która dziedziczy po Dialog, wnosi jedynie wkład mDatePicker.clearFocus () (od najnowszej poprawki do źródeł Androida 4.3), co nie wydaje się istotne.

Dlatego zastąpienie onStop () metodą, która nic nie robi, powinno w wielu przypadkach naprawić aplikację i zapewnić, że pozostanie taką w dającej się przewidzieć przyszłości. W ten sposób po prostu rozszerz klasę DatePickerDialog o własną i przesłoń onStop () metodą fikcyjną. Będziesz musiał również zapewnić jednego lub dwóch konstruktorów, zgodnie ze swoimi wymaganiami. Należy również pamiętać, że nie należy kusić się, aby próbować przesadzić z tą poprawką, np. Próbując zrobić coś bezpośrednio z paskiem aktywności, ponieważ ograniczyłoby to twoją zgodność tylko z najnowszymi wersjami Androida. Zauważ również, że byłoby miło móc wywołać super dla metody onStop () DatePicker, ponieważ błąd występuje tylko w onStop () w samej DatePickerDialog, ale nie w superklasie DatePickerDialog. Jednak wymagałoby to wywołania super.super.onStop () z własnej klasy, na co nie pozwoli ci Java, ponieważ jest to sprzeczne z filozofią hermetyzacji :) Poniżej znajduje się moja mała klasa, w której przeglądałem DatePickerDialog. Mam nadzieję, że ten komentarz komuś się przyda. 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.
}   

}

Wojtek Jarosz
źródło
Nawet jeśli jest używany ActionBar, może to być akceptowalne obejście, jeśli nigdy nie ukryjesz / nie pokazujesz ActionBar. Aby uczynić wszystko bardziej bezpiecznym, możesz zastąpić onStart, aby również nic nie robić (wtedy wywołania animacji zostaną bezpiecznie odłączone). A nawet jeśli to ukryjesz / pokażesz, to tylko animacja jest wyłączona.
Daniel
0


Wypróbuj poniższe koncepcje.

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


metoda onDateSet () wywołuje dwa razy (jeśli sprawdzasz w emulator.it wywołuje dwa razy. jeśli używasz prawdziwego urządzenia, to jeden raz zadzwoni poprawnie. jeśli używasz emulatora, użyj licznika. jeśli pracujesz w prawdziwym urządzeniu to ignoruj ​​zmienną licznika, dla prawdziwego urządzenia działa dla mnie.),
gdy użytkownik kliknie przycisk w DatePickerDialog.
w tym celu należy zachować wartość licznika i nic nie robić, gdy mothod wywołuje pierwszy raz i wykonywać operację, gdy metoda wywołuje drugi raz.
Zapoznaj się z poniższymi fragmentami kodu

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



W przypadku anulowania dilalogu datepicker działa dla mnie, dla emulatora nie działa

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


Działa dla mnie na prawdziwym urządzeniu, ale dla emulatora nie działa poprawnie. Myślę, że to błąd emulatora Androida.

SIVAKUMAR.J
źródło
0

Prostym rozwiązaniem byłoby użycie wartości logicznej do pominięcia drugiego biegu

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();
chamikaw
źródło
Jeszcze raz, tak jak powiedziałem innemu facetowi przed tobą, to nie rozwiązuje błędu. Nie chcę zabrzmieć niegrzecznie, ale powinniście przetestować własne rozwiązanie przed wysłaniem tutaj ... tylko po to, aby udowodnić mój punkt widzenia, użyj kodu i pozwól użytkownikowi nacisnąć wstecz, aby anulować okno dialogowe i zobaczyć, co mam na myśli. Przyczyną tego błędu jest onStopwywołanie metody, kiedy nie powinien ... to dwukrotnie wypalania jest konsekwencją błędu.
davidcsb,
Jeśli nie było to wystarczająco jasne, pozwól mi: nacisnąć wstecz, aby anulować rozmowy dialogowe onDateSet. Tak więc zepsuty.
davidcsb,
Dziękuję za pokazanie błędu. Edytuję odpowiedź tak, aby działała z przyciskiem Wstecz. Twoja odpowiedź działa, ale DatePicker dp = picker.getDatePicker (); nie działa z TimePickers, ponieważ metoda getTimePicker () nie została dodana. Więc to byłaby prawidłowa odpowiedź
chamikaw
Jak próbujemy pominąć drugi bieg? !!, próbowałem tego kilka razy, przy zamykaniu okna dialog w jakikolwiek sposób onDateSetzostanie wywołany raz, ale po wybraniu „gotowe” lub „ustaw” zostanie wywołany dwukrotnie. Dlatego musimy pominąć tylko pierwszy, więc jeśli zostanie wywołany dwa razy i dopiero wtedy mamy poprawną datę
AbdelHady
0

Możesz przesłonić onCancel () i użyć setOnDismissListener () do wykrywania negatywnych działań użytkownika. A dzięki DatePickerDialog.BUTTON_POSITIVE wiesz, że użytkownik chce ustawić nową datę.

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

następnie sprawdź setDate:

public void onDateSet(DatePicker view, int year, int month, int day) {
    if(setDate){
        //do something with new date
    }
}
Markus Rubey
źródło
0

Oto moja klasa obejścia dla DatePickerDialog na przycisku anulowania, a także porzucenie go za pomocą przycisku Wstecz. Kopiuj i używaj w stylu DatePickerDialog (ponieważ odbiornik jest stanowy, musimy utworzyć nową instancję podczas używania, w przeciwnym razie potrzeba więcej kodu, aby działał)

Posługiwać się:

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

Klasa:

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

}

Loc Phan
źródło
0

Używam selektorów dat, selektorów czasu i selektorów liczb. Selektory numerów wywołują onValueChanged za każdym razem, gdy użytkownik wybierze numer, zanim selektor zostanie odrzucony, więc miałem już taką strukturę, aby zrobić coś z wartością tylko wtedy, gdy selektor zostanie zwolniony:

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

Rozszerzyłem to, aby ustawić niestandardowe narzędzia onClickListeners dla moich przycisków, z argumentem, aby zobaczyć, który przycisk został kliknięty. Teraz mogę sprawdzić, który przycisk został naciśnięty przed ustawieniem ostatecznej wartości:

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

Następnie rozszerzyłem to, aby pracować z typami daty i godziny dla selektorów daty i godziny, a także typem int dla selektorów liczb.

Opublikowałem to, ponieważ uważałem, że jest to prostsze niż niektóre z powyższych rozwiązań, ale teraz, gdy zawarłem cały kod, myślę, że nie jest to dużo prostsze! Ale ładnie pasował do struktury, którą już miałem.

Aktualizacja dla Lollipopa: Najwyraźniej ten błąd nie występuje na wszystkich urządzeniach z Androidem 4.1-4.4, ponieważ otrzymałem kilka raportów od użytkowników, których selektory daty i czasu nie wywoływały wywołań zwrotnych onDateSet i onTimeSet. A błąd został oficjalnie naprawiony w Androidzie 5.0. Moje podejście zadziałało tylko na urządzeniach, na których obecny jest błąd, ponieważ moje niestandardowe przyciski nie wywoływały modułu obsługi onClick okna dialogowego, co jest jedynym miejscem, w którym onDateSet i onTimeSet są wywoływane, gdy błąd nie występuje. Zaktualizowałem powyższy kod, aby wywołać okno dialogowe onClick, więc teraz działa niezależnie od tego, czy błąd jest obecny.

arlomedia
źródło
0

Podobała mi się powyższa odpowiedź Davida Cesarino, ale chciałem czegoś, co zastąpiłoby zepsute okno dialogowe i działałoby na każdym oknie dialogowym, w którym może brakować anulowania / ma nieprawidłowe zachowanie anulowania. Oto klasy pochodne dla DatePickerDialog / TimePickerDialog, które powinny działać jako zamienniki upuszczania. To nie są widoki niestandardowe. Używa systemowego okna dialogowego, ale po prostu zmienia zachowanie przycisku Anuluj / Wstecz, aby działało zgodnie z oczekiwaniami.

Powinno to działać na poziomie API 3 i wyższym. A więc w zasadzie każda wersja Androida (testowałem ją konkretnie na Jellybean i Lollipop).

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
źródło
Jest to bardzo podobne do powyższej odpowiedzi The Sea.
utknął
0

Moja działająca wersja z ClearButton używająca 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);
    }
}
Wasilij Prokopishin
źródło
0

W przypadku TimePickerDialog obejście może wyglądać następująco:

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

Wszystkie zdarzenia deleguję do wrappera KitKatSetTimeListener i odpalam tylko do oryginalnego OnTimeSetListener w przypadku kliknięcia przycisku BUTTON_POSITIVE.

Southerton
źródło
0

Po przetestowaniu niektórych zamieszczonych tu sugestii osobiście uważam, że to rozwiązanie jest najprostsze. Przekazuję „null” jako mój detektor w konstruktorze DatePickerDialog, a następnie po kliknięciu przycisku „OK” wywołuję mój 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();
        }
    });
Birk Bonnicksen
źródło
-1

Wiem, że ten post był tu od prawie roku, ale pomyślałem, że powinienem opublikować swoje ustalenia. Możesz nadal zachować słuchacza (zamiast ustawiać go na mull) i nadal mieć tę pracę zgodnie z oczekiwaniami. Kluczem jest niejawne ustawienie przycisków „OK” lub (i) „Anuluj”. Przetestowałem to i działa na mnie z wdzięcznością. Słuchacz nie zostaje dwukrotnie wyrzucony.

Spójrz na ten przykład,

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();
}
Krokodyl
źródło
Jasne i proste, nie działa tak, jak mówisz. timePickerListenerjest nadal wywoływana niezależnie od tego, co robisz w oknie dialogowym. Nie muszę nawet testować, aby to wiedzieć, wystarczy spojrzeć na źródła : jeśli nie ustawisz tego na null, tryNotifyTimeSet()wywoła słuchacza onTimeSet()zarówno w swoim, jak onClick()i onStop().
davidcsb
Innymi słowy, nie pozwól, aby klasa natywna zawierała odniesienie do twojego listenera (tj. Przekazując ją w konstruktorze). Sposób obchodzenia się z przyciskami nie ma znaczenia.
davidcsb
Cześć David, Nie przetestowałeś tego i założyłeś, że to nie zadziała. To jest dokładny fragment kodu, którego obecnie używam w mojej aplikacji i działa jak mistrz.
Krokodyl
1) Nigdy nie powiedziałem, że to nie działa. Powiedziałem, że nie zadziałało tak, jak powiedziałeś. Przeczytaj ponownie. 2) Państwo naruszone LSP i SRP, marnowanie roboczogodzin aby niepotrzebnie zmienić cały swój całą logikę klienta, że nie potrzeba żadnych zmian na początku. 3) Twoja odpowiedź jest odpowiedzią na pytanie, tak, w przeciwnym razie oznaczyłbym ją do usunięcia jako „nie jest odpowiedzią”, ale 4) Twoja odpowiedź jest nadal (przepraszam za to) bardzo nieefektywna i nie odnosi się do podstawowego problemu projektowego (wywołanie słuchacza ), stąd tylko głos przeciw. 6) wyłączyłeś ponownie i ustawiłeś okno modalne, aby wymusić wartość logiczną. A więc 6 poważnych problemów!
davidcsb