1. 程式人生 > >[Android] 更好的解決 "返回鍵或取消時自動回撥DatePickerDialog的方法onDateSet()" 的問題

[Android] 更好的解決 "返回鍵或取消時自動回撥DatePickerDialog的方法onDateSet()" 的問題

自從忙完工作變動的事情後好久沒寫博了, 內心愧疚啊.... 說好的堅持學習呢... TAT

好吧, 迴歸正題.  用過 Android自帶的DatePickerDialog的預設樣式是這樣的:


只有一個 "完成" 按鈕...

如果將完成選擇日期的觸發事件放在方法onDateSet(), 那麼無論是點選返回鍵或螢幕outSide的地方,  或者點選 "完成"按鈕, 都會自動取消這個日期選擇彈出框時並每次呼叫onDateSet(). 就是說, 這時, 放在onDateSet()裡面的觸發事件無論如何都會被呼叫啊, 簡直不能忍...  要是彈出框的介面除了 "完成" 按鈕, 還有 "取消" 按鈕, 並且能控制什麼時候才去呼叫完成日期選擇的觸發事件,  作為社會主義接班人的我們, 該會多開心啊~~~

那麼問題來了, 除了自定一個DatePickerDialog, 比如有種做法是繼承DatePickerDialog, 然後在onStop() 裡面去註釋裡面的super.onStop() 達到不呼叫 onDateSet() 的目的.  但自帶的DatePickerDialog究竟支不支援我們上面的需求呢?       真相只有一個:

闊以的~

我們翻開DatePickerDialog所繼承的AlertDialog類原始碼,  其實發現裡面提供了setButton(...)這樣的方法, 

/**
     * Set a listener to be invoked when the positive button of the dialog is pressed.
     * 
     * @param whichButton Which button to set the listener on, can be one of
     *            {@link DialogInterface#BUTTON_POSITIVE},
     *            {@link DialogInterface#BUTTON_NEGATIVE}, or
     *            {@link DialogInterface#BUTTON_NEUTRAL}
     * @param text The text to display in positive button.
     * @param listener The {@link DialogInterface.OnClickListener} to use.
     */
    public void setButton(int whichButton, CharSequence text, OnClickListener listener) {
        mAlert.setButton(whichButton, text, listener, null);
    }
貌似只需要傳入三個引數 控制元件ID, 控制元件顯示名稱, 和回撥的介面方法就行了.

然後, 用程式碼去見證"奇蹟的一刻"吧. 哈哈哈~

final Calendar c = Calendar.getInstance();
        mYear = c.get(Calendar.YEAR);
        mMonth = c.get(Calendar.MONTH);
        mDay = c.get(Calendar.DAY_OF_MONTH);

        final DatePickerDialog dpd = new DatePickerDialog(context,
                new DatePickerDialog.OnDateSetListener() {
                    @Override
                    public void onDateSet(DatePicker view, int year, int month,
                                          int day) {
                    }
                }, mYear, mMonth, mDay);

        Button btnConfirm = new Button(getActivity());
        Button btnCancel = new Button(getActivity());


        dpd.setButton(btnConfirm.getId(), "btnConfirm", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(getActivity(), "點選\nbtnConfirm", Toast.LENGTH_SHORT).show();
            }
        });

        dpd.setButton(btnCancel.getId(), "btnCancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(getActivity(), "點選\nbtnCancel", Toast.LENGTH_SHORT).show();
            }
        });
        dpd.setTitle("選擇日期");
        dpd.show();
信心滿滿的執行, 看到彈出框, 還是隻有一個按鈕, 按鈕顯示的名稱為 "btnCancel" .  納尼 什麼鬼? 這真的是我想要的嗎?

這裡我們先來看看 setButton() 是如何新增按鈕和回撥事件的.

點進去 setButton() 方法, 發現呼叫的是AlertController 的 setButton() 方法:

/** 
     * Sets a click listener or a message to be sent when the button is clicked. 
     * You only need to pass one of {@code listener} or {@code msg}. 
     *  
     * @param whichButton Which button, can be one of 
     *            {@link DialogInterface#BUTTON_POSITIVE}, 
     *            {@link DialogInterface#BUTTON_NEGATIVE}, or 
     *            {@link DialogInterface#BUTTON_NEUTRAL} 
     * @param text The text to display in positive button. 
     * @param listener The {@link DialogInterface.OnClickListener} to use. 
     * @param msg The {@link Message} to be sent when clicked. 
     */  
    public void setButton(int whichButton, CharSequence text,  
            DialogInterface.OnClickListener listener, Message msg) {  
  
        if (msg == null && listener != null) {  
            msg = mHandler.obtainMessage(whichButton, listener);  
        }  
          
        switch (whichButton) {  
  
            case DialogInterface.BUTTON_POSITIVE:  
                mButtonPositiveText = text;  
                mButtonPositiveMessage = msg;  
                break;  
                  
            case DialogInterface.BUTTON_NEGATIVE:  
                mButtonNegativeText = text;  
                mButtonNegativeMessage = msg;  
                break;  
                  
            case DialogInterface.BUTTON_NEUTRAL:  
                mButtonNeutralText = text;  
                mButtonNeutralMessage = msg;  
                break;  
                  
            default:  
                throw new IllegalArgumentException("Button does not exist");  
        }  
    }
看到這個方法的四個引數, 大家都會表示很熟悉吧. 但是這和上面新增 btnConfirm 和 btnCancel 兩個按鈕只顯示最後一個, 有什麼關係?

我們回去看Button的構造方法, 從Button -> ... -> 追溯到View類的原始碼, 就會發現, new出來的控制元件, 如果沒有設定控制元件ID, 是預設賦予值為 -1 的控制元件ID:

/**
     * The view's identifier.
     * {@hide}
     *
     * @see #setId(int)
     * @see #getId()
     */
    @ViewDebug.ExportedProperty(resolveId = true)
    int mID = NO_ID;

/**
     * Used to mark a View that has no ID.
     */
    public static final int NO_ID = -1;
這個mID 就是控制元件的ID值.

再次看上面AlertController 的 setButton() 方法中用於判斷的幾個引數的值

/**
     * The identifier for the positive button.
     */
    public static final int BUTTON_POSITIVE = -1;

    /**
     * The identifier for the negative button. 
     */
    public static final int BUTTON_NEGATIVE = -2;

    /**
     * The identifier for the neutral button. 
     */
    public static final int BUTTON_NEUTRAL = -3;
BUTTON_POSITIVE 的值是 -1 . 而我們新建的兩個按鈕控制元件的預設ID也都是 -1.  所以程式碼中即使兩次呼叫 setButton() 方法去給DatePickerDialog 新增兩個按鈕, 卻只顯示後者  btnCancel 按鈕.  SOGA ~

來到這裡, 問題好像還沒解決, 卻又好像想到怎麼解決了.

上面不是提供了三個引數嗎? BUTTON_POSITIVE, BUTTON_NEGATIVE, BUTTON_NEUTRAL . 繼續試試看!

final Calendar c = Calendar.getInstance();
        mYear = c.get(Calendar.YEAR);
        mMonth = c.get(Calendar.MONTH);
        mDay = c.get(Calendar.DAY_OF_MONTH);

        final DatePickerDialog dpd = new DatePickerDialog(context,
                new DatePickerDialog.OnDateSetListener() {
                    @Override
                    public void onDateSet(DatePicker view, int year, int month,
                                          int day) {
                    }
                }, mYear, mMonth, mDay);


        dpd.setButton(DatePickerDialog.BUTTON_POSITIVE, "取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });

        dpd.setButton(DatePickerDialog.BUTTON_NEGATIVE, "確定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {

            }
        });

        dpd.setButton(DatePickerDialog.BUTTON_NEUTRAL, "中間按鈕", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });

        dpd.setTitle(R.string.str_select_dob);
        dpd.setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                dialog.dismiss();
            }
        });

        dpd.setCanceledOnTouchOutside(true);
        dpd.show();

顯示效果:


POSITIVE 對應預設的確定鍵, NEGATIVE 是取消. 但是DatePickerDialog 對這個兩個按鈕的位置是將取消鍵放在左邊, 確定鍵放在右邊.→_→ 為了好看點, 我將它們顯示的名稱互換了, 反正它們各自有對應回撥的介面, 所以毫無影響.  如果將新增中間按鈕 setButton(DatePickerDialog.BUTTON_NEUTRAL, ...) 的方法去掉,  順便把POSITIVE  的傳入引數改為"確定", NEGATIVE 改為 "取消",  就會看到我們預期的介面: (果然, 確定鍵放在右邊是有點奇葩吧 == )


到了這裡, 顯示的問題, 我們搞掂了. 就剩下如何獲取日期, 在哪裡觸發完成選擇日期的事件.  你這麼聰明, 肯定猜到啦~  沒錯, 就這樣:

dpd.setButton(DatePickerDialog.BUTTON_NEGATIVE, "確定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                int year = dpd.getDatePicker().getYear();
                int month = dpd.getDatePicker().getMonth();
                int day = dpd.getDatePicker().getDayOfMonth();

                // 獲取日期後,進行處理...
                dialog.dismiss();
            }
        });

OK, onDateSet() 方法裡面甚至可以不去寫一行程式碼. 並且只在需要的時候才去獲取日期.  這樣我們就完成了, 利用DatePickerDialog 自身的方法不但實現了日期選擇, 還解決了自動回撥 onDateSet() 方法的問題.