[Android] 更好的解決 "返回鍵或取消時自動回撥DatePickerDialog的方法onDateSet()" 的問題
自從忙完工作變動的事情後好久沒寫博了, 內心愧疚啊.... 說好的堅持學習呢... TAT
好吧, 迴歸正題. 用過 Android自帶的DatePickerDialog的預設樣式是這樣的:
只有一個 "完成" 按鈕...
如果將完成選擇日期的觸發事件放在方法onDateSet(), 那麼無論是點選返回鍵或螢幕outSide的地方, 或者點選 "完成"按鈕, 都會自動取消這個日期選擇彈出框時並每次呼叫onDateSet(). 就是說, 這時, 放在onDateSet()裡面的觸發事件無論如何都會被呼叫啊, 簡直不能忍... 要是彈出框的介面除了 "完成" 按鈕, 還有 "取消" 按鈕, 並且能控制什麼時候才去呼叫完成日期選擇的觸發事件, 作為社會主義接班人的我們, 該會多開心啊~~~
那麼問題來了, 除了自定一個DatePickerDialog, 比如有種做法是繼承DatePickerDialog, 然後在onStop() 裡面去註釋裡面的super.onStop() 達到不呼叫 onDateSet() 的目的. 但自帶的DatePickerDialog究竟支不支援我們上面的需求呢? 真相只有一個:
闊以的~
我們翻開DatePickerDialog所繼承的AlertDialog類原始碼, 其實發現裡面提供了setButton(...)這樣的方法,
貌似只需要傳入三個引數 控制元件ID, 控制元件顯示名稱, 和回撥的介面方法就行了./** * 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); }
然後, 用程式碼去見證"奇蹟的一刻"吧. 哈哈哈~
信心滿滿的執行, 看到彈出框, 還是隻有一個按鈕, 按鈕顯示的名稱為 "btnCancel" . 納尼 什麼鬼? 這真的是我想要的嗎?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();
這裡我們先來看看 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() 方法的問題.