Jelly Bean DatePickerDialog - มีวิธีการยกเลิกหรือไม่?


148

--- หมายเหตุผู้ดูแล:วันนี้ (15 กรกฎาคม) ผมได้สังเกตเห็นว่าคนที่ต้องเผชิญกับปัญหานี้แล้วที่นี่ แต่ฉันไม่แน่ใจว่ามันเหมาะสมที่จะปิดเป็นซ้ำเพราะฉันคิดว่าฉันให้คำอธิบายที่ดีขึ้น ฉันไม่แน่ใจว่าฉันควรแก้ไขคำถามอื่นและวางเนื้อหานี้ที่นั่น แต่ฉันไม่สะดวกที่จะเปลี่ยนคำถามของคนอื่นมากเกินไป ---

ฉันมีอะไรแปลก ๆที่นี่

ฉันไม่คิดว่าปัญหาจะขึ้นอยู่กับ SDK ที่คุณสร้างขึ้น เวอร์ชั่นของระบบปฏิบัติการของอุปกรณ์นั้นเป็นสิ่งที่สำคัญ

ปัญหา # 1: ความไม่สอดคล้องกันตามค่าเริ่มต้น

DatePickerDialogถูกเปลี่ยน (?) ใน Jelly Bean และตอนนี้ให้ปุ่มเสร็จสิ้นเท่านั้น รุ่นก่อนหน้านี้มีปุ่มยกเลิกและสิ่งนี้อาจส่งผลกระทบต่อประสบการณ์ของผู้ใช้ (ความไม่สอดคล้องกันหน่วยความจำกล้ามเนื้อจาก Android รุ่นก่อนหน้า)

ทำซ้ำ:สร้างโครงการพื้นฐาน ใส่สิ่งนี้ในonCreate:

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

ที่คาดว่าจะยกเลิกปุ่มที่จะปรากฏในกล่องโต้ตอบ

ปัจจุบัน:ยกเลิกปุ่มไม่ปรากฏ

สกรีน ช็อต: 4.0.3 (ตกลง) และ 4.1.1 (อาจผิด)

ปัญหา # 2: พฤติกรรมการเลิกผิด

โทรโต้ตอบแล้วแต่จำนวนใดจะฟังมันควรจะเรียกแน่นอนแล้วมักจะเรียกร้องให้OnDateSetListenerฟัง การยกเลิกยังคงเรียกวิธีการตั้งค่าและการตั้งค่าจะเรียกวิธีการสองครั้ง

ทำซ้ำ:ใช้รหัส # 1 แต่เพิ่มรหัสด้านล่าง (คุณจะเห็นสิ่งนี้แก้ได้ # 1 แต่มองเห็นเท่านั้น / UI):

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

ที่คาดว่าจะ

  • การกดปุ่ม BACK หรือคลิกที่นอกกรอบข้อความไม่ควรทำอะไรเลย
  • การกด "ยกเลิก" ควรพิมพ์ตัวเลือกตัวเลือกยกเลิก! .
  • การกด "Set" ควรพิมพ์Picker Set! .

ปัจจุบัน:

  • กดปุ่ม BACK หรือคลิกที่ด้านนอกกล่องโต้ตอบจะพิมพ์Picker Set! .
  • กด "ยกเลิก" พิมพ์ตัวเลือกยกเลิก! และจากนั้นชุดตัวเลือก! .
  • กด "Set" พิมพ์ชุดตัวเลือก! และจากนั้นชุดตัวเลือก! .

เส้นบันทึกแสดงพฤติกรรม:

07-15 12:00:13.415: D/Picker(21000): Set!

07-15 12:00:24.860: D/Picker(21000): Cancel!
07-15 12:00:24.876: D/Picker(21000): Set!

07-15 12:00:33.696: D/Picker(21000): Set!
07-15 12:00:33.719: D/Picker(21000): Set!

บันทึกและความคิดเห็นอื่น ๆ

  • ห่อมันรอบ ๆDatePickerFragmentไม่สำคัญ ฉันทำให้ปัญหาของคุณง่ายขึ้น แต่ฉันได้ทำการทดสอบแล้ว

ขอแสดงความยินดีดูเหมือนคุณจะพบข้อบกพร่องใน Android คุณสามารถรายงานได้ที่นี่
Michael Hampton

1
รายงานบั๊กที่เขียนได้ดีมาก ฉันสามารถเข้าใจได้อย่างสมบูรณ์โดยไม่ต้องทดสอบเรียกใช้รหัส
Cheok Yan Cheng

6
ฉันขอให้ทุกคนโหวตปัญหานี้ขึ้น! ปัญหา 34833
แบรดลีย์

คุณไม่สามารถแทนที่ฟังก์ชั่นปุ่มเพื่อทำหน้าที่เหมือนถูกไล่ออกเนื่องจากการสัมผัสนอกกล่องโต้ตอบหรือไม่?
Karthik Balakrishnan

2
ข้อผิดพลาดยังคงเปิดหลังจาก 2 ปี ... ไม่น่าเชื่อ
erdomester

คำตอบ:


115

หมายเหตุ: คงเป็นของ Lollipop , แหล่งที่มาที่นี่ คลาสอัตโนมัติสำหรับใช้ในไคลเอนต์ (รองรับกับ Android ทุกรุ่น) ได้รับการอัปเดตเช่นกัน

TL; DR: 1-2-3 ขั้นตอนง่าย ๆ ที่ตายแล้วสำหรับโซลูชันระดับโลก:

  1. ดาวน์โหลดนี้ระดับ
  2. นำไปใช้OnDateSetListenerในกิจกรรมของคุณ (หรือเปลี่ยนชั้นเรียนเพื่อให้เหมาะกับความต้องการของคุณ)
  3. เปิดกล่องโต้ตอบด้วยรหัสนี้ (ในตัวอย่างนี้ฉันใช้ภายใน a Fragment):

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

และนั่นคือทั้งหมดที่ใช้! เหตุผลที่ฉันยังคงรักษาคำตอบของฉันในฐานะ "ยอมรับ" ก็เพราะฉันยังคงชอบโซลูชันของฉันเพราะมันมีขนาดเล็กมากในรหัสลูกค้ามันแก้ไขปัญหาพื้นฐาน (ผู้ฟังที่ถูกเรียกในคลาสเฟรมเวิร์ก) ทำงานได้ดีในการเปลี่ยนแปลง config และมันจะประมวลผลลอจิกของรหัสไปยังการใช้งานเริ่มต้นในรุ่น Android ก่อนหน้านี้ที่ไม่ได้เกิดจากข้อผิดพลาดนี้ (ดูแหล่งที่มาของคลาส)

คำตอบเดิม (เก็บไว้เพื่อเหตุผลทางประวัติศาสตร์และการสอน):

แหล่งที่มาของแมลง

ตกลงดูเหมือนว่ามันเป็นข้อผิดพลาดและมีคนอื่นเติมเต็มแล้ว ฉบับที่ 34833

ฉันพบว่าปัญหาอาจเป็นไปDatePickerDialog.javaได้ มันอ่านที่ไหน:

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

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

ฉันเดาว่าน่าจะเป็น:

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

ตอนนี้ถ้าใครสามารถบอกฉันได้ว่าฉันสามารถเสนอรายงานการแก้ไข / ข้อบกพร่องกับ Android ฉันจะดีใจ ในขณะเดียวกันฉันแนะนำการแก้ไขที่เป็นไปได้ (ง่าย) เป็นรุ่นที่แนบมาของDatePickerDialog.javaในฉบับนั้น

แนวคิดในการหลีกเลี่ยงบั๊ก

ตั้งค่าฟังเพื่อnullในการสร้างและสร้างของคุณเองBUTTON_POSITIVEปุ่มในภายหลัง นั่นคือรายละเอียดด้านล่าง

ปัญหาเกิดขึ้นเพราะDatePickerDialog.javaอย่างที่คุณเห็นในแหล่งที่มาเรียกตัวแปรทั่วโลก ( mCallBack) ที่เก็บฟังที่ถูกส่งผ่านในตัวสร้าง:

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

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

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

ดังนั้นเคล็ดลับคือการให้nullผู้ฟังจัดเก็บเป็นผู้ฟังจากนั้นจึงหมุนชุดปุ่มของคุณเอง (ด้านล่างคือรหัสต้นฉบับจาก # 1 ที่อัปเดตแล้ว):

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

ตอนนี้มันจะทำงานได้เพราะการแก้ไขที่เป็นไปได้ที่ฉันโพสต์ข้างต้น

และเนื่องจากการDatePickerDialog.javaตรวจสอบnullเมื่อใดก็ตามที่อ่านmCallback( ตั้งแต่วันที่ API 3 / 1.5 ดูเหมือน --- ไม่สามารถตรวจสอบ Honeycomb แน่นอน) มันจะไม่เรียกข้อยกเว้น การพิจารณา Lollipop แก้ไขปัญหาฉันจะไม่เข้าไปดูมัน: เพียงแค่ใช้การติดตั้งเริ่มต้น (ครอบคลุมในคลาสที่ฉันให้ไว้)

ตอนแรกฉันกลัวที่จะไม่โทรclearFocus()แต่ฉันได้ทดสอบที่นี่และสายบันทึกก็สะอาด ดังนั้นบรรทัดที่ฉันเสนออาจไม่จำเป็นเลยหลังจากทั้งหมด แต่ฉันไม่รู้

ความเข้ากันได้กับระดับ API ก่อนหน้า(แก้ไข)

ขณะที่ผมชี้ให้เห็นในความคิดเห็นด้านล่างที่เป็นแนวคิดและคุณสามารถดาวน์โหลดระดับฉันใช้จากบัญชี Google ไดรฟ์ของฉัน วิธีที่ฉันใช้การใช้งานระบบเริ่มต้นจะใช้กับเวอร์ชันที่ไม่ได้รับผลกระทบจากข้อบกพร่อง

ฉันใช้สมมติฐานสองสามข้อ (ชื่อปุ่ม ฯลฯ ) ที่เหมาะสมกับความต้องการของฉันเพราะฉันต้องการลดรหัสสำเร็จรูปในคลาสไคลเอนต์ให้น้อยที่สุด ตัวอย่างการใช้งานเต็มรูปแบบ:

class YourActivity extends SherlockFragmentActivity implements OnDateSetListener

// ...

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

11
สุดยอดงานวิจัย! น่าเศร้าที่มันจำเป็น แต่ก็น่ากลัว
CommonsWare

1
ดีมาก! ฉันใช้ค้อนทุบหัวของฉันกับกำแพงในปัญหานี้ซึ่งได้รับการอัปเดตฟิลด์ข้อความของฉันและโทร / ไม่โทรกลับที่ถูกต้อง ขอบคุณ! ฉันสงสัยว่าวิธีแก้ปัญหาที่แนะนำคือ "การพิสูจน์ในอนาคต" หรือถ้าคุณคิดว่ามันจะทำให้เกิดปัญหาเมื่อแก้ไขข้อบกพร่อง?
ช่วง

1
@RomainGuidoux ดูคำตอบที่ปรับปรุงแล้วในตอนท้าย คลาสในลิงก์มีสมาร์ทเพื่อเรียกเมธอดนั้นใน jelly bean เท่านั้น ในทุกสิ่งที่อยู่ด้านล่างมันจะเลี่ยงผ่านสิ่งนั้นและใช้การใช้งานระบบเริ่มต้นการกำหนดเส้นทางการโทรของระบบสำหรับ ondateset ไปยังกิจกรรมของคุณ เป็นเพียงใน Jelly Bean จะใช้มาตรการเพิ่มเติม (หลีกเลี่ยงข้อผิดพลาด) ก่อนที่จะกำหนดเส้นทางการโทรกลับและที่เกี่ยวข้องกับการเรียกวิธีการที่รวงผึ้ง + แต่อีกครั้งเฉพาะใน JB
davidcsb

1
ทำงานสุดยอดเยี่ยมที่นี่ ฉันไม่มีคำว่าปัญหานี้ไร้สาระมากแค่ไหน
Bill Phillips

5
วิธีการเกี่ยวกับ TimePickerDialog ดูเหมือนว่า TimePickerDialog ไม่มี getTimePicker ()
lokoko

16

ฉันจะเพิ่ม riff ของฉันเองในโซลูชันที่ David Cesarino โพสต์ในกรณีที่คุณไม่ได้ใช้ Fragments และต้องการวิธีง่าย ๆ ในการแก้ไขในทุกเวอร์ชั่น (2.1 ถึง 4.1):

public class FixedDatePickerDialog extends DatePickerDialog {
  //I use a Calendar object to initialize it, but you can revert to Y,M,D easily
  public FixedDatePickerDialog(Calendar dateToShow, Context context, OnDateSetListener callBack) {
    super(context, null, dateToShow.get(YEAR), dateToShow.get(MONTH), dateToShow.get(DAY_OF_MONTH));
    initializePicker(callBack);
  }

  public FixedDatePickerDialog(Calendar dateToShow, Context context, int theme,
    OnDateSetListener callBack) {
    super(context, theme, null, dateToShow.get(YEAR), dateToShow.get(MONTH), dateToShow.get(DAY_OF_MONTH));
    initializePicker(callBack);
  }

  private void initializePicker(final OnDateSetListener callback) {
    try {
      //If you're only using Honeycomb+ then you can just call getDatePicker() instead of using reflection
      Field pickerField = DatePickerDialog.class.getDeclaredField("mDatePicker");
      pickerField.setAccessible(true);
      final DatePicker picker = (DatePicker) pickerField.get(this);
      this.setCancelable(true);
      this.setButton(DialogInterface.BUTTON_NEGATIVE, getContext().getText(android.R.string.cancel), (OnClickListener) null);
      this.setButton(DialogInterface.BUTTON_POSITIVE, getContext().getText(android.R.string.ok),
          new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                picker.clearFocus(); //Focus must be cleared so the value change listener is called
                callback.onDateSet(picker, picker.getYear(), picker.getMonth(), picker.getDayOfMonth());
              }
          });
    } catch (Exception e) { /* Reflection probably failed*/ }
  }
}

เพียงจำไว้ว่า: หากคุณกำลังเดินสายชื่อปุ่มเพื่อตกลงและยกเลิกการใช้มาตรฐานandroid.R.string.okและandroid.R.string.cancelเขตข้อมูลอาจดีกว่าแทนที่จะเป็นของผู้ใช้ และขอบคุณสำหรับการตอบกลับ
davidcsb

อ่าดีฉันไม่ได้สังเกตเห็นพวกเขาให้ตกลงและยกเลิกข้อความ ขอบคุณ!
dmon

รับข้อยกเว้น nullpointer ที่ super (บริบท, ธีม, null, dateToShow.get (YEAR), dateToShow.get (MONTH), dateToShow.get (DAY_OF_MONTH);
Kishore

เอ่อ ... คุณผ่านโมฆะไปแล้วdateToShowเหรอ? Null อื่น ๆ ที่มีอยู่จริงคือ "แก้ไข" ดังนั้นควรจะมี คุณเป็นเวอร์ชั่นไหน
dmon

คุณช่วยบอกฉันหน่อยได้ไหมว่าimportคุณมีFieldอะไรให้? ฉันมี 6 ตัวเลือกและไม่ทำงานเลย
Adil Malik

8

จนกว่าข้อผิดพลาดจะได้รับการแก้ไขฉันไม่แนะนำให้ใช้ DatePickerDialog หรือ TimePickerDialog ใช้ AlertDialog ที่ทำเองกับวิดเจ็ต TimePicker / DatePicker

เปลี่ยน TimePickerDialog ด้วย;

    final TimePicker timePicker = new TimePicker(this);
    timePicker.setIs24HourView(true);
    timePicker.setCurrentHour(20);
    timePicker.setCurrentMinute(15);

    new AlertDialog.Builder(this)
            .setTitle("Test")
            .setPositiveButton(android.R.string.ok, new OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Log.d("Picker", timePicker.getCurrentHour() + ":"
                            + timePicker.getCurrentMinute());
                }
            })
            .setNegativeButton(android.R.string.cancel,
                    new OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog,
                                int which) {
                            Log.d("Picker", "Cancelled!");
                        }
                    }).setView(timePicker).show();

เปลี่ยน DatePickerDialog ด้วย;

    final DatePicker datePicker = new DatePicker(this);
    datePicker.init(2012, 10, 5, null);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        datePicker.setCalendarViewShown(false);
    }

    new AlertDialog.Builder(this)
            .setTitle("Test")
            .setPositiveButton(android.R.string.ok, new OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Log.d("Picker", datePicker.getYear() + " "
                            + (datePicker.getMonth() + 1) + " "
                            + datePicker.getDayOfMonth());
                }
            })
            .setNegativeButton(android.R.string.cancel,
                    new OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog,
                                int which) {
                            Log.d("Picker", "Cancelled!");
                        }
                    }).setView(datePicker).show();

4

หนึ่งสำหรับ TimePicker ซึ่งอิงตามโซลูชันของDavid Cesarino, "TL; DR: 1-2-3 ขั้นตอนง่าย ๆ สำหรับการแก้ไขปัญหาระดับโลก"

TimePickerDialog ไม่ได้มีฟังก์ชั่นเช่น DatePickerDialog.getDatePicker ดังนั้นผู้ฟังOnTimeSetListenerจะต้องได้รับการจัดเตรียม เพียงเพื่อให้ความคล้ายคลึงกันกับวิธีแก้ปัญหา DatePicker ฉันได้รักษาแนวคิด mListener เก่า คุณสามารถเปลี่ยนได้ถ้าคุณต้องการ

การโทรและผู้ฟังเหมือนกับโซลูชันดั้งเดิม เพียงแค่รวม

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

ขยายชั้นผู้ปกครอง

... implements OnDateSetListener, OnTimeSetListener

Implement

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

ตัวอย่างการโทร

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


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

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

(อัปเดตเพื่อจัดการการยกเลิก)

public class TimePickerDialogFragment extends DialogFragment {

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

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

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

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

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

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

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

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

มันไม่ทำงานสำหรับฉันเมื่อทดลองใช้กับ s3 API 18
AbdelHady

3

ในกรณีที่ทุกคนต้องการวิธีแก้ปัญหาอย่างรวดเร็วนี่คือรหัสที่ฉันใช้:

public void showCustomDatePicker () {

final DatePicker mDatePicker = (DatePicker) getLayoutInflater().
        inflate(R.layout.date_picker_view, null);
//Set an initial date for the picker
final Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
//Set the date now
mDatePicker.updateDate(year, month, day);

//create the dialog
AlertDialog.Builder mBuilder = new Builder(this);
//set the title
mBuilder.setTitle(getString(R.string.date_picker_title))
    //set our date picker
    .setView(mDatePicker)
    //set the buttons 
.setPositiveButton(android.R.string.ok, new OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        //whatever method you choose to handle the date changes
            //the important thing to know is how to retrieve the data from the picker
        handleOnDateSet(mDatePicker.getYear(), 
                mDatePicker.getMonth(), 
                mDatePicker.getDayOfMonth());
    }
})
.setNegativeButton(android.R.string.cancel, new OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        dialog.dismiss();
    }
})
//create the dialog and show it.
.create().show();

}

โดยที่ layout.date_picker_view เป็นทรัพยากรรูปแบบเรียบง่ายที่มี DatePicker เนื่องจากเป็นเพียงองค์ประกอบ:

<!xml version="1.0" encoding="utf-8">
<DatePicker xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/date_picker"
android:layout_width="fill_parent"   
android:spinnersShown="true" 
android:calendarViewShown="false"
android:layout_height="fill_parent"/>

นี่คือแบบฝึกหัดฉบับเต็มในกรณีที่คุณสนใจ


มันใช้งานได้ดีสำหรับฉัน เข้าใจง่ายใช้งานง่าย! ทดสอบในวันที่ 4.4
erdomester

3

ทางออกที่ง่ายของฉัน เมื่อคุณต้องการทำให้มันเริ่มทำงานอีกครั้งให้เรียกใช้ "resetFired" (พูดเมื่อเปิดกล่องโต้ตอบอีกครั้ง)

private class FixedDatePickerDialogListener implements DatePickerDialog.OnDateSetListener{
    private boolean fired;

    public void resetFired(){
        fired = false;
    }

    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        if (fired) {
            Log.i("DatePicker", "Double fire occurred.");
            return;//ignore and return.
        } 
        //put your code here to handle onDateSet
        fired = true;//first time fired 
    }
}

@AbdelHady การแก้ไขรหัสของคุณไม่ถูกต้อง แต่ฉันได้แก้ไขเพื่อให้ชัดเจนยิ่งขึ้น ไม่จำเป็นต้องเพิ่มเมธอด "isJellyBeanOrAbove ()" โดยไม่มีข้อได้เปรียบเพียงเพิ่มความซับซ้อนที่ไม่จำเป็น การรีเซ็ตการโทรยิงราคาถูก
Daniel Ryan

รหัสที่นี่ไม่ถูกต้องเพราะonDateSetจะถูกเรียกเพียงครั้งเดียวเมื่อกล่องโต้ตอบถูกปิดด้วยวิธีการใด ๆ และหากค่าเฉลี่ยนั้นถูกตั้งเวลาแล้วมันจะถูกยิงอีกครั้งดังนั้นเราต้องจับสายที่สองไม่ใช่สายแรก ตามที่คุณทำ
AbdelHady

เกี่ยวกับisJellyBeanOrAbove()รุ่นที่ต่ำกว่า Jellybean ไม่มีข้อผิดพลาดที่คำถามทั้งหมดนี้เป็นเรื่องเกี่ยวกับ & เนื่องจากเราต้องการที่จะรับสายที่สองรหัสจะไม่ทำงานจนกว่าเราจะตรวจสอบนี้เชื่อฉันฉันได้ลองแล้ว โค้ดเกี่ยวกับอีมูเลเตอร์ & อุปกรณ์จริง (มีหลายเวอร์ชั่น) หลายครั้ง & มันทำงานเหมือนมีเสน่ห์
AbdelHady

ฉันไม่แน่ใจว่าคุ้มค่ากับการลงคะแนนเสียงหรือไม่ ฉันโพสต์เมื่อ 2 ปีที่แล้วนี่เป็นแอปพลิเคชั่นเชิงพาณิชย์ของเราตั้งแต่นั้นทำงานได้ดี ไม่มีข้อบกพร่องที่รายงานโดยทีมงานควบคุมคุณภาพของเราหรือผู้ใช้นับพันของเรา นี่เป็นคำตอบง่ายๆที่ผู้คนสามารถขยายได้ โทรหา "resetFired" เมื่อคุณต้องการที่จะยิงอีกครั้ง
Daniel Ryan

ในแอพของเรา "isJellyBeanOrAbove" ไม่จำเป็นต้องใช้ แอพจะทำงานได้ดีในทุกรุ่นหากคุณเรียกว่า "resetFired" ในพื้นที่ที่ถูกต้อง
Daniel Ryan

3

ตามคำตอบที่ยอดเยี่ยมของ Ankur Chaudhary ในTimePickerDialogประเด็นที่คล้ายกันหากเราตรวจสอบภายในonDateSetถ้ามองในมุมมองที่กำหนดisShown()มันจะแก้ปัญหาทั้งหมดด้วยความพยายามน้อยที่สุดโดยไม่จำเป็นต้องขยายตัวเลือกหรือตรวจสอบธงที่น่าเกลียดบางรอบรหัส หรือแม้กระทั่งการตรวจสอบเวอร์ชั่นของระบบปฏิบัติการให้ทำดังต่อไปนี้:

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

และแน่นอนว่าสามารถทำได้onTimeSetเหมือนกันตามคำตอบของ Ankur


1
คำตอบที่ดีที่สุดจากทั้งหมด!
hiew1

2

วิธีที่ฉันจัดการสถานการณ์นี้คือการใช้แฟล็กและเอาชนะเมธอด onCancel และ onDismiss

onCancel ได้รับการเรียกเมื่อผู้ใช้สัมผัสนอกกล่องโต้ตอบหรือปุ่มย้อนกลับ onDismiss ถูกเรียกเสมอ

การตั้งค่าสถานะในวิธี onCancel สามารถช่วยกรองในวิธี onDismiss เจตนาของผู้ใช้: ยกเลิกการกระทำหรือการกระทำที่กระทำ ด้านล่างโค้ดบางส่วนที่แสดงความคิด

public class DatePickerDialogFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener {

    private boolean cancelDialog = false;
    private int year;
    private int month;
    private int day;

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        DatePickerDialog dpd = new DatePickerDialog(getActivity(), this, year, month, day);
        return dpd;
    }

    public void setDatePickerDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        super.onCancel(dialog);
        cancelDialog = true;
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        if (!cancelDialog) {
          #put the code you want to execute if the user clicks the done button
        }
    }

    @Override
    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        setDatePickerDate(year, monthOfYear, dayOfMonth);
    }
}

1

มีวิธีแก้ปัญหาที่ง่ายมากหากแอปพลิเคชันของคุณไม่ได้ใช้แถบการกระทำ โปรดทราบว่าแอพบางตัวใช้ฟังก์ชั่นนี้ใช้งานได้เนื่องจากการยกเลิกตัวเลือกวันที่มีความหมายพิเศษ (เช่นจะล้างฟิลด์วันที่เป็นสตริงว่างเปล่าซึ่งสำหรับบางแอพเป็นประเภทอินพุตที่ถูกต้องและมีความหมาย ) และการใช้ธงบูลีนเพื่อป้องกันไม่ให้การตั้งค่าวันที่สองครั้งบน OK จะไม่ช่วยคุณในกรณีนี้

เรื่อง การแก้ไขจริงคุณไม่จำเป็นต้องสร้างปุ่มใหม่หรือโต้ตอบของคุณเอง ประเด็นคือต้องเข้ากันได้กับทั้ง Android รุ่นเก่ารุ่น buggy (4. ) และรุ่นอื่น ๆ ในอนาคตแม้ว่าสิ่งหลังนี้จะเป็นไปไม่ได้แน่นอน โปรดทราบว่าใน Android 2. , onStop () สำหรับ android.app.Dialog ไม่ได้ทำอะไรเลยและใน 4 * มันจะ mActionBar.setShowHideAnimationEnabled (false) ซึ่งมีความสำคัญเฉพาะเมื่อแอปของคุณมีแถบการกระทำ onStop () ใน DatePickerDialog ซึ่งสืบทอดมาจากกล่องโต้ตอบเฉพาะสนับสนุน mDatePicker.clearFocus () (เป็นของการแก้ไขล่าสุดไปยังแหล่ง Android 4.3) ซึ่งดูเหมือนจะไม่จำเป็น

ดังนั้นการแทนที่ onStop () ด้วยวิธีการที่ไม่ทำอะไรเลยในหลาย ๆ กรณีจะแก้ไขแอปของคุณและตรวจสอบให้แน่ใจว่ามันจะยังคงอยู่ในอนาคตอันใกล้ ดังนั้นเพียงแค่ขยายชั้น DatePickerDialog ด้วยตัวคุณเองและแทนที่ onStop () ส่วนเล็กน้อยเป็นวิธีการหลอกตา นอกจากนี้คุณยังจะต้องให้ก่อสร้างหนึ่งหรือสองตามความต้องการของคุณ โปรดทราบว่าไม่ควรพยายามที่จะแก้ไขปัญหานี้มากเกินไปโดยการพยายามทำบางสิ่งบางอย่างกับแถบกิจกรรมโดยตรงเนื่องจากจะจำกัดความเข้ากันได้ของคุณกับ Android เวอร์ชันล่าสุดเท่านั้น นอกจากนี้โปรดทราบว่ามันจะเป็นการดีที่จะสามารถเรียก super สำหรับ DatePicker's onStop () ได้เนื่องจากข้อบกพร่องนั้นอยู่ใน onStop () ใน DatePickerDialog เท่านั้น อย่างไรก็ตามสิ่งนี้จะทำให้คุณต้องเรียก super.super.onStop () จากคลาสที่คุณกำหนดเอง ซึ่ง Java จะไม่ยอมให้คุณทำตามที่ขัดแย้งกับปรัชญาการห่อหุ้ม :) ด้านล่างเป็นคลาสเล็ก ๆ ของฉันที่ฉันใช้ในการ verride DatePickerDialog ฉันหวังว่าความคิดเห็นนี้จะเป็นประโยชน์สำหรับใครบางคน 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.
}   

}


แม้ว่าจะใช้ ActionBar อยู่ก็ตามนี่อาจเป็นวิธีแก้ปัญหาที่ยอมรับได้หากคุณไม่เคยซ่อน / แสดง ActionBar เพื่อให้สิ่งต่าง ๆ ปลอดภัยเป็นพิเศษคุณสามารถแทนที่ onStart เพื่อไม่ทำสิ่งใดเช่นกัน และแม้ว่าคุณจะซ่อน / แสดงเป็นเพียงภาพเคลื่อนไหวที่ปิดการใช้งาน
Daniel

0


ลองแนวคิดด้านล่าง

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


onDateSet () วิธีการโทรสองครั้ง (หากคุณกำลังตรวจสอบใน emulator.it โทรสองครั้งถ้าใช้อุปกรณ์จริงแล้วมันจะโทรอย่างถูกต้องครั้งเดียวถ้าคุณใช้ emulator แล้วใช้ counter.if คุณกำลังทำงานในอุปกรณ์จริงแล้ว ไม่สนใจตัวแปรตัวนับสำหรับอุปกรณ์จริงมันใช้งานได้สำหรับฉัน)
เมื่อผู้ใช้คลิกปุ่มใน DatePickerDialog
สำหรับสิ่งนี้คุณควรรักษาค่าตัวนับและไม่มีอะไรทำเมื่อ mothod เรียกครั้งแรกและดำเนินการเมื่อเมธอดเรียกครั้งที่ 2
อ้างอิงตัวอย่างโค้ดด้านล่าง

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



สำหรับการยกเลิก dilalog datepicker มันทำงานสำหรับฉันสำหรับจำลองมันไม่ได้ปลุก

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


มันทำงานให้ฉันสำหรับอุปกรณ์จริง แต่สำหรับตัวจำลองไม่ทำงานอย่างถูกต้องฉันคิดว่ามันเป็นข้อผิดพลาดจำลอง Android


0

วิธีแก้ปัญหาง่ายๆคือใช้บูลีนเพื่อข้ามการรันครั้งที่สอง

boolean isShow = false; // define global variable


// when showing time picker
TimePickerDialog timeDlg = new TimePickerDialog( this, new OnTimeSetListener()
            {

                @Override
                public void onTimeSet( TimePicker view, int hourOfDay, int minute )
                {
                    if ( isShow )
                    {
                        isShow = false;
                        // your code
                    }

                }
            }, 8, 30, false );

timeDlg.setButton( TimePickerDialog.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick( DialogInterface dialog, int which )
                {
                    isShow = false;
                }
            } );
timeDlg.setButton( TimePickerDialog.BUTTON_POSITIVE, "Set", new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick( DialogInterface dialog, int which )
                {
                    isShow = true;
                }
            } );

timeDlg.show();

อีกครั้งเหมือนที่ฉันบอกคนอื่นก่อนหน้าคุณนี่ไม่ได้แก้ปัญหา ฉันไม่ต้องการเสียงหยาบคาย แต่พวกคุณควรทดสอบวิธีการแก้ปัญหาของคุณก่อนโพสต์ที่นี่ ... เพื่อพิสูจน์ประเด็นของฉันใช้รหัสของคุณและให้ผู้ใช้กดกลับเพื่อยกเลิกการโต้ตอบและดูสิ่งที่ฉันหมายถึง สาเหตุของข้อผิดพลาดที่มีการonStopเรียกวิธีเมื่อมันไม่ควร ... มันยิงสองครั้งเป็นผลมาจากข้อผิดพลาด
davidcsb

ถ้าฉันไม่ได้ชัดเจนพอให้ฉันเป็น: onDateSetกลับกดเพื่อยกเลิกการโทรโต้ตอบ ดังนั้นเสีย
davidcsb

ขอบคุณที่แสดงข้อผิดพลาด ฉันแก้ไขคำตอบเพื่อที่จะทำงานกับปุ่มย้อนกลับ คำตอบของคุณใช้งานได้ แต่ DatePicker dp = picker.getDatePicker (); ไม่ทำงานกับ TimePickers เนื่องจากไม่ได้เพิ่มวิธี getTimePicker () ดังนั้นนี่จะเป็นคำตอบที่ถูกต้อง
chamikaw

เราจะพยายามข้ามการรันครั้งที่สองได้อย่างไร !! ฉันลองหลายครั้งแล้วเมื่อปิดกล่องโต้ตอบด้วยวิธีใดก็ตามonDateSetจะถูกเรียกหนึ่งครั้ง แต่เมื่อเลือก "เสร็จสิ้น" หรือ "ตั้ง" แล้วมันจะถูกเรียกสองครั้ง ดังนั้นเราจำเป็นต้องข้ามเฉพาะคนแรกดังนั้นหากมีการเรียกสองครั้งแล้วเท่านั้นเราจึงมีวันที่ที่ถูกต้อง
AbdelHady

0

คุณสามารถแทนที่ onCancel () และใช้ setOnDismissListener () เพื่อตรวจจับการกระทำของผู้ใช้ที่เป็นลบ และด้วย DatePickerDialog.BUTTON_POSITIVE คุณรู้ว่าผู้ใช้ต้องการกำหนดวันที่ใหม่

 DatePickerDialog mDPD = new DatePickerDialog(
                      getActivity(), mOnDateSetListener, mYear, mMonth, mDay);
 mDPD.setOnCancelListener(new OnCancelListener() {
    @Override
    public void onCancel(DialogInterface dialog) {
        // do something onCancek
        setDate = false;
    }
 });

 mDPD.setOnDismissListener(new OnDismissListener() {
    @Override
    public void onDismiss(DialogInterface arg0) {
        // do something onDismiss
        setDate = false;
    }
});

mDPD.setButton(DatePickerDialog.BUTTON_POSITIVE, "Finish", new DatePickerDialog.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
        // user set new date
        setDate = true;
    }
});

จากนั้นตรวจสอบ setDate:

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

0

นี่คือคลาสการแก้ปัญหาของฉันสำหรับ DatePickerDialog เมื่อปุ่มยกเลิกรวมถึงการละทิ้งมันด้วยปุ่มย้อนกลับ คัดลอกและใช้งานในรูปแบบของ DatePickerDialog (เนื่องจากผู้ฟังเป็น stateful เราต้องสร้างอินสแตนซ์ใหม่เมื่อใช้งานมิฉะนั้นต้องใช้รหัสเพิ่มเติมเพื่อให้ทำงานได้)

ใช้:

new FixedDatePickerDialog(this,
            new FixedOnDateSetListener() {

                @Override
                public void onDateSet(DatePicker view, int year,
                        int monthOfYear, int dayOfMonth) {
                    if (isOkSelected()) {
                        // when DONE button is clicked
                    }
                }

            }, year, month, day).show();

ประเภท:

public class FixedDatePickerDialog extends DatePickerDialog {
private final FixedOnDateSetListener fixedCallback;
public FixedDatePickerDialog(Context context,
        FixedOnDateSetListener callBack, int year, int monthOfYear,
        int dayOfMonth) {
    super(context, callBack, year, monthOfYear, dayOfMonth);
    fixedCallback = callBack;
    this.setButton(DialogInterface.BUTTON_NEGATIVE,
            context.getString(R.string.cancel), this);
    this.setButton(DialogInterface.BUTTON_POSITIVE,
            context.getString(R.string.done), this);
}

@Override
public void onClick(DialogInterface dialog, int which) {
    if (which == BUTTON_POSITIVE) {
        fixedCallback.setOkSelected(true);
    } else {
        fixedCallback.setOkSelected(false);
    }
    super.onClick(dialog, which);
}

public abstract static class FixedOnDateSetListener implements
        OnDateSetListener {
    private boolean okSelected = false;

    @Override
    abstract public void onDateSet(DatePicker view, int year,
            int monthOfYear, int dayOfMonth);

    public void setOkSelected(boolean okSelected) {
        this.okSelected = okSelected;
    }

    public boolean isOkSelected() {
        return okSelected;
    }
}

}


0

ฉันใช้ตัวใช้เลือกวันที่ตัวเลือกเวลาและตัวเลือกตัวเลข ตัวใช้เลือกจำนวนเรียก onValueChanged เมื่อใดก็ตามที่ผู้ใช้เลือกหมายเลขก่อนที่ตัวเลือกจะถูกยกเลิกดังนั้นฉันจึงมีโครงสร้างเช่นนี้เพื่อทำสิ่งที่มีค่าเมื่อตัวเลือกถูกไล่ออก:

public int interimValue;
public int finalValue;

public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
    this.interimValue = newVal;
}

public void onDismiss(DialogInterface dialog) {
    super.onDismiss(dialog);
    this.finalValue = this.interimValue;
}

ฉันขยายสิ่งนี้เพื่อตั้งค่า onClickListeners แบบกำหนดเองสำหรับปุ่มของฉันพร้อมอาร์กิวเมนต์เพื่อดูว่ามีการคลิกปุ่มใด ตอนนี้ฉันสามารถตรวจสอบว่าปุ่มใดถูกแตะก่อนที่ฉันจะตั้งค่าสุดท้ายของฉัน:

public int interimValue;
public int finalValue;
public boolean saveButtonClicked;

public void setup() {
    picker.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.BUTTON_SAVE), new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            picker.onClick(dialog, which); // added for Android 5.0
            onButtonClicked(true);
        }
    });
    picker.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.BUTTON_CANCEL), new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int which) {
            picker.onClick(dialog, which); // added for Android 5.0
            onButtonClicked(false);
        }
    });
}

public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
    this.interimValue = newVal;
}

public void onButtonClicked(boolean save) {
    this.saveButtonClicked = save;
}

public void onDismiss(DialogInterface dialog) {
    super.onDismiss(dialog);
    if (this.saveButtonClicked) {
        // save
        this.finalValue = this.interimValue;
    } else {
        // cancel
    }
}

จากนั้นฉันก็ขยายเวลาเพื่อให้ทำงานกับชนิดของวันที่และเวลาสำหรับตัวใช้เลือกวันที่และเวลารวมถึงชนิด int สำหรับตัวเลือกตัวเลข

ฉันโพสต์สิ่งนี้เพราะฉันคิดว่ามันง่ายกว่าโซลูชันบางตัวด้านบน แต่ตอนนี้เมื่อฉันรวมรหัสทั้งหมดแล้วฉันคิดว่ามันไม่ง่ายกว่านี้มาก! แต่มันก็พอดีกับโครงสร้างที่ฉันมีอยู่แล้ว

อัพเดทสำหรับอมยิ้ม:เห็นได้ชัดว่าข้อผิดพลาดนี้ไม่ได้เกิดขึ้นในอุปกรณ์ Android 4.1-4.4 ทั้งหมดเพราะฉันได้รับรายงานจากผู้ใช้ที่มีตัวเลือกวันที่และเวลาไม่ได้เรียกการเรียกกลับ onDateSet และ onTimeSet และข้อผิดพลาดได้รับการแก้ไขอย่างเป็นทางการใน Android 5.0 วิธีการของฉันใช้งานได้เฉพาะกับอุปกรณ์ที่มีบั๊กอยู่เนื่องจากปุ่มที่กำหนดเองของฉันไม่ได้เรียก onClick handler ของไดอะล็อกซึ่งเป็นที่เดียวที่ onDateSet และ onTimeSet ถูกเรียกเมื่อไม่มีบั๊ก ฉันอัปเดตรหัสของฉันด้านบนเพื่อเรียกใช้ onClick ของไดอะล็อกดังนั้นตอนนี้มันทำงานได้หรือไม่ข้อผิดพลาดที่มีอยู่


0

ฉันชอบคำตอบของ David Cesarino ด้านบน แต่ต้องการบางสิ่งที่เป็นการแทนที่กล่องโต้ตอบที่เสียหายและจะใช้งานกล่องโต้ตอบใด ๆ ที่อาจหายไปยกเลิก / มีพฤติกรรมการยกเลิกที่ไม่ถูกต้อง นี่คือคลาสที่ได้รับมาสำหรับ DatePickerDialog / TimePickerDialog ที่ควรทำงานในรูปแบบของการแทนที่ นี่ไม่ใช่มุมมองที่กำหนดเอง มันใช้กล่องโต้ตอบของระบบ แต่เพียงแค่เปลี่ยนการทำงานของปุ่มยกเลิก / กลับไปทำงานตามที่คาดไว้

สิ่งนี้ควรใช้กับ API ระดับ 3 และสูงกว่า ดังนั้นโดยทั่วไป Android ทุกรุ่น (ฉันทดสอบโดยเฉพาะกับ jellybean และ lollipop)

DatePickerDialog:

package snappy_company_name_here;

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

/**
 * This is a modified version of DatePickerDialog that correctly handles cancellation behavior since it's broken on jellybean and
 * kitkat date pickers.
 *
 * Here is the bug: http://code.google.com/p/android/issues/detail?id=34833
 * Here is an SO post with a bunch of details: http://stackoverflow.com/questions/11444238/jelly-bean-datepickerdialog-is-there-a-way-to-cancel
 *
 * @author stuckj, created on 5/5/15.
 */
public class DatePickerDialog extends android.app.DatePickerDialog implements DialogInterface.OnClickListener
{
    final CallbackHelper callbackHelper;

    // NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
    private static class CallbackHelper implements OnDateSetListener
    {
        private final OnDateSetListener callBack;
        private boolean dialogButtonPressHandled = false; // To prevent setting the date when the dialog is dismissed...

        // NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
        public CallbackHelper(final OnDateSetListener callBack)
        {
            this.callBack = callBack;
        }

        @Override
        public void onDateSet(final DatePicker view, final int year, final int monthOfYear, final int dayOfMonth)
        {
            if (!dialogButtonPressHandled && (callBack != null))
            {
                callBack.onDateSet(view, year, monthOfYear, dayOfMonth);
            }
        }
    }

    /**
     * Sets the positive and negative buttons to use the dialog callbacks we define.
     */
    private void setButtons(final Context context)
    {
        setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), this);
        setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), this);
    }

    @Override
    public void onClick(final DialogInterface dialog, final int which)
    {
        // ONLY call the super method in the positive case...
        if (which == DialogInterface.BUTTON_POSITIVE)
        {
            super.onClick(dialog, which);
        }

        callbackHelper.dialogButtonPressHandled = true;
    }

    @Override
    public void onBackPressed()
    {
        getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
    }

    // Need this so we can both pass callbackHelper to the super class and save it off as a variable.
    private DatePickerDialog(final Context context,
                             final OnDateSetListener callBack,
                             final int year,
                             final int monthOfYear,
                             final int dayOfMonth,
                             final CallbackHelper callbackHelper)
    {
        super(context, callbackHelper, year, monthOfYear, dayOfMonth);
        this.callbackHelper = callbackHelper;
        setButtons(context);
    }

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

    // Need this so we can both pass callbackHelper to the super class and save it off as a variable.
    private DatePickerDialog(final Context context, final int theme, final OnDateSetListener listener, final int year,
                             final int monthOfYear, final int dayOfMonth, final CallbackHelper callbackHelper)
    {
        super(context, theme, callbackHelper, year, monthOfYear, dayOfMonth);
        this.callbackHelper = callbackHelper;
        setButtons(context);
    }

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

TimePickerDialog:

package snappy_company_name_here;

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

/**
 * This is a modified version of TimePickerDialog that correctly handles cancellation behavior since it's broken on jellybean and
 * kitkat date pickers.
 *
 * Here is the bug: http://code.google.com/p/android/issues/detail?id=34833
 * Here is an SO post with a bunch of details: http://stackoverflow.com/questions/11444238/jelly-bean-datepickerdialog-is-there-a-way-to-cancel
 *
 * @author stuckj, created on 5/5/15.
 */
public class TimePickerDialog extends android.app.TimePickerDialog implements DialogInterface.OnClickListener
{
    final CallbackHelper callbackHelper;

    // NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
    private static class CallbackHelper implements OnTimeSetListener
    {
        private final OnTimeSetListener callBack;
        private boolean dialogButtonPressHandled = false; // To prevent setting the date when the dialog is dismissed...

        // NOTE: Must be static since we're using it in a super constructor call. Which is annoying, but necessary
        public CallbackHelper(final OnTimeSetListener callBack)
        {
            this.callBack = callBack;
        }

        @Override
        public void onTimeSet(final TimePicker view, final int hourOfDay, final int minute)
        {
            if (!dialogButtonPressHandled && (callBack != null))
            {
                callBack.onTimeSet(view, hourOfDay, minute);
            }
        }
    }

    /**
     * Sets the positive and negative buttons to use the dialog callbacks we define.
     */
    private void setButtons(final Context context)
    {
        setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), this);
        setButton(DialogInterface.BUTTON_POSITIVE, context.getString(android.R.string.ok), this);
    }

    @Override
    public void onClick(final DialogInterface dialog, final int which)
    {
        // ONLY call the super method in the positive case...
        if (which == DialogInterface.BUTTON_POSITIVE)
        {
            super.onClick(dialog, which);
        }

        callbackHelper.dialogButtonPressHandled = true;
    }

    @Override
    public void onBackPressed()
    {
        getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
    }

    // Need this so we can both pass callbackHelper to the super class and save it off as a variable.
    private  TimePickerDialog(final Context context,
                              final OnTimeSetListener callBack,
                              final int hourOfDay, final int minute, final boolean is24HourView, final CallbackHelper callbackHelper)
    {
        super(context, callbackHelper, hourOfDay, minute, is24HourView);
        this.callbackHelper = callbackHelper;
        setButtons(context);
    }

    /**
     * @param context Parent.
     * @param callBack How parent is notified.
     * @param hourOfDay The initial hour.
     * @param minute The initial minute.
     * @param is24HourView Whether this is a 24 hour view, or AM/PM.
     */
    public TimePickerDialog(final Context context,
                            final OnTimeSetListener callBack,
                            final int hourOfDay, final int minute, final boolean is24HourView)
    {
        this(context, callBack, hourOfDay, minute, is24HourView, new CallbackHelper(callBack));
    }

    // Need this so we can both pass callbackHelper to the super class and save it off as a variable.
    private TimePickerDialog(final Context context, final int theme, final OnTimeSetListener callBack, final int hourOfDay,
                            final int minute, final boolean is24HourView, final CallbackHelper callbackHelper)
    {
        super(context, theme, callbackHelper, hourOfDay, minute, is24HourView);
        this.callbackHelper = callbackHelper;
        setButtons(context);
    }

    /**
     * @param context Parent.
     * @param theme the theme to apply to this dialog
     * @param callBack How parent is notified.
     * @param hourOfDay The initial hour.
     * @param minute The initial minute.
     * @param is24HourView Whether this is a 24 hour view, or AM/PM.
     */
    public TimePickerDialog(final Context context, final int theme, final OnTimeSetListener callBack, final int hourOfDay,
                            final int minute, final boolean is24HourView)
    {
        this(context, theme, callBack, hourOfDay, minute, is24HourView, new CallbackHelper(callBack));
    }
}

นี่คล้ายกับคำตอบของ The Sea ด้านบน
stuckj

0

เวอร์ชันการทำงานของฉันกับ ClearButton โดยใช้ Lambda Expressions:

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

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

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

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

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


    public interface OnDateClearListener {
        void onDateClear();
    }

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

0

สำหรับ TimePickerDialog วิธีแก้ปัญหาสามารถเป็นดังนี้:

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

            private KitKatTimeSetListener() {
            }

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

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

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

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

        return timePickerDialog;
    }

ฉันมอบหมายเหตุการณ์ทั้งหมดให้กับผู้ห่อหุ้ม KitKatSetTimeListener และส่งกลับไปที่ OnTimeSetListener ดั้งเดิมเท่านั้นในกรณีที่ BUTTON_POSITIVE ถูกคลิก


0

หลังจากทดสอบความแออัดบางอย่างที่โพสต์ที่นี่ฉันเองคิดว่าวิธีนี้เป็นวิธีที่ง่ายที่สุด ฉันผ่าน "null" เป็นผู้ฟังของฉันในตัวสร้าง DatePickerDialog และเมื่อฉันคลิกปุ่ม "ตกลง" ฉันเรียก onDateSearchSetListener ของฉัน:

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

-1

ฉันรู้ว่าโพสต์นี้อยู่ที่นี่มาเกือบปีแล้ว แต่ฉันคิดว่าฉันควรโพสต์สิ่งที่ค้นพบ คุณยังคงสามารถรักษาผู้ฟัง (แทนที่จะตั้งค่าให้เป็น mull) และยังคงมีงานนี้ตามที่คาดไว้ กุญแจสำคัญคือการตั้งค่าปุ่ม "ตกลง" หรือ (และ) ปุ่ม "ยกเลิก" โดยปริยาย ฉันทดสอบและใช้งานได้อย่างสุดซึ้งสำหรับฉัน ผู้ฟังไม่ถูกไล่ออกสองครั้ง

ดูตัวอย่างนี้

private void setTime(){
final Calendar c = Calendar.getInstance();
int hour = c.get(Calendar.HOUR_OF_DAY);
int minute = c.get(Calendar.MINUTE);

final TimePickerDialog timepicker = new TimePickerDialog(this.getActivity(),
        timePickerListener,
        hour, 
        minute, 
        DateFormat.is24HourFormat(getActivity()));

timepicker.setButton(DialogInterface.BUTTON_POSITIVE, "Print", new    
    android.content.DialogInterface.OnClickListener(){
        @Override
        public void onClick(DialogInterface dialog,int which) {
            print = true;
            timepicker.dismiss();           
        }
});

timepicker.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new 
    android.content.DialogInterface.OnClickListener(){
        @Override
        public void onClick(DialogInterface dialog,int which){
            print = false;
            timepicker.dismiss();       
        }
});

timepicker.setCancelable(false);
timepicker.show();
}

ธรรมดาและเรียบง่ายมันไม่ทำงานในแบบที่คุณบอก timePickerListenerยังคงถูกเรียกใช้โดยไม่คำนึงถึงสิ่งที่คุณทำในกล่องโต้ตอบ ฉันไม่จำเป็นต้องไปทดสอบแม้จะรู้ว่าคุณก็ต้องมองไปที่แหล่งที่มา : ถ้าคุณไม่ได้ตั้งค่าให้null, tryNotifyTimeSet()จะเรียกผู้ฟังเป็นonTimeSet()ทั้งในของมันและonClick() onStop()
davidcsb

ในคำอื่น ๆ จะไม่ปล่อยให้ระดับพื้นเมืองถืออ้างอิงถึงฟังของคุณ (เช่นผ่านมันในตัวสร้าง) วิธีจัดการกับปุ่มนั้นไม่เกี่ยวข้อง
davidcsb

สวัสดีเดวิดคุณไม่ได้ทดสอบสิ่งนี้และคุณคิดว่ามันใช้งานไม่ได้ นี่เป็นรหัสที่แน่นอนที่ฉันใช้อยู่ในแอพของฉันและใช้งานได้ดีเหมือนแชมป์เปี้ยน
จระเข้

1) ฉันไม่เคยบอกว่ามันใช้งานไม่ได้ ฉันบอกว่ามันไม่ทำงานอย่างที่คุณบอก โปรดอ่านอีกครั้ง 2) คุณได้ละเมิด LSP และ SRP เสียชั่วโมงคนที่จะไม่จำเป็นต้องเปลี่ยนทั้งหมดของคุณทั้งตรรกะไคลเอนต์ที่ไม่จำเป็นต้องมีการเปลี่ยนแปลงใด ๆ จะเริ่มต้นด้วย 3) คำตอบของคุณตอบคำถามใช่มิฉะนั้นฉันจะตั้งค่าสถานะเพื่อลบออกว่า "ไม่ใช่คำตอบ" แต่ 4) คำตอบของคุณยังคงมีอยู่ (ขออภัยเกี่ยวกับเรื่องนี้) ไม่มีประสิทธิภาพมากและไม่ได้แก้ไขปัญหาการออกแบบพื้นฐาน ) ดังนั้น downvote เท่านั้น 6) คุณปิดการใช้งานกลับและทำให้มันเป็นหน้าต่างโมดัลเพื่อบังคับบูลีน ดังนั้น 6 ประเด็นที่ร้ายแรงที่นั่น!
davidcsb
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.