眾所周知,AlertDialog類對于顯示對話。關(guān)于AlertDialog的基本用法在這里就不詳細介紹了,網(wǎng)上有很多,讀者可以自己搜索。那么本文要介紹的是如何隨心所欲地控制AlertDialog。
現(xiàn)在我們來看看第一個需求:如果某個應(yīng)用需要彈出一個對話框。當(dāng)單擊“確定“按鈕時完成某些工作,如果這些工作失敗,對話框不能關(guān)閉。而當(dāng)成功完成工作后,則關(guān)閉對話框。當(dāng)然,無論何程度情況,單擊“取消”按鈕都會關(guān)閉對話框。
這個需求并不復(fù)雜,也并不過分(雖然我們可以自己弄個Activity來完成這個工作,也可在View上自己放按鈕,但這顯示有些大炮打蚊子了,如果對話框上只有一行文本,費這么多勁太不值了)。但使用過AlertDialog的讀者都知道,無論單擊的哪個按鈕,無論按鈕單擊事件的執(zhí)行情況如何,對話框是肯定要關(guān)閉的。也就是說,用戶無法控制對話框的關(guān)閉動作。實際上,關(guān)閉對話框的動作已經(jīng)在Android" title="Android">Android SDK寫死了,并且未給使用者留有任何接口。但我的座右銘是“宇宙中沒有什么是不能控制的”。
既然要控制對放框的關(guān)閉行為,首先就得分析是哪些類、哪些代碼使這個對話框關(guān)閉的。進入AlertDialog類的源代碼。在AlertDialog中只定義了一個變量:mAlert。這個變量是AlertController類型。AlertController類是Android的內(nèi)部類,在com.android.internal.app包中,無法通過普通的方式訪問。也無法在Eclipse中通過按Ctrl鍵跟蹤進源代碼。但可以直接在Android源代碼中找到AlertController.java。我們再回到AlertDialog類中。AlertDialog類實際上只是一個架子。象設(shè)置按鈕、設(shè)置標(biāo)題等工作都是由AlertController類完成的。因此,AlertController類才是關(guān)鍵。
找到AlertController.java文件。打開后不要感到頭暈哦,這個文件中的代碼是很多地。不過這么多代碼對本文的主題也沒什么用處。下面就找一下控制按鈕的代碼。
在AlertController類的開頭就會看到如下的代碼:
View.OnClickListener mButtonHandler = new View.OnClickListener() {
public void onClick(View v) {
Message m = null;
if (v == mButtonPositive && mButtonPositiveMessage != null) {
m = Message.obtain(mButtonPositiveMessage);
} else if (v == mButtonNegative && mButtonNegativeMessage != null) {
m = Message.obtain(mButtonNegativeMessage);
} else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
m = Message.obtain(mButtonNeutralMessage);
}
if (m != null) {
m.sendToTarget();
}
// Post a message so we dismiss after the above handlers are executed
mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
.sendToTarget();
}
};
從這段代碼中可以猜出來,前幾行代碼用來觸發(fā)對話框中的三個按鈕(Positive、Negative和Neutral)的單擊事件,而最后的代碼則用來關(guān)閉對話框(因為我們發(fā)現(xiàn)了MSG_DISMISS_DIALOG、猜出來的)。
mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
.sendToTarget();
上面的代碼并不是直接來關(guān)閉對話框的,而是通過一個Handler來處理,代碼如下:
private static final class ButtonHandler extends Handler {
// Button clicks have Message.what as the BUTTON{1,2,3} constant
private static final int MSG_DISMISS_DIALOG = 1;
private WeakReference《DialogInterface》 mDialog;
public ButtonHandler(DialogInterface dialog) {
mDialog = new WeakReference《DialogInterface》(dialog);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DialogInterface.BUTTON_POSITIVE:
case DialogInterface.BUTTON_NEGATIVE:
case DialogInterface.BUTTON_NEUTRAL:
?。ǎ―ialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
break;
case MSG_DISMISS_DIALOG:
?。ǎ―ialogInterface) msg.obj).dismiss();
}
}
}
從上面代碼的最后可以找到
?。ǎ―ialogInterface) msg.obj).dismiss();。現(xiàn)在看了這么多源代碼,我們來總結(jié)一下對話框按鈕單擊事件的處理過程。在AlertController處理對話框按鈕時會為每一個按鈕添加一個onclick事件。而這個事件類的對象實例就是上面的mButtonHandler。在這個單擊事件中首先會通過發(fā)送消息的方式調(diào)用為按鈕設(shè)置的單擊事件(也就是通過setPositiveButton等方法的第二個參數(shù)設(shè)置的單擊事件),在觸發(fā)完按鈕的單擊事件后,會通過發(fā)送消息的方式調(diào)用dismiss方法來關(guān)閉對話框。而在AlertController類中定義了一個全局的mHandler變量。在AlertController類中通過ButtonHandler類來對象來為mHandler賦值。因此,我們只要使用我們自己Handler對象替換ButtonHandler就可以阻止調(diào)用dismiss方法來關(guān)閉對話框。下面先在自己的程序中建立一個新的ButtonHandler類(也可叫其他的名)。
class ButtonHandler extends Handler
{
private WeakReference《DialogInterface》 mDialog;
public ButtonHandler(DialogInterface dialog)
{
mDialog = new WeakReference《DialogInterface》(dialog);
}
@Override
public
void handleMessage(Message msg)
{
switch (msg.what)
{
case DialogInterface.BUTTON_POSITIVE:
case DialogInterface.BUTTON_NEGATIVE:
case DialogInterface.BUTTON_NEUTRAL:
?。ǎ―ialogInterface.OnClickListener) msg.obj).onClick(mDialog
.get(), msg.what);
break;
}
}
}
我們可以看到,上面的類和AlertController中的ButtonHandler類很像,只是支掉了switch語句的最后一個case子句(用于調(diào)用dismiss方法)和相關(guān)的代碼。
眾所周知,AlertDialog類對于顯示對話。關(guān)于AlertDialog的基本用法在這里就不詳細介紹了,網(wǎng)上有很多,讀者可以自己搜索。那么本文要介紹的是如何隨心所欲地控制AlertDialog。
現(xiàn)在我們來看看第一個需求:如果某個應(yīng)用需要彈出一個對話框。當(dāng)單擊“確定“按鈕時完成某些工作,如果這些工作失敗,對話框不能關(guān)閉。而當(dāng)成功完成工作后,則關(guān)閉對話框。當(dāng)然,無論何程度情況,單擊“取消”按鈕都會關(guān)閉對話框。
這個需求并不復(fù)雜,也并不過分(雖然我們可以自己弄個Activity來完成這個工作,也可在View上自己放按鈕,但這顯示有些大炮打蚊子了,如果對話框上只有一行文本,費這么多勁太不值了)。但使用過AlertDialog的讀者都知道,無論單擊的哪個按鈕,無論按鈕單擊事件的執(zhí)行情況如何,對話框是肯定要關(guān)閉的。也就是說,用戶無法控制對話框的關(guān)閉動作。實際上,關(guān)閉對話框的動作已經(jīng)在Android SDK寫死了,并且未給使用者留有任何接口。但我的座右銘是“宇宙中沒有什么是不能控制的”。
既然要控制對放框的關(guān)閉行為,首先就得分析是哪些類、哪些代碼使這個對話框關(guān)閉的。進入AlertDialog類的源代碼。在AlertDialog中只定義了一個變量:mAlert。這個變量是AlertController類型。AlertController類是Android的內(nèi)部類,在com.android.internal.app包中,無法通過普通的方式訪問。也無法在Eclipse中通過按Ctrl鍵跟蹤進源代碼。但可以直接在Android源代碼中找到AlertController.java。我們再回到AlertDialog類中。AlertDialog類實際上只是一個架子。象設(shè)置按鈕、設(shè)置標(biāo)題等工作都是由AlertController類完成的。因此,AlertController類才是關(guān)鍵。
找到AlertController.java文件。打開后不要感到頭暈哦,這個文件中的代碼是很多地。不過這么多代碼對本文的主題也沒什么用處。下面就找一下控制按鈕的代碼。
在AlertController類的開頭就會看到如下的代碼:
View.OnClickListener mButtonHandler = new View.OnClickListener() {
public void onClick(View v) {
Message m = null;
if (v == mButtonPositive && mButtonPositiveMessage != null) {
m = Message.obtain(mButtonPositiveMessage);
} else if (v == mButtonNegative && mButtonNegativeMessage != null) {
m = Message.obtain(mButtonNegativeMessage);
} else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
m = Message.obtain(mButtonNeutralMessage);
}
if (m != null) {
m.sendToTarget();
}
// Post a message so we dismiss after the above handlers are executed
mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
.sendToTarget();
}
};
從這段代碼中可以猜出來,前幾行代碼用來觸發(fā)對話框中的三個按鈕(Positive、Negative和Neutral)的單擊事件,而最后的代碼則用來關(guān)閉對話框(因為我們發(fā)現(xiàn)了MSG_DISMISS_DIALOG、猜出來的)。
mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
.sendToTarget();
上面的代碼并不是直接來關(guān)閉對話框的,而是通過一個Handler來處理,代碼如下:
private static final class ButtonHandler extends Handler {
// Button clicks have Message.what as the BUTTON{1,2,3} constant
private static final int MSG_DISMISS_DIALOG = 1;
private WeakReference《DialogInterface》 mDialog;
public ButtonHandler(DialogInterface dialog) {
mDialog = new WeakReference《DialogInterface》(dialog);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DialogInterface.BUTTON_POSITIVE:
case DialogInterface.BUTTON_NEGATIVE:
case DialogInterface.BUTTON_NEUTRAL:
?。ǎ―ialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
break;
case MSG_DISMISS_DIALOG:
?。ǎ―ialogInterface) msg.obj).dismiss();
}
}
}
從上面代碼的最后可以找到
?。ǎ―ialogInterface) msg.obj).dismiss();。現(xiàn)在看了這么多源代碼,我們來總結(jié)一下對話框按鈕單擊事件的處理過程。在AlertController處理對話框按鈕時會為每一個按鈕添加一個onclick事件。而這個事件類的對象實例就是上面的mButtonHandler。在這個單擊事件中首先會通過發(fā)送消息的方式調(diào)用為按鈕設(shè)置的單擊事件(也就是通過setPositiveButton等方法的第二個參數(shù)設(shè)置的單擊事件),在觸發(fā)完按鈕的單擊事件后,會通過發(fā)送消息的方式調(diào)用dismiss方法來關(guān)閉對話框。而在AlertController類中定義了一個全局的mHandler變量。在AlertController類中通過ButtonHandler類來對象來為mHandler賦值。因此,我們只要使用我們自己Handler對象替換ButtonHandler就可以阻止調(diào)用dismiss方法來關(guān)閉對話框。下面先在自己的程序中建立一個新的ButtonHandler類(也可叫其他的名)。
class ButtonHandler extends Handler
{
private WeakReference《DialogInterface》 mDialog;
public ButtonHandler(DialogInterface dialog)
{
mDialog = new WeakReference《DialogInterface》(dialog);
}
@Override
public
void handleMessage(Message msg)
{
switch (msg.what)
{
case DialogInterface.BUTTON_POSITIVE:
case DialogInterface.BUTTON_NEGATIVE:
case DialogInterface.BUTTON_NEUTRAL:
?。ǎ―ialogInterface.OnClickListener) msg.obj).onClick(mDialog
.get(), msg.what);
break;
}
}
}
我們可以看到,上面的類和AlertController中的ButtonHandler類很像,只是支掉了switch語句的最后一個case子句(用于調(diào)用dismiss方法)和相關(guān)的代碼。
下面我們就要為AlertController中的mHandler重新賦值。由于mHandler是private變量,因此,在這里需要使用Java的反射技術(shù)來為mHandler賦值。由于在AlertDialog類中的mAlert變量同樣也是private,因此,也需要使用同樣的反射技術(shù)來獲得mAlert變量。代碼如下:
先建立一個AlertDialog對象
AlertDialog alertDialog = new AlertDialog.Builder(this)
.setTitle(“abc”)
.setMessage(“content”)
.setIcon(R.drawable.icon)
.setPositiveButton( “確定”,
new OnClickListener()
{
@Override
public void onClick(DialogInterface dialog,
int which)
{
}
}).setNegativeButton(“取消”, new OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
}).create();
上面的對話框很普通,單擊哪個按鈕都會關(guān)閉對話框。下面在調(diào)用show方法之前來修改一個mHandler變量的值,OK,下面我們就來見證奇跡的時刻。
try
{
Field field = alertDialog1.getClass().getDeclaredField(“mAlert”);
field.setAccessible(true);
//
獲得mAlert變量的值
Object obj = field.get(alertDialog1);
field = obj.getClass().getDeclaredField(“mHandler”);
field.setAccessible(true);
//
修改mHandler變量的值,使用新的ButtonHandler類
field.set(obj, new ButtonHandler(alertDialog1));
}
catch (Exception e)
{
}
//
顯示對話框
alertDialog.show();
我們發(fā)現(xiàn),如果加上try
catch語句,單擊對話框中的確定按鈕不會關(guān)閉對話框(除非在代碼中調(diào)用dismiss方法),單擊取消按鈕則會關(guān)閉對話框(因為調(diào)用了dismiss方法)。如果去了try…catch代碼段,對話框又會恢復(fù)正常了。
雖然上面的代碼已經(jīng)解決了問題,但需要編寫的代碼仍然比較多,為此,我們也可采用另外一種方法來阻止關(guān)閉對話框。這種方法不需要定義任何的類。
這種方法需要用點技巧。由于系統(tǒng)通過調(diào)用dismiss來關(guān)閉對話框,那么我們可以在dismiss方法上做點文章。在系統(tǒng)調(diào)用dismiss方法時會首先判斷對話框是否已經(jīng)關(guān)閉,如果對話框已經(jīng)關(guān)閉了,就會退出dismiss方法而不再繼續(xù)關(guān)閉對話框了。因此,我們可以欺騙一下系統(tǒng),當(dāng)調(diào)用dismiss方法時我們可以讓系統(tǒng)以為對話框已經(jīng)關(guān)閉(雖然對話框還沒有關(guān)閉),這樣dismiss方法就失效了,這樣即使系統(tǒng)調(diào)用了dismiss方法也無法關(guān)閉對話框了。
下面讓我們回到AlertDialog的源代碼中,再繼續(xù)跟蹤到AlertDialog的父類Dialog的源代碼中。找到dismissDialog方法。實際上,dismiss方法是通過dismissDialog方法來關(guān)閉對話框的,dismissDialog方法的代碼如下:
private
void dismissDialog() {
if (mDecor == null) {
if (Config.LOGV) Log.v(LOG_TAG,
“[Dialog] dismiss: already dismissed, ignore”);
return;
}
if (!mShowing) {
if (Config.LOGV) Log.v(LOG_TAG,
“[Dialog] dismiss: not showing, ignore”);
return;
}
mWindowManager.removeView(mDecor);
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
該方法后面的代碼不用管它,先看if(!mShowing){…}這段代碼。這個mShowing變量就是判斷對話框是否已關(guān)閉的。因此,我們在代碼中通過設(shè)置這個變量就可以使系統(tǒng)認為對話框已經(jīng)關(guān)閉,就不再繼續(xù)關(guān)閉對話框了。由于mShowing也是private變量,因此,也需要反射技術(shù)來設(shè)置這個變量。我們可以在對話框按鈕的單擊事件中設(shè)置mShowing,代碼如下:
try
{
Field field = dialog.getClass()
.getSuperclass().getDeclaredField(
“mShowing”);
field.setAccessible(true);
//
將mShowing變量設(shè)為false,表示對話框已關(guān)閉
field.set(dialog, false);
dialog.dismiss();
}
catch (Exception e)
{
}
將上面的代碼加到哪個按鈕的單擊事件代碼中,哪個按鈕就再也無法關(guān)閉對話框了。如果要關(guān)閉對話框,只需再將mShowing設(shè)為true即可。要注意的是,在一個按鈕里設(shè)置了mShowing變量,也會影響另一個按鈕的關(guān)閉對話框功能,因此,需要在每一個按鈕的單擊事件里都設(shè)置mShowing變量的值。