Jelly Bean DatePickerDialog - apakah ada cara untuk membatalkan?


148

--- Catatan untuk moderator: Hari ini (15 Juli), saya perhatikan seseorang telah menghadapi masalah ini di sini . Tapi saya tidak yakin apakah pantas untuk menutup ini sebagai duplikat, karena saya pikir saya memberikan penjelasan yang lebih baik tentang masalah ini. Saya tidak yakin apakah saya harus mengedit pertanyaan lain dan menempelkan konten ini di sana, tetapi saya tidak nyaman terlalu banyak mengubah pertanyaan orang lain. ---

Saya punya sesuatu yang aneh di sini.

Saya tidak berpikir masalahnya tergantung pada SDK yang Anda buat. Versi perangkat OS adalah yang penting.

Masalah # 1: tidak konsisten secara default

DatePickerDialogtelah diubah (?) di Jelly Bean dan sekarang hanya menyediakan tombol Selesai . Versi sebelumnya menyertakan tombol Batal , dan ini dapat memengaruhi pengalaman pengguna (inkonsistensi, memori otot dari versi Android sebelumnya).

Replika: Buat proyek dasar. Masukkan inionCreate:

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

Diharapkan: Sebuah Batal tombol untuk muncul dalam dialog.

Saat ini: Sebuah Batal tombol tidak muncul.

Tangkapan layar: 4.0.3 (OK) dan 4.1.1 (mungkin salah?).

Masalah # 2: perilaku salah memberhentikan

Dialog memanggil pendengar mana pun yang seharusnya dipanggil, dan kemudian selalu memanggil OnDateSetListenerpendengar. Membatalkan masih memanggil metode yang ditetapkan, dan mengaturnya memanggil metode dua kali.

Replika: Gunakan kode # 1, tetapi tambahkan kode di bawah ini (Anda akan melihat ini menyelesaikan # 1, tetapi hanya secara visual / UI):

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

Diharapkan:

  • Menekan tombol BACK atau mengklik di luar dialog seharusnya tidak melakukan apa-apa .
  • Menekan "Cancel" harus mencetak Picker Cancel! .
  • Menekan "Set" harus mencetak Picker Set! .

Arus:

  • Menekan tombol BACK atau mengklik di luar dialog akan mencetak Picker Set! .
  • Menekan "Cancel" mencetak Picker Cancel! dan kemudian Picker Set! .
  • Menekan "Set" mencetak Picker Set! dan kemudian Picker Set! .

Baris log yang menunjukkan perilaku:

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!

Catatan dan komentar lainnya

  • Membungkusnya di sekitar DatePickerFragmenttidak masalah. Saya menyederhanakan masalah untuk Anda, tetapi saya telah mengujinya.

Selamat, Anda sepertinya telah menemukan bug di Android. Anda dapat melaporkannya di sini .
Michael Hampton

1
Laporan bug yang ditulis dengan sangat baik. Saya dapat memahaminya sepenuhnya tanpa harus menguji menjalankan kode.
Cheok Yan Cheng

6
Saya mendesak semua orang untuk memilih masalah ini! Edisi 34833
Bradley

Tidak bisakah Anda hanya mengesampingkan fungsi tombol untuk bertindak seperti itu ditolak karena sentuhan di luar dialog?
Karthik Balakrishnan

2
Bug masih terbuka setelah 2 tahun ... luar biasa.
erdomester

Jawaban:


115

Catatan: Diperbaiki pada Lollipop , sumber di sini . Kelas otomatis untuk digunakan dalam klien (kompatibel dengan semua versi Android) diperbarui juga.

TL; DR: 1-2-3 langkah mudah mati untuk solusi global:

  1. Unduh kelas ini .
  2. Terapkan OnDateSetListenerdalam aktivitas Anda (atau ubah kelas sesuai kebutuhan Anda).
  3. Trigger dialog dengan kode ini (dalam sampel ini, saya menggunakannya di dalam 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");

Dan hanya itu yang dibutuhkan! Alasan saya masih menjaga jawaban saya sebagai "diterima" adalah karena saya masih lebih suka solusi saya karena memiliki jejak yang sangat kecil dalam kode klien, ini membahas masalah mendasar (pendengar dipanggil dalam kelas kerangka kerja), berfungsi dengan baik di seluruh perubahan konfigurasi dan itu merutekan logika kode ke implementasi default di versi Android sebelumnya yang tidak terganggu oleh bug ini (lihat sumber kelas).

Jawaban asli (disimpan karena alasan historis dan didaktik):

Sumber bug

OK, sepertinya itu memang bug dan orang lain sudah mengisinya. Masalah 34833 .

Saya telah menemukan bahwa masalahnya mungkin di DatePickerDialog.java. Di mana bunyinya:

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

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

Saya kira itu bisa saja:

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

Sekarang jika seseorang dapat memberi tahu saya bagaimana saya bisa mengusulkan laporan tambalan / bug ke Android, saya akan senang melakukannya. Sementara itu, saya menyarankan perbaikan yang mungkin (sederhana) sebagai versi terlampir DatePickerDialog.javadalam Masalah di sana.

Konsep untuk menghindari bug

Atur pendengar ke nulldalam konstruktor dan buat BUTTON_POSITIVEtombol Anda sendiri nanti . Itu dia, detailnya di bawah.

Masalahnya terjadi karena DatePickerDialog.java, seperti yang Anda lihat di sumber, memanggil variabel global ( mCallBack) yang menyimpan pendengar yang diteruskan dalam konstruktor:

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

Jadi, triknya adalah menyediakan nullpendengar untuk disimpan sebagai pendengar, dan kemudian menggulung set tombol Anda sendiri (di bawah ini adalah kode asli dari # 1, diperbarui):

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

Sekarang ini akan berfungsi karena kemungkinan koreksi yang saya posting di atas.

Dan karena DatePickerDialog.javamemeriksa untuk nullsetiap kali dibaca mCallback( sejak hari-hari API 3 / 1,5 tampaknya --- tidak dapat memeriksa Honeycomb tentu saja), itu tidak akan memicu pengecualian. Mengingat Lollipop memperbaiki masalah ini, saya tidak akan memeriksanya: cukup gunakan implementasi default (tercakup dalam kelas yang saya berikan).

Awalnya saya takut tidak menelepon clearFocus(), tapi saya sudah menguji di sini dan garis-garis Log bersih. Jadi kalimat yang saya usulkan itu mungkin tidak diperlukan, tetapi saya tidak tahu.

Kompatibilitas dengan level API sebelumnya (diedit)

Seperti yang saya tunjukkan dalam komentar di bawah, itu adalah sebuah konsep, dan Anda dapat mengunduh kelas yang saya gunakan dari akun Google Drive saya . Cara saya menggunakan, implementasi sistem default digunakan pada versi yang tidak terpengaruh oleh bug.

Saya mengambil beberapa asumsi (nama tombol dll.) Yang cocok untuk kebutuhan saya karena saya ingin mengurangi kode boilerplate di kelas klien seminimal mungkin. Contoh penggunaan penuh:

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
Pekerjaan luar biasa dalam penelitian! Sedih karena itu perlu, tapi tetap saja luar biasa.
CommonsWare

1
Sangat bagus! Saya telah memukulkan kepala saya ke tembok pada masalah ini yang memperbarui bidang teks saya dan menelepon / tidak memanggil panggilan balik yang benar. Terima kasih! Saya ingin tahu apakah solusi yang disarankan adalah "bukti masa depan" atau jika Anda pikir itu akan menimbulkan masalah ketika bug diperbaiki?
span

1
@RomainGuidoux melihat jawaban yang diperbarui di akhir. Kelas dalam tautan memiliki kecerdasan untuk hanya memanggil metode itu dalam jelly bean. Pada apa pun di bawah ini akan melewati itu dan menggunakan implementasi sistem default, merutekan panggilan sistem untuk ondateset ke aktivitas Anda. Hanya saja dalam jelly bean diperlukan tindakan tambahan (menghindari bug) sebelum merute panggilan balik, dan itu melibatkan memanggil metode honeycomb +. Tapi sekali lagi, hanya di JB.
davidcsb

1
Kerja luar biasa di sini. Saya tidak memiliki kata-kata untuk betapa konyolnya masalah ini.
Bill Phillips

5
Bagaimana dengan TimePickerDialog? Sepertinya TimePickerDialog tidak memiliki getTimePicker ()
lokoko

16

Saya akan menambahkan riff saya sendiri pada solusi yang diposting David Cesarino, jika Anda tidak menggunakan Fragmen, dan ingin cara mudah untuk memperbaikinya di semua versi (2.1 hingga 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*/ }
  }
}

Hanya dengan mengingat: jika Anda menggunakan nama tombol untuk ok dan membatalkan, mungkin lebih baik menggunakan standar android.R.string.okdan android.R.string.cancelbidang, bukan milik pengguna. Dan terima kasih atas jawabannya.
davidcsb

Ah, bagus, saya belum melihat mereka menyediakan OK dan Batalkan teks. Terima kasih!
Dmon

mendapatkan pengecualian nullpointer di super (konteks, tema, null, dateToShow.get (YEAR), dateToShow.get (MONTH), dateToShow.get (DAY_OF_MONTH));
Kishore

Errr ... apakah Anda memberikan nol dateToShow? Null lainnya sebenarnya ada "fix" sehingga harus ada. Kamu versi apa?
Ayah

bisakah Anda memberi tahu saya importuntuk apa Field? Saya memiliki 6 opsi dan tidak ada yang bekerja.
Adil Malik

8

Sampai bug diperbaiki, saya sarankan untuk tidak menggunakan DatePickerDialog atau TimePickerDialog. Gunakan AlertDialog yang dibuat khusus dengan widget TimePicker / DatePicker;

Ubah TimePickerDialog dengan;

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

Ubah DatePickerDialog dengan;

    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

Yang untuk TimePicker berdasarkan pada solusi oleh David Cesarino, "TL; DR: 1-2-3 langkah mudah mati untuk solusi global"

TimePickerDialog tidak menyediakan fungsionalitas seperti DatePickerDialog.getDatePicker. Jadi, pendengar OnTimeSetListener harus disediakan. Hanya untuk menjaga kesamaan dengan solusi penyelesaian DatePicker, saya telah mempertahankan konsep mListener lama. Anda dapat mengubahnya jika perlu.

Panggilan dan Pendengar sama dengan solusi asli. Cukup sertakan

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

memperpanjang kelas induk,

... implements OnDateSetListener, OnTimeSetListener

Melaksanakan

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

contoh memanggil

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

(Diperbarui untuk menangani pembatalan)

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

Ini tidak berfungsi untuk saya ketika mencobanya di s3 API 18
AbdelHady

3

Jika ada yang menginginkan solusi cepat, inilah kode yang saya gunakan:

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

}

Di mana layout.date_picker_view adalah sumber daya tata letak sederhana dengan DatePicker karena itu hanya elemen:

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

Inilah tutorial lengkap jika Anda tertarik.


Ini bekerja dengan baik bagi saya. Mudah dimengerti, mudah diimplementasikan! Diuji pada 4.4
erdomester

3

Solusi sederhana saya. Ketika Anda ingin menjalankannya lagi jalankan saja "resetFired" (katakanlah ketika membuka dialog lagi).

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 pengeditan kode Anda salah, tetapi saya telah mengeditnya agar lebih jelas. Tidak perlu menambahkan metode "isJellyBeanOrAbove ()", tidak ada keuntungan, hanya menambahkan kompleksitas yang tidak perlu. Memanggil resetFired murah.
Daniel Ryan

kode di sini salah, karena onDateSetakan dipanggil sekali ketika dialog ditutup dengan cara apa pun, dan jika itu berarti mengatur waktu maka akan dipecat lagi, jadi kita perlu menangkap panggilan kedua, bukan yang pertama seperti yang Anda lakukan,
AbdelHady

Mengenai isJellyBeanOrAbove(), versi lebih rendah dari Jellybean tidak memiliki bug di mana semua pertanyaan ini adalah tentang, & mengingat kami ingin menangkap panggilan kedua, kode tidak akan berjalan kecuali jika kami melakukan pemeriksaan ini, percayalah, saya sudah mencoba kode pada Emulator & perangkat nyata (dengan versi yang berbeda) beberapa kali & ini berfungsi seperti pesona
AbdelHady

Saya tidak yakin apakah itu layak untuk downvote. Saya memposting ini 2 tahun yang lalu, ini telah ada dalam aplikasi komersial kami sejak itu berfungsi dengan baik. Tidak ada bug yang dilaporkan oleh tim QA kami atau ribuan pengguna kami. Ini dimaksudkan sebagai jawaban sederhana yang dapat diberikan orang. Panggil "resetFired" ketika Anda ingin memecat lagi.
Daniel Ryan

Di aplikasi kami "isJellyBeanOrAbove" tidak diperlukan. Aplikasi ini hanya akan berfungsi dengan baik di semua versi jika Anda memanggil "resetFired" di area yang benar.
Daniel Ryan

3

Menurut jawaban brilian Ankur Chaudhary pada TimePickerDialogmasalah yang sama , jika kami memeriksa di dalam onDateSetapakah tampilan yang diberikan isShown()atau tidak, itu akan menyelesaikan seluruh masalah dengan upaya minimal, tanpa perlu memperluas pemilih atau memeriksa beberapa bendera mengerikan yang mengelilingi kode atau bahkan memeriksa versi OS, lakukan saja hal berikut:

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

dan tentu saja hal yang sama dapat dilakukan onTimeSetsesuai jawaban Ankur


1
Jawaban terbaik dari yang lainnya!
hiew1

2

Cara saya mengelola situasi ini menggunakan bendera dan mengganti metode onCancel dan onDismiss.

onCancel dipanggil hanya ketika pengguna menyentuh di luar dialog atau tombol kembali. onDismiss selalu dipanggil

Mengatur bendera di metode onCancel dapat membantu menyaring dalam metode onDismiss maksud pengguna: batalkan tindakan atau tindakan yang dilakukan. Di bawah ini beberapa kode yang menunjukkan ide.

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

Ada solusi yang sangat sederhana, jika aplikasi Anda tidak menggunakan panel tindakan. Perhatikan bahwa beberapa aplikasi bergantung pada fungsi ini untuk bekerja, karena membatalkan pemilih tanggal memiliki arti khusus (misalnya, membersihkan bidang tanggal ke string kosong, yang untuk beberapa aplikasi adalah jenis input yang valid dan bermakna. ) dan menggunakan bendera boolean untuk mencegah tanggal ditetapkan dua kali pada OK tidak akan membantu Anda dalam kasus ini.

Kembali. perbaikan yang sebenarnya, Anda tidak perlu membuat tombol baru atau dialog Anda sendiri. Intinya adalah agar kompatibel dengan keduanya, versi Android yang lebih lama, yang buggy (4. ) dan yang di masa depan, meskipun yang terakhir tidak mungkin untuk dipastikan, tentu saja. Perhatikan bahwa di Android 2. , onStop () untuk android.app.Dialog tidak melakukan apa-apa, dan di 4. * ia melakukan mActionBar.setShowHideAnimationEnabled (false) yang penting hanya jika aplikasi Anda memiliki bilah tindakan. OnStop () di DatePickerDialog, yang mewarisi dari Dialog, hanya berkontribusi mDatePicker.clearFocus () (pada perbaikan terbaru ke sumber-sumber Android 4.3), yang tampaknya tidak penting.

Oleh karena itu mengganti onStop () dengan metode yang tidak melakukan apa pun dalam banyak hal harus memperbaiki aplikasi Anda dan memastikan bahwa itu akan tetap seperti itu di masa mendatang. Dengan demikian cukup memperluas kelas DatePickerDialog dengan Anda sendiri dan menimpa onStop () sedikit pun dengan metode dummy. Anda juga harus menyediakan satu atau dua konstruktor, sesuai kebutuhan Anda. Perhatikan juga bahwa seseorang tidak boleh tergoda untuk mencoba melakukan perbaikan yang berlebihan ini dengan misalnya mencoba melakukan sesuatu dengan bilah aktivitas secara langsung, karena ini akan membatasi kompatibilitas Anda hanya untuk versi terbaru Android. Perhatikan juga bahwa akan lebih baik untuk memanggil super untuk DatePicker's onStop () karena bug hanya ada di onStop () di DatePickerDialog sendiri, tetapi tidak di super class DatePickerDialog. Namun, ini akan mengharuskan Anda untuk memanggil super.super.onStop () dari kelas khusus Anda, Java mana yang tidak akan membiarkan Anda melakukannya, karena bertentangan dengan filosofi enkapsulasi :) Di bawah ini adalah kelas kecil saya yang saya gunakan untuk memverifikasi DatePickerDialog. Semoga komentar ini bermanfaat bagi seseorang. 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.
}   

}


Bahkan jika ActionBar digunakan maka ini mungkin solusi yang dapat diterima jika Anda tidak pernah menyembunyikan / menampilkan ActionBar. Untuk membuat hal-hal ekstra aman Anda dapat menimpa onStart untuk tidak melakukan apa-apa juga (maka panggilan untuk animasi akan terlepas dengan aman). Dan bahkan jika Anda menyembunyikan / menampilkannya, itu hanya animasi yang dinonaktifkan.
Daniel

0


Coba konsep di bawah ini.

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


metode onDateSet () memanggil dua kali (jika Anda memeriksa emulator.it memanggil dua kali. Jika menggunakan perangkat nyata maka itu akan memanggil dengan benar satu kali. Jika Anda menggunakan emulator maka gunakan counter. Jika Anda bekerja di perangkat nyata maka abaikan variabel penghitung. Untuk perangkat nyata ini berfungsi untuk saya.)
ketika pengguna mengklik tombol di DatePickerDialog.
untuk ini, Anda harus mempertahankan nilai penghitung dan tidak ada yang dilakukan saat mothod memanggil pertama kali dan melakukan operasi ketika metode memanggil 2nd time.
Lihat cuplikan kode di bawah ini

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



Untuk membatalkan datepicker, dilalog-nya berfungsi untuk saya. Untuk emulatornya tidak wokring

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


Ini bekerja untuk saya untuk perangkat nyata. Tetapi untuk emulator tidak bekerja dengan benar. Saya pikir ini adalah bug emulator android.


0

Solusi sederhana akan menggunakan boolean untuk melewati run kedua

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

Sekali lagi, sama seperti saya katakan pada orang lain sebelum Anda, ini tidak menyelesaikan bug. Saya tidak ingin terdengar kasar, tetapi kalian harus menguji solusi Anda sendiri sebelum memposting di sini ... hanya untuk membuktikan maksud saya, gunakan kode Anda dan biarkan pengguna menekan kembali untuk membatalkan dialog dan melihat apa yang saya maksud. The Penyebab dari bug tersebut onStopmemanggil metode ketika seharusnya tidak ... itu menembakkan dua kali merupakan konsekuensi dari bug.
davidcsb

Jika saya tidak cukup jelas, izinkan saya: menekan kembali untuk membatalkan panggilan dialog onDateSet. Dengan demikian, rusak.
davidcsb

Terima kasih telah menunjukkan kesalahan. Saya mengedit jawaban sehingga akan bekerja dengan tombol kembali. Jawaban Anda berfungsi tetapi DatePicker dp = picker.getDatePicker (); tidak bekerja dengan TimePickers karena metode getTimePicker () tidak ditambahkan. Jadi ini akan menjadi jawaban yang valid
chamikaw

Bagaimana kita mencoba untuk melewatkan run kedua? !!, Saya sudah mencobanya beberapa kali, ketika menutup dialog dengan cara apa pun onDateSetakan dipanggil sekali, tetapi ketika memilih "selesai" atau "set" maka akan dipanggil dua kali. Untuk itu kita hanya perlu melewati yang pertama, jadi jika itu dipanggil dua kali maka & hanya kemudian kita memiliki tanggal yang benar
AbdelHady

0

Anda dapat mengganti onCancel () dan menggunakan setOnDismissListener () untuk mendeteksi tindakan pengguna yang negatif. Dan dengan DatePickerDialog.BUTTON_POSITIVE Anda tahu bahwa pengguna ingin menetapkan tanggal baru.

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

kemudian periksa setDate:

public void onDateSet(DatePicker view, int year, int month, int day) {
    if(setDate){
        //do something with new date
    }
}

0

Ini adalah kelas penyelesaian masalah saya untuk DatePickerDialog pada tombol batal serta meninggalkannya dengan tombol kembali. Salin & gunakan dengan gaya DatePickerDialog (Karena pendengarnya stateful, kita harus membuat instance baru saat digunakan, jika tidak diperlukan lebih banyak kode untuk membuatnya berfungsi)

Menggunakan:

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

Kelas:

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

Saya menggunakan pemilih tanggal, pemilih waktu dan pemilih nomor. Pemilih nomor memanggil pada ValueChanged setiap kali pengguna memilih nomor, sebelum pemetik dipecat, jadi saya sudah memiliki struktur seperti ini untuk melakukan sesuatu dengan nilai hanya ketika pemetik dipecat:

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

Saya memperluas ini untuk mengatur onClickListeners khusus untuk tombol saya, dengan argumen untuk melihat tombol mana yang diklik. Sekarang saya dapat memeriksa tombol mana yang diketuk sebelum saya menetapkan nilai akhir saya:

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

Dan kemudian saya memperluasnya untuk bekerja dengan tipe tanggal dan waktu untuk pemilih tanggal dan waktu serta tipe int untuk pemilih nomor.

Saya memposting ini karena saya pikir itu lebih sederhana daripada beberapa solusi di atas, tetapi sekarang saya sudah memasukkan semua kode, saya kira itu tidak jauh lebih sederhana! Tapi itu pas dengan struktur yang sudah saya miliki.

Pembaruan untuk Lollipop: Rupanya bug ini tidak terjadi pada semua perangkat Android 4.1-4.4, karena saya menerima beberapa laporan dari pengguna yang pemetik tanggal dan waktunya tidak memanggil callback onDateSet dan onTimeSet. Dan bug itu secara resmi diperbaiki di Android 5.0. Pendekatan saya hanya bekerja pada perangkat di mana bug hadir, karena tombol kustom saya tidak memanggil dialog handler onClick, yang merupakan satu-satunya tempat yang onDateSet dan onTimeSet dipanggil ketika bug tidak ada. Saya memperbarui kode saya di atas untuk memanggil onClick dialog, jadi sekarang berfungsi apakah bug ada atau tidak.


0

Saya menyukai jawaban David Cesarino di atas, tetapi menginginkan sesuatu yang merupakan pengganti untuk dialog yang rusak dan akan bekerja pada dialog apa pun yang mungkin tidak ada, batalkan / ada perilaku pembatalan yang salah. Berikut adalah turunan kelas untuk DatePickerDialog / TimePickerDialog yang seharusnya berfungsi sebagai pengganti pengganti. Ini bukan tampilan khusus. Ini menggunakan dialog sistem, tetapi hanya mengubah perilaku tombol batal / kembali untuk bekerja seperti yang diharapkan.

Ini harus bekerja pada API level 3 dan lebih tinggi. Jadi, pada dasarnya semua versi Android (saya mengujinya pada jellybean dan lollipop khusus).

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

Ini sangat mirip dengan jawaban The Sea di atas.
stuckj

0

Versi kerja saya dengan ClearButton menggunakan Ekspresi Lambda:

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

Untuk TimePickerDialog solusinya dapat sebagai berikut:

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

Saya mendelegasikan semua acara untuk membungkus KitKatSetTimeListener, dan hanya menjalankan kembali ke OnTimeSetListener asli jika BUTTON_POSITIVE diklik.


0

Setelah menguji beberapa saran yang diposting di sini, saya pribadi berpikir solusi ini adalah yang paling sederhana. Saya meneruskan "null" sebagai pendengar saya di konstruktor DatePickerDialog, dan kemudian ketika saya mengklik tombol "OK" saya memanggil onDateSearchSetListener saya:

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

Saya tahu posting ini telah ada di sini selama hampir satu tahun tetapi saya pikir saya harus memposting temuan saya. Anda masih bisa menjaga pendengar (alih-alih mengaturnya untuk merenung) dan masih memiliki pekerjaan ini seperti yang diharapkan. Kuncinya adalah secara implisit mengatur tombol "OK" atau (dan) "batal". Saya mengujinya dan itu bekerja untuk saya. Pendengar tidak dipecat dua kali.

Lihat contoh ini,

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

Sederhana dan sederhana, itu tidak bekerja seperti yang Anda katakan. timePickerListenermasih dipanggil terlepas dari apa yang Anda lakukan dalam dialog Anda. Saya bahkan tidak perlu menguji untuk mengetahui hal itu, Anda hanya perlu melihat sumbernya : jika Anda tidak menyetelnya null, tryNotifyTimeSet()akan memanggil pendengar onTimeSet()baik dalam onClick()dan onStop().
davidcsb

Dengan kata lain, jangan tidak membiarkan kelas pribumi memegang referensi ke pendengar Anda (misalnya, lewat itu di konstruktor). Bagaimana Anda menangani tombol tidak relevan.
davidcsb

Hai David, Anda tidak menguji ini dan Anda menganggap itu tidak akan berhasil. Ini adalah bagian kode yang tepat saya gunakan saat ini di aplikasi saya dan berfungsi seperti juara.
Crocodile

1) Saya tidak pernah mengatakan itu tidak berhasil. Saya mengatakan itu tidak bekerja seperti yang Anda katakan. Silakan baca lagi. 2) Anda melanggar LSP dan SRP, membuang-buang man-jam untuk sia-sia mengubah semua Anda seluruh logika klien yang tidak memerlukan perubahan untuk memulai. 3) jawaban Anda menjawab pertanyaan, ya, kalau tidak saya akan menandainya untuk dihapus sebagai "bukan jawaban", tetapi 4) jawaban Anda masih (maaf tentang itu) sangat tidak efisien dan tidak membahas masalah desain mendasar (pendengar disebut ), maka hanya downvote. 6) Anda menonaktifkan kembali dan menjadikannya jendela modal untuk memaksa boolean. Jadi, 6 masalah serius di sana!
davidcsb
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.