Jelly Bean DatePickerDialog - có cách nào để hủy không?


148

--- Lưu ý với người điều hành: Hôm nay (15 tháng 7), tôi nhận thấy rằng ai đó đã phải đối mặt với vấn đề này ở đây . Nhưng tôi không chắc liệu nó có phù hợp để đóng cái này như một bản sao không, vì tôi nghĩ rằng tôi đã cung cấp một lời giải thích tốt hơn về vấn đề này. Tôi không chắc mình có nên chỉnh sửa câu hỏi khác và dán nội dung này vào đó không, nhưng tôi không thoải mái thay đổi câu hỏi của người khác quá nhiều. ---

Tôi có một cái gì đó kỳ lạ ở đây.

Tôi không nghĩ vấn đề phụ thuộc vào SDK mà bạn xây dựng dựa vào. Phiên bản hệ điều hành thiết bị là những gì quan trọng.

Vấn đề # 1: không nhất quán theo mặc định

DatePickerDialogđã được thay đổi (?) trong Jelly Bean và giờ chỉ cung cấp nút Xong . Các phiên bản trước có nút Hủy và điều này có thể ảnh hưởng đến trải nghiệm người dùng (không nhất quán, bộ nhớ cơ từ các phiên bản Android trước).

Nhân rộng: Tạo một dự án cơ bản. Đặt cái này vàoonCreate:

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

Dự kiến: Mộtnút Hủy xuất hiện trong hộp thoại.

Hiện tại: Một Hủy bỏ nút không xuất hiện.

Ảnh chụp màn hình: 4.0.3 (OK) và 4.1.1 (có thể sai?).

Vấn đề # 2: hành vi sa thải sai

Hộp thoại gọi bất cứ ai nghe nó nên gọi thực sự, và sau đó luôn luôn gọi OnDateSetListenerngười nghe. Hủy bỏ vẫn gọi phương thức thiết lập và thiết lập nó gọi phương thức hai lần.

Sao chép: Sử dụng mã số 1, nhưng thêm mã bên dưới (bạn sẽ thấy mã này giải quyết được số 1, nhưng chỉ trực quan / UI):

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

Hy vọng:

  • Nhấn phím BACK hoặc nhấp bên ngoài hộp thoại sẽ không làm gì cả .
  • Nhấn "Hủy" sẽ in Picker Hủy! .
  • Nhấn "Set" sẽ in Bộ chọn! .

Hiện hành:

  • Nhấn phím BACK hoặc nhấp bên ngoài hộp thoại in Bộ chọn! .
  • Nhấn "Hủy" bản in Picker Hủy! và sau đó Bộ chọn! .
  • Nhấn "Đặt" bản in Bộ chọn! và sau đó Bộ chọn! .

Các dòng nhật ký thể hiện hành vi:

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!

Ghi chú và bình luận khác

  • Bao bọc nó xung quanh DatePickerFragmentkhông thành vấn đề. Tôi đã đơn giản hóa vấn đề cho bạn, nhưng tôi đã thử nó.

Xin chúc mừng, bạn dường như đã tìm thấy một lỗi trong Android. Bạn có thể báo cáo nó ở đây .
Michael Hampton

1
Báo cáo lỗi rất tốt bằng văn bản. Tôi có thể hiểu nó hoàn toàn mà không cần phải chạy thử mã.
Cheok Yan Cheng

6
Tôi kêu gọi mọi người bỏ phiếu vấn đề này lên! Số phát hành 34833
Bradley

Bạn không thể ghi đè chức năng nút để hành động như bị tắt do chạm bên ngoài hộp thoại?
Karthik Balakrishnan

2
Bug vẫn mở sau 2 năm ... không thể tin được.
erdomester

Câu trả lời:


115

Lưu ý: Đã sửa lỗi như Lollipop , nguồn ở đây . Lớp tự động để sử dụng trong máy khách (tương thích với tất cả các phiên bản Android) cũng được cập nhật.

TL; DR: 1-2-3 bước dễ dàng cho một giải pháp toàn cầu:

  1. Tải về lớp này .
  2. Thực hiện OnDateSetListenertrong hoạt động của bạn (hoặc thay đổi lớp học cho phù hợp với nhu cầu của bạn).
  3. Kích hoạt hộp thoại với mã này (trong mẫu này, tôi sử dụng nó bên trong 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");

Và đó là tất cả những gì nó cần! Lý do tôi vẫn giữ câu trả lời là "được chấp nhận" là vì tôi vẫn thích giải pháp của mình vì nó có dấu chân rất nhỏ trong mã máy khách, nó giải quyết vấn đề cơ bản (người nghe được gọi trong lớp khung), hoạt động tốt qua các thay đổi cấu hình và nó định tuyến logic mã đến việc triển khai mặc định trong các phiên bản Android trước đó không bị lỗi này (xem nguồn lớp).

Câu trả lời gốc (được giữ vì lý do lịch sử và mô phạm):

Nguồn lỗi

OK, có vẻ như đó thực sự là một lỗi và người khác đã lấp đầy nó. Số phát hành 34833 .

Tôi thấy rằng vấn đề có thể xảy ra DatePickerDialog.java. Nó đọc ở đâu:

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

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

Tôi đoán nó có thể là:

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

Bây giờ nếu ai đó có thể cho tôi biết làm thế nào tôi có thể đề xuất báo cáo vá / lỗi cho Android, tôi rất vui lòng. Trong khi đó, tôi đã đề xuất một bản sửa lỗi có thể (đơn giản) là một phiên bản đính kèm DatePickerDialog.javatrong Vấn đề ở đó.

Khái niệm để tránh lỗi

Đặt trình lắng nghe nulltrong hàm tạo và tạo BUTTON_POSITIVEnút riêng của bạn sau này . Đó là nó, chi tiết dưới đây.

Vấn đề xảy ra bởi vì DatePickerDialog.java, như bạn có thể thấy trong nguồn, gọi một biến toàn cục ( mCallBack) lưu trữ trình nghe được truyền trong hàm tạo:

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

Vì vậy, mẹo là cung cấp cho nullngười nghe được lưu trữ dưới dạng người nghe, sau đó cuộn bộ nút của riêng bạn (bên dưới là mã gốc từ # 1, được cập nhật):

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

Bây giờ nó sẽ hoạt động vì sự điều chỉnh có thể mà tôi đã đăng ở trên.

Và kể từ khi DatePickerDialog.javakiểm tra nullbất cứ khi nào nó đọcmCallback ( kể từ thời API 3 / 1.5, dường như --- không thể kiểm tra Honeycomb), nó sẽ không kích hoạt ngoại lệ. Xem xét Lollipop đã khắc phục sự cố, tôi sẽ không xem xét vấn đề này: chỉ sử dụng triển khai mặc định (được bao phủ trong lớp tôi cung cấp).

Lúc đầu, tôi sợ không gọi được clearFocus(), nhưng tôi đã thử nghiệm ở đây và các dòng Nhật ký đã sạch. Vì vậy, dòng tôi đề xuất thậm chí có thể không cần thiết, nhưng tôi không biết.

Khả năng tương thích với các cấp API trước đó (đã chỉnh sửa)

Như tôi đã chỉ ra trong bình luận dưới đây, đó là một khái niệm và bạn có thể tải xuống lớp tôi đang sử dụng từ tài khoản Google Drive của mình . Cách tôi sử dụng, triển khai hệ thống mặc định được sử dụng trên các phiên bản không bị ảnh hưởng bởi lỗi.

Tôi đã lấy một vài giả định (tên nút, v.v.) phù hợp với nhu cầu của mình vì tôi muốn giảm mã nồi hơi trong các lớp máy khách xuống mức tối thiểu. Ví dụ sử dụng đầy đủ:

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
Công việc tuyệt vời về nghiên cứu! Buồn vì điều đó là cần thiết, nhưng dù sao cũng tuyệt vời.
CommonsWare

1
Rất đẹp! Tôi đã đập đầu vào tường về vấn đề này đang cập nhật trường văn bản của tôi và gọi / không gọi các cuộc gọi lại chính xác. Cảm ơn bạn! Tôi tự hỏi nếu cách giải quyết được đề xuất là "bằng chứng trong tương lai" hoặc nếu bạn nghĩ rằng nó sẽ gây ra vấn đề khi sửa lỗi?
khoảng

1
@RomainGuidoux xem câu trả lời cập nhật ở cuối. Lớp trong liên kết có thông minh chỉ gọi phương thức đó trong thạch đậu. Trên bất cứ điều gì bên dưới, nó sẽ bỏ qua điều đó và sử dụng triển khai hệ thống mặc định, định tuyến cuộc gọi hệ thống cho ondateset đến hoạt động của bạn. Chỉ là trong thạch đậu, cần có các biện pháp bổ sung (tránh lỗi) trước khi định tuyến lại cuộc gọi lại, và điều đó liên quan đến việc gọi phương thức honeycomb + đó. Nhưng một lần nữa, chỉ trong JB.
davidcsb

1
Công việc tuyệt vời ở đây Tôi không có từ ngữ nào cho vấn đề này nực cười như thế nào.
Bill Phillips

5
Làm thế nào về TimePickerDialog? Có vẻ như TimePickerDialog không có getTimePicker ()
lokoko

16

Tôi sẽ thêm đoạn trích của riêng mình vào giải pháp David Cesarino đã đăng, trong trường hợp bạn không sử dụng Fragment và muốn một cách dễ dàng để sửa nó trong tất cả các phiên bản (2.1 đến 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*/ }
  }
}

Chỉ cần nhớ: nếu bạn sử dụng các tên nút để ok và hủy, có lẽ tốt hơn là sử dụng các tiêu chuẩn android.R.string.okandroid.R.string.cancelcác trường, thay vì của riêng người dùng. Và cảm ơn đã trả lời.
davidcsb

À, tốt, tôi không nhận thấy họ cung cấp văn bản OK và Hủy. Cảm ơn!
dmon

nhận ngoại lệ nullpulum tại super (bối cảnh, chủ đề, null, dateToShow.get (NĂM), dateToShow.get (MONTH), dateToShow.get (DAY_OF_MONTH));
Kishore

Errr ... bạn đang vượt qua một null dateToShow? Các null khác thực sự là "sửa chữa" vì vậy nên có ở đó. Bạn đang dùng phiên bản nào?
dmon

bạn có thể vui lòng cho tôi biết importbạn đã làm Fieldgì không? Tôi đang có 6 lựa chọn và không ai trong số họ làm việc.
Adil Malik

8

Cho đến khi lỗi sẽ được sửa, tôi đề nghị không sử dụng DatePickerDialog hoặc TimePickerDialog. Sử dụng AlertDialog tùy chỉnh với tiện ích TimePicker / DatePicker;

Thay đổi TimePickerDialog bằng;

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

Thay đổi DatePickerDialog bằng;

    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

Một cho TimePicker dựa trên giải pháp của David Cesarino, "TL; DR: 1-2-3 bước dễ dàng cho một giải pháp toàn cầu"

TimePickerDialog không cung cấp chức năng như DatePickerDialog.getDatePicker. Vì vậy, OnTimesetListener người nghe phải được cung cấp. Chỉ để giữ sự tương đồng với giải pháp khắc phục DatePicker, tôi đã duy trì khái niệm mListener cũ. Bạn có thể thay đổi nó nếu bạn cần.

Gọi và nghe giống như giải pháp ban đầu. Chỉ cần bao gồm

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

mở rộng lớp cha mẹ,

... implements OnDateSetListener, OnTimeSetListener

Triển khai thực hiện

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

gọi ví dụ

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

(Cập nhật để xử lý hủy)

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

Nó không hoạt động với tôi khi thử nó trên s3 API 18
AbdelHady

3

Trong trường hợp bất cứ ai muốn giải quyết nhanh, đây là mã tôi đã sử dụng:

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

}

Trong đó layout.date_picker_view là một tài nguyên bố cục đơn giản với DatePicker vì đây là phần tử duy nhất:

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

Đây là hướng dẫn đầy đủ trong trường hợp bạn quan tâm.


Điều này làm việc cho tôi tuyệt vời. Dễ hiểu, dễ thực hiện! Đã thử nghiệm vào ngày 4.4
erdomester

3

Giải pháp đơn giản của tôi. Khi bạn muốn làm cho nó bắn lại, chỉ cần chạy "resetFired" (giả sử khi mở lại hộp thoại).

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 chỉnh sửa mã của bạn không chính xác nhưng tôi đã chỉnh sửa nó để làm cho nó rõ ràng hơn. Không cần thêm phương thức "isJellyBeanOrA Upper ()", không có lợi thế, chỉ cần thêm độ phức tạp không cần thiết. Gọi resetFired là rẻ.
Daniel Ryan

mã ở đây không chính xác, bởi vì onDateSetsẽ được gọi một lần khi hộp thoại được đóng bằng bất kỳ phương tiện nào và nếu điều đó có nghĩa là đặt thời gian thì nó sẽ được kích hoạt lại, vì vậy chúng ta cần bắt cuộc gọi thứ hai, không phải cuộc gọi đầu tiên như bạn đã làm,
AbdelHady

Liên quan isJellyBeanOrAbove(), các phiên bản thấp hơn Jellybean không có lỗi mà toàn bộ câu hỏi này là tất cả, và xem xét chúng tôi muốn bắt cuộc gọi thứ hai, mã sẽ không chạy trừ khi chúng tôi thực hiện kiểm tra này, tin tôi đi, tôi đã thử mã trên Trình giả lập & thiết bị thực (với các phiên bản khác nhau) nhiều lần và nó hoạt động như một bùa mê
AbdelHady

Tôi không chắc đó có phải là một downvote không. Tôi đã đăng bài này 2 năm trước, cái này đã có trong ứng dụng thương mại của chúng tôi kể từ đó hoạt động tốt. Không có lỗi được báo cáo bởi các nhóm QA của chúng tôi hoặc hàng ngàn người dùng của chúng tôi. Điều này có nghĩa là một câu trả lời đơn giản mà mọi người có thể mở rộng. Gọi "resetFired" khi bạn muốn nó kích hoạt lại.
Daniel Ryan

Trong ứng dụng của chúng tôi "isJellyBeanOrA Trên" không cần thiết. Ứng dụng sẽ chỉ hoạt động tốt trong tất cả các phiên bản nếu bạn gọi "resetFired" ở đúng khu vực.
Daniel Ryan

3

Theo câu trả lời xuất sắc của Ankur Chaudhary về TimePickerDialogvấn đề tương tự , nếu chúng tôi kiểm tra bên trong onDateSetnếu quan điểm đã cho isShown()hay không, nó sẽ giải quyết toàn bộ vấn đề với nỗ lực tối thiểu, không cần phải mở rộng trình chọn hoặc kiểm tra một số cờ gớm ghiếc xung quanh mã hoặc thậm chí kiểm tra phiên bản HĐH, chỉ cần làm như sau:

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

và tất nhiên điều tương tự cũng có thể được thực hiện onTimeSettheo câu trả lời của Ankur


1
Câu trả lời tốt nhất trong số tất cả các khác!
hả1

2

Cách tôi quản lý tình huống này là sử dụng cờ và ghi đè các phương thức on Hủy và onDismiss.

on Hủy chỉ được gọi khi người dùng chạm bên ngoài hộp thoại hoặc nút quay lại. onDismiss luôn được gọi

Đặt cờ trong phương thức on Hủy có thể giúp lọc theo phương thức onDismiss, mục đích của người dùng: hủy hành động hoặc thực hiện hành động. Dưới đây một số mã cho thấy ý tưởng.

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

Có một cách giải quyết rất đơn giản, nếu ứng dụng của bạn không sử dụng thanh hành động. Nhân tiện, lưu ý rằng một số ứng dụng dựa vào chức năng này để hoạt động, bởi vì việc hủy bỏ bộ chọn ngày có ý nghĩa đặc biệt (ví dụ: xóa trường ngày thành một chuỗi trống, đối với một số ứng dụng là loại đầu vào hợp lệ và có ý nghĩa ) và sử dụng cờ boolean để ngăn ngày được đặt hai lần trên OK sẽ không giúp bạn trong trường hợp này.

Re. Sửa chữa thực tế, bạn không phải tạo các nút mới hoặc hộp thoại của riêng bạn. Vấn đề là phải tương thích với cả hai phiên bản Android cũ hơn, phiên bản lỗi (4. ) và bất kỳ phiên bản nào trong tương lai, mặc dù điều này là không thể chắc chắn về, tất nhiên. Lưu ý rằng trong Android 2. , onStop () cho android.app.Dialog hoàn toàn không làm gì cả và trong 4. * nó mActionBar.setShowHideAnimationEnabled (false) chỉ quan trọng nếu ứng dụng của bạn có thanh hành động. OnStop () trong DatePickerDialog, kế thừa từ Dialog, chỉ đóng góp mDatePicker.clearF Focus () (kể từ bản sửa lỗi mới nhất cho các nguồn Android 4.3), có vẻ không cần thiết.

Do đó, thay thế onStop () bằng một phương thức không có gì nên trong nhiều trường hợp sẽ sửa ứng dụng của bạn và đảm bảo rằng nó sẽ duy trì như vậy trong tương lai gần. Do đó, chỉ cần mở rộng lớp DatePickerDialog bằng phương thức giả của riêng bạn và ghi đè lên phương thức giả. Bạn cũng sẽ phải cung cấp một hoặc hai nhà xây dựng, theo yêu cầu của bạn. Cũng lưu ý rằng người ta không nên cố gắng khắc phục sự cố này bằng cách thử trực tiếp làm gì đó với thanh hoạt động, vì điều này sẽ hạn chế khả năng tương thích của bạn với các phiên bản Android mới nhất. Cũng lưu ý rằng thật tuyệt khi có thể gọi siêu cho DatePicker's onStop () vì lỗi này chỉ có trong onStop () trong chính DatePickerDialog, nhưng không phải trong siêu hạng của DatePickerDialog. Tuy nhiên, điều này sẽ yêu cầu bạn gọi super.super.onStop () từ lớp tùy chỉnh của bạn, mà Java sẽ không cho phép bạn làm, vì nó đi ngược lại triết lý đóng gói :) Dưới đây là lớp học nhỏ mà tôi đã sử dụng để giới thiệu DatePickerDialog. Tôi hy vọng bình luận này sẽ hữu ích cho ai đó. 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.
}   

}


Ngay cả khi ActionBar được sử dụng thì đây có thể là một cách giải quyết chấp nhận được nếu bạn không bao giờ ẩn / hiển thị ActionBar. Để làm cho mọi thứ thêm an toàn, bạn có thể ghi đè lên Khởi động để không làm gì cả (sau đó các lệnh gọi cho hình ảnh động sẽ không được thực hiện một cách an toàn). Và ngay cả khi bạn ẩn / hiển thị nó, đó chỉ là hoạt hình bị vô hiệu hóa.
Daniel

0


Hãy thử các khái niệm dưới đây.

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


phương thức onDateset () gọi hai lần (nếu bạn đang kiểm tra trình giả lập. Nó gọi hai lần. Nếu bạn đang sử dụng thiết bị thực thì nó sẽ gọi đúng một lần. Nếu bạn đang sử dụng trình giả lập thì hãy sử dụng bộ đếm. Nếu bạn đang làm việc trong thiết bị thực bỏ qua biến đếm. Đối với thiết bị thực, nó hoạt động với tôi.)
khi người dùng nhấp vào nút trong DatePickerDialog.
để làm điều này, bạn nên duy trì một giá trị bộ đếm và không làm gì khi mothod gọi lần đầu tiên và thực hiện thao tác khi phương thức gọi lần thứ hai.
Tham khảo các đoạn mã dưới đây

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



Để hủy bỏ công cụ khai thác datepicker, nó hoạt động với tôi. Đối với trình giả lập, nó không 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);


Nó hoạt động với tôi cho một thiết bị thực sự. Nhưng đối với trình giả lập thì nó không hoạt động chính xác. Tôi nghĩ đó là một lỗi giả lập Android.


0

Một giải pháp đơn giản sẽ là sử dụng boolean để bỏ qua lần chạy thứ hai

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

Một lần nữa, giống như tôi đã nói với anh chàng kia trước bạn, điều này không giải quyết được lỗi. Tôi không muốn tỏ ra thô lỗ, nhưng các bạn nên thử nghiệm giải pháp của riêng bạn trước khi đăng ở đây ... chỉ để chứng minh quan điểm của tôi, sử dụng mã của bạn và để người dùng nhấn lại để hủy hộp thoại và xem ý tôi là gì. Nguyên nhân của lỗi là onStopgọi phương thức khi không nên ... nó bắn hai lần là hậu quả của lỗi.
davidcsb

Nếu tôi không đủ rõ ràng, hãy để tôi là: nhấn trở lại để hủy các cuộc gọi hộp thoại onDateSet. Như vậy, tan vỡ.
davidcsb

Cảm ơn bạn đã hiển thị lỗi. Tôi chỉnh sửa câu trả lời để nó hoạt động với nút quay lại. Câu trả lời của bạn đang hoạt động nhưng DatePicker dp = picker.getDatePicker (); không hoạt động với TimePickers vì phương thức getTimePicker () không được thêm vào. Vì vậy, đây sẽ là một câu trả lời hợp lệ
chamikaw

Làm thế nào chúng ta đang cố gắng bỏ qua lần chạy thứ hai? !!, tôi đã thử nó nhiều lần, khi đóng hộp thoại bằng bất kỳ phương tiện nào onDateSetsẽ được gọi một lần, nhưng khi chọn "xong" hoặc "đặt" thì nó sẽ được gọi hai lần. Do đó, chúng ta chỉ cần bỏ qua cái đầu tiên, vì vậy nếu nó được gọi hai lần thì chỉ sau đó chúng ta mới có ngày chính xác
AbdelHady

0

Bạn có thể ghi đè on Hủy () và sử dụng setOnDismissListener () để phát hiện hành động tiêu cực của người dùng. Và với DatePickerDialog.BUTTON_POSITIVE, bạn biết rằng người dùng muốn đặt một ngày mới.

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

sau đó kiểm tra setDate:

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

0

Đây là lớp giải quyết của tôi cho DatePickerDialog trên nút hủy cũng như từ bỏ nó bằng nút quay lại. Sao chép và sử dụng theo kiểu DatePickerDialog (Vì người nghe có trạng thái, chúng tôi phải tạo phiên bản mới khi sử dụng, nếu không sẽ cần thêm mã để làm cho nó hoạt động)

Sử dụng:

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

Lớp học:

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

Tôi đang sử dụng bộ chọn ngày, bộ chọn thời gian và bộ chọn số. Bộ chọn số gọi onValueChanged bất cứ khi nào người dùng chọn một số, trước khi bộ chọn bị loại bỏ, vì vậy tôi đã có cấu trúc như thế này để làm một cái gì đó với giá trị chỉ khi bộ chọn bị loại bỏ:

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

Tôi đã mở rộng phần này để đặt tùy chỉnh onClickListener cho các nút của mình, với một đối số để xem nút nào được nhấp. Bây giờ tôi có thể kiểm tra nút nào đã được gõ trước khi tôi đặt giá trị cuối cùng của mình:

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

Và sau đó tôi đã mở rộng nó để làm việc với các loại ngày và giờ cho bộ chọn ngày và giờ cũng như kiểu int cho bộ chọn số.

Tôi đã đăng bài này vì tôi nghĩ nó đơn giản hơn một số giải pháp ở trên, nhưng bây giờ tôi đã bao gồm tất cả các mã, tôi đoán nó không đơn giản hơn nhiều! Nhưng nó phù hợp với cấu trúc tôi đã có.

Cập nhật cho Lollipop: Rõ ràng lỗi này không xảy ra trên tất cả các thiết bị Android 4.1-4.4, vì tôi đã nhận được một vài báo cáo từ người dùng có bộ chọn ngày và giờ không gọi các cuộc gọi lại onDateset và onTimeset. Và lỗi đã chính thức được sửa trong Android 5.0. Cách tiếp cận của tôi chỉ hoạt động trên các thiết bị có lỗi, vì các nút tùy chỉnh của tôi không gọi trình xử lý onClick của hộp thoại, đây là nơi duy nhất mà onDateset và onTimeset được gọi khi không có lỗi. Tôi đã cập nhật mã của mình ở trên để gọi onClick của hộp thoại, vì vậy bây giờ nó hoạt động cho dù có lỗi hay không.


0

Tôi thích câu trả lời của David Cesarino ở trên, nhưng muốn một cái gì đó thay thế cho hộp thoại bị hỏng và sẽ hoạt động trên bất kỳ hộp thoại nào có thể thiếu hủy / có hành vi hủy không chính xác. Dưới đây là các lớp dẫn xuất cho DatePickerDialog / TimePickerDialog sẽ hoạt động như một sự thay thế. Đây không phải là quan điểm tùy chỉnh. Nó sử dụng hộp thoại hệ thống, nhưng chỉ thay đổi hành vi nút hủy / quay lại để hoạt động như mong đợi.

Điều này sẽ hoạt động trên API cấp 3 trở lên. Vì vậy, về cơ bản là bất kỳ phiên bản nào của Android (tôi đã thử nghiệm nó trên thạch và kẹo mút cụ thể).

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

Điều này khá giống với câu trả lời của Biển ở trên.
mắc kẹt vào

0

Phiên bản làm việc của tôi với ClearButton bằng Lambda Expressions:

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

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

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

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

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


    public interface OnDateClearListener {
        void onDateClear();
    }

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

0

Đối với TimePickerDialog, cách giải quyết có thể như sau:

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

Tôi ủy quyền tất cả các sự kiện cho trình bao bọc KitKatSetTimeListener và chỉ quay trở lại OnTimeSetListener ban đầu trong trường hợp BUTTON_POSITIVE được nhấp.


0

Sau khi thử nghiệm một số loại đường được đăng ở đây, cá nhân tôi nghĩ rằng giải pháp này là đơn giản nhất. Tôi chuyển "null" làm trình nghe của mình trong hàm tạo DatePickerDialog và sau đó khi tôi nhấp vào nút "OK", tôi gọi onDateSearchSetListener của mình:

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

Tôi biết bài đăng này đã ở đây được gần một năm nhưng tôi nghĩ tôi nên đăng những phát hiện của mình. Bạn vẫn có thể giữ cho người nghe (thay vì đặt nó thành mull) và vẫn có tác phẩm này như mong đợi. Điều quan trọng là đặt ngầm các nút "OK" hoặc (và) các nút "hủy". Tôi đã thử nghiệm nó và nó hoạt động biết ơn đối với tôi. Người nghe không bị sa thải hai lần.

Nhìn vào ví dụ này

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

Rõ ràng và đơn giản, nó không hoạt động theo cách bạn nói. timePickerListenervẫn được gọi bất kể bạn làm gì trong hộp thoại của bạn. Tôi thậm chí không cần kiểm tra để biết rằng, bạn chỉ cần xem các nguồn : nếu bạn không đặt nó null, tryNotifyTimeSet()sẽ gọi cả người nghe onTimeSet()cả trong onClick()onStop().
davidcsb

Nói cách khác, đừng không để cho các lớp học có nguồn gốc giữ một tham chiếu đến người nghe của bạn (ví dụ, đi qua nó trong constructor). Làm thế nào bạn đối phó với các nút là không liên quan.
davidcsb

Xin chào David, Bạn đã không kiểm tra điều này và bạn cho rằng nó sẽ không hoạt động. Đây là đoạn mã chính xác mà tôi hiện đang sử dụng trong ứng dụng của mình và nó hoạt động như một nhà vô địch.
Cá sấu

1) Tôi chưa bao giờ nói nó không hoạt động. Tôi nói nó không hoạt động như bạn nói. Xin vui lòng đọc lại. 2) bạn vi phạm LSP và SRP, lãng phí do con người giờ để không cần thiết thay đổi tất cả các bạn toàn bộ lý khách hàng mà không cần bất kỳ thay đổi để bắt đầu với. 3) câu trả lời của bạn giải quyết câu hỏi, vâng, nếu không, tôi đánh dấu câu đó là "không phải là câu trả lời", nhưng 4) câu trả lời của bạn vẫn (rất tiếc về nó) rất không hiệu quả và không giải quyết được vấn đề thiết kế cơ bản (người nghe gọi ), do đó chỉ có downvote. 6) bạn vô hiệu hóa trở lại biến nó thành một cửa sổ phương thức để buộc boolean. Vì vậy, 6 vấn đề nghiêm trọng đấy!
davidcsb
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.