--- 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ść
DatePickerDialog
został 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 OnDateSetListener
sł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ół
DatePickerFragment
nie ma znaczenia. Uprościłem dla ciebie problem, ale przetestowałem go.
Odpowiedzi:
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:
OnDateSetListener
w swojej działalności (lub zmień klasę, aby odpowiadała Twoim potrzebom).Uruchom okno dialogowe za pomocą tego kodu (w tym przykładzie używam go wewnątrz a
Fragment
):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:Myślę, że mogło to być:
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.java
w tamtym numerze.Koncepcja, aby uniknąć błędu
Ustaw odbiornik
null
w konstruktorze na, aBUTTON_POSITIVE
później utwórz własny przycisk . To wszystko, szczegóły poniżej.Problem
DatePickerDialog.java
pojawia się, ponieważ , jak widać w źródle, wywołuje zmienną globalną (mCallBack
), która przechowuje odbiornik przekazany w konstruktorze:Tak więc sztuczka polega na zapewnieniu
null
sł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):Teraz będzie działać z uwagi na ewentualną korektę, którą zamieściłem powyżej.
A ponieważ
DatePickerDialog.java
sprawdza, czy zanull
każdym razem, gdy czytamCallback
( 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:
źródło
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):
źródło
android.R.string.ok
iandroid.R.string.cancel
pól zamiast własnych. Dziękuję za odpowiedź.dateToShow
? Drugą wartością zerową jest w rzeczywistości „poprawka”, więc powinna tam być. Którą wersję używasz?import
maszField
? Mam 6 opcji i żadna z nich nie zadziałała.Do czasu naprawienia błędu sugeruję nie używać DatePickerDialog ani TimePickerDialog. Użyj niestandardowego AlertDialog z widgetem TimePicker / DatePicker;
Zmień TimePickerDialog z;
Zmień DatePickerDialog z;
źródło
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
rozszerz klasę rodzicielską,
Wprowadzić w życie
przykład dzwonienia
(Zaktualizowano w celu obsługi anulowania)
źródło
Na wypadek, gdyby ktoś chciał szybko obejść, oto kod, którego użyłem:
}
Gdzie layout.date_picker_view jest prostym zasobem układu z DatePicker jako jedynym elementem:
Oto pełny samouczek, jeśli jesteś zainteresowany.
źródło
Moje proste rozwiązanie. Jeśli chcesz, aby znów działał, po prostu uruchom "resetFired" (powiedz przy ponownym otwieraniu okna).
źródło
onDateSet
zostanie 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,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 urokZgodnie z genialną odpowiedzią Ankur Chaudhary w podobnej
TimePickerDialog
sprawie, jeśli sprawdzimy w środku,onDateSet
czy 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:i oczywiście to samo można zrobić
onTimeSet
zgodnie z odpowiedzią Ankuraźródło
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ł.
źródło
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
}
źródło
Wypróbuj poniższe koncepcje.
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
W przypadku anulowania dilalogu datepicker działa dla mnie, dla emulatora nie działa
Działa dla mnie na prawdziwym urządzeniu, ale dla emulatora nie działa poprawnie. Myślę, że to błąd emulatora Androida.
źródło
Prostym rozwiązaniem byłoby użycie wartości logicznej do pominięcia drugiego biegu
źródło
onStop
wywołanie metody, kiedy nie powinien ... to dwukrotnie wypalania jest konsekwencją błędu.onDateSet
. Tak więc zepsuty.onDateSet
zostanie 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ę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ę.
następnie sprawdź setDate:
źródło
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ę:
Klasa:
}
źródło
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:
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:
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.
źródło
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:
TimePickerDialog:
źródło
Moja działająca wersja z ClearButton używająca Lambda Expressions:
źródło
W przypadku TimePickerDialog obejście może wyglądać następująco:
Wszystkie zdarzenia deleguję do wrappera KitKatSetTimeListener i odpalam tylko do oryginalnego OnTimeSetListener w przypadku kliknięcia przycisku BUTTON_POSITIVE.
źródło
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:
źródło
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,
źródło
timePickerListener
jest 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 nanull
,tryNotifyTimeSet()
wywoła słuchaczaonTimeSet()
zarówno w swoim, jakonClick()
ionStop()
.