轉自http://blog.csdn.net/huangyabin001/article/details/30053835
詳解一:
Android提供alert、prompt、pick-list,單選、多選,progress、time-picker和date-picker對話框,並提供自定義的dialog。在Android 3.0後,dialog基於fragment,並對之前版本提供兼容支持庫,也就是說對於開發者而言,dialog是基於DialogFragment的,但此時需要在應用中加入相關的兼容庫。
和Windows或者網頁JS的Dialog不同,Android的dialog是異步的,而不是同步的。對於同步的dialog,顯示dialog後,下一行代碼會等到dialog結束,即下一行代碼可以知道dialog的輸入以及用戶點擊的button。而對於異步的dialog,dialog顯示後,下一行代碼繼續執行,而不是等dialog消失,通過callback來處理dialog的事件。異步的dialog也意味着應用的代碼也可以關閉dialog。
我們的小例子通過菜單觸發分別觸發告警框和自定義佈局提示框,提示框中有三個button,其中一個Help按鈕可以再觸發一個幫助內容的對話框。
創建dialog fragment
對話框基於DialogFrame,告警框AlterDialogFrament類如下,如何通過newInstance()創建實例在Fragment的學習中已經學過,不再詳述。newInstance()有兩個參數,一是告警框的標題,一是告警框的內容。
public class AlterDialogFragment extends DialogFragment{
/*【步驟1】:通過newInstance()創建實例,並返回,這裏的處理和系統從save狀態中re-create相同。
* 1、通過缺省構造函數創建對象
* 2、將傳遞的信息設置爲fragment的參數
* 3、返回對象
* */
public static AlterDialogFragment newInstance(String title,String message){
AlterDialogFragment adf = new AlterDialogFragment();
Bundle bundle = new Bundle();
bundle.putString("alert-title", title);
bundle.putString("alert-message", message);
adf.setArguments(bundle);
return adf;
}
...... 略,見後文......
}
自定義佈局提示框PromptDialogFragment同樣是DialogFragment的繼承。類似的,代碼如下:
public class PromptDialogFragment extends DialogFragment{
public static PromptDialogFragment newInstance(String prompt){
PromptDialogFragment pdf = new PromptDialogFragment();
Bundle b = new Bundle();
b.putString("prompt-message", prompt);
pdf.setArguments(b);
return pdf;
}
......略,見後文......
}
Activity顯示對話框
在MyActivity中,通過optionsMenu來分別觸發告警框和提示框的顯示,代碼如下:
public class MainActivity extends Activity{
//設置告警框、提示框和幫助框的dialog fragment的tag。
public final static String ALERT_DIALOG_TAG = "ALERT_DIALOG_TAG";
public final static String PROMPT_DIALOG_TAG = "PROMPT_DIALOG_TAG";
public final static String HELP_DIALOG_TAG = "HELP_DIALOG_TAG";
…... 略 : 設置UI和創建OptionsMenu ......
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()){
case R.id.alter_dialog:
alterDialogTestCase();
break;
case R.id.prompt_dialog:
promptDialogTestCase();
default:
break;
}
return false;
}
/* 觸發告警框:通過dialogFragment.show()觸發
* 我們注意對於FragmentTransaction ft,代碼中沒有執行ft.commit()。查看DialogFragment的show方法的源代碼,如下
public void show(FragmentManager manager, String tag){
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit();
}
public int show(FragmentTransaction transaction, String tag) {
mDismissed = false;
mShownByMe = true;
transaction.add(this, tag);
mViewDestroyed = false;
mBackStackId = transaction.commit();
return mBackStackId;
}
* 這裏面的操作含有ft.add()和ft.commit(),故不需要在代碼中重複commit,否則會異常。 add表示加入到activity,這裏沒有填容器的ID,即contianerViewID爲0,表示不加載在具體容器內,對於dialog,container爲null。
* 這本例中也可以通過adf.show(getFragmentManager(), ALERT_DIALOG_TAG)來實現。對於將fragment transaction作爲參數的方式,在調用show()之前,可通過fragment transaction進行控制,如加入到back stack中,這將在按提示框的Help按鈕彈幫助框中進行演示。在show()中,同時設置了fragment的tag,可用於索引,可在fragment中可以通過getTag()獲取。 */
private void alterDialogTestCase(){
AlterDialogFragment adf = AlterDialogFragment.newInstance("Alert", "This is the Alter Message for test!");
FragmentTransaction ft = getFragmentManager().beginTransaction();
adf.show(ft, ALERT_DIALOG_TAG);
}
/* 彈出提示框 */
private void promptDialogTestCase(){
PromptDialogFragment pdf = PromptDialogFragment.newInstance("This is a Prompt Dialog!");
FragmentTransaction ft = getFragmentManager().beginTransaction();
pdf.show(ft, PROMPT_DIALOG_TAG);
}
/* 此爲用戶按對話框按鍵時被調用的方法,通過Toast顯示相關信息。*/
public void onDialogDone(String tag, boolean cancelled, CharSequence message) {
String s = tag + " responds with: " + message;
if(cancelled)
s = tag + " was cancelled by the user";
//Toast是沒有button的信息框,在一定時間後消失,很適合用於debug。
Toast.makeText(this, s, Toast.LENGTH_LONG).show();
}
}
通過fragment實現dialog的好處是:activity配置改變(例如轉向)進行重構的情況下,fragment管理器能夠自動重夠,恢復原來的狀態,無需人工干預。
詳解二:DialogFragment的實例newInstance()已經在上一次學習筆記中實現。我們創建dialog的UI,可以通過重寫DialogFragment的兩個函數當中的一個來實現,這兩個函數是onCreateView()和onCreateDialog(),前者返回view,後者返回dialog,如同通過AlertDialog.Builder構造一樣。
重寫onCreateView()
重寫onCreateView()是fragment的傳統方式,適合自定義的對話框,本例適合用於提示框,如下圖所示。通過按菜單彈出提示框,提示框由一個TextView,一個EditText和三個Button組成UI。按不同的按鈕觸發不同的處理。小例子自作範例,按Save和Dismiss按鈕,都會調用Activity的onDialogDone()函數,根據用戶的實際操作,顯示不同的信息。按Help按鈕,則彈出一個幫助框。再彈框在稍後學習筆記中實現。
通過onCreateView()設置UI和按鍵反饋
利用Fragment的onCreateView()來實現對話框的UI和Fragment學習中沒有差別,在本例中,我們增加了按鈕點擊的觸發,代碼如下:
public class PromptDialogFragment extends DialogFragment implements OnClickListener{
public static PromptDialogFragment newInstance(String prompt){
...略...
}
@Override //通過重寫Fragment的onCreateView()實現dialog的UI
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//1、通過inflate,根據layout XML定義,創建view
View v = inflater.inflate(R.layout.prompt_dialog, container,false);
TextView tv = (TextView)v.findViewById(R.id.prompt_message);
tv.setText(getPrompt());
//2、註冊三個button的按鍵監聽listener
Button dismissBtn = (Button)v.findViewById(R.id.button_dismiss);
dismissBtn.setOnClickListener(this);
Button saveBtn = (Button)v.findViewById(R.id.button_save);
saveBtn.setOnClickListener(this);
Button helpBtn = (Button)v.findViewById(R.id.button_help);
helpBtn.setOnClickListener(this);
return v;
}
private String getPrompt(){
Bundle b = getArguments();
return b.getString("prompt-message");
}
@Override //在onCreate中設置對話框的風格、屬性等
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//如果setCancelable()中參數爲true,若點擊dialog覆蓋不到的activity的空白或者按返回鍵,則進行cancel,狀態檢測依次onCancel()和onDismiss()。如參數爲false,則按空白處或返回鍵無反應。缺省爲true
setCancelable(true);
//可以設置dialog的顯示風格,如style爲STYLE_NO_TITLE,將被顯示title。遺憾的是,我沒有在DialogFragment中找到設置title內容的方法。theme爲0,表示由系統選擇合適的theme。
int style = DialogFragment.STYLE_NO_NORMAL, theme = 0;
setStyle(style,theme);
}
@Override //僅用於狀態跟蹤
public void onCancel(DialogInterface dialog) {
showInfo("onCancel() is called");
super.onCancel(dialog);
}
@Override //僅用戶狀態跟蹤
public void onDismiss(DialogInterface dialog) {
showInfo("onDismiss() is called");
super.onDismiss(dialog);
}
@Override //Button按鍵觸發的回調函數
public void onClick(View v) {
MainActivity act = (MainActivity)getActivity();
switch(v.getId()){
case R.id.button_dismiss:
act.onDialogDone(getTag(), true, null);
//調用activity的onDialogDone(),通過Toast顯示相關信息
dismiss(); //關閉對話框,並觸發onDismiss()回調函數。
break;
case R.id.button_help:
… 略:以後實現 …
break;
case R.id.button_save:
TextView tv = (TextView)getView().findViewById(R.id.input_text);
act.onDialogDone(getTag(), false, "[save]" + tv.getText());
//調用activity的onDialogDone(),通過Toast顯示相關信息
dismiss(); //關閉對話框,並觸發onDismiss()回調函數
break;
default:
break;
}
}
private void showInfo(String s){
Log.d("PromptDialogFragment",s);
}
}
信息保存
如果用戶在輸入框中填入text,然後進行屏幕的橫屏和豎屏切換,這涉及到填入內容的保存,可以通過onSaveInstanceState(),將之保存到fragment的Bundle savedInstanceState中,並在onCreateView()中將之恢復。但是在Android 4.2版本的測試中,系統已經能夠自動保存和恢復,無需加入代碼。當然,安全地我們仍建議進行以下處理。
public class PromptDialogFragment extends DialogFragment implements OnClickListener{
private EditText et = null;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
......
et = (EditText)v.findViewById(R.id.input_text);
if(savedInstanceState != null){
CharSequence text = savedInstanceState.getCharSequence("input");
et.setText(text == null ? "" : text);
}
......
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putCharSequence("input", et.getText());
super.onSaveInstanceState(outState);
}
}
重寫onCreateDialog()
對於簡單的對話框,可以通過AlterDialog.Builder直接創建對話框的UI,本例用於告警框,如下圖。AlertDialog.Builder在Android 3.0版本之前的創建對話框方式,在之後的版本中,可用在DialogFragment中,適用於創建簡單對話框。
代碼如下。雖然都是OnClickListener接口,但提示框的是View.OnClickListener,這裏是DialogInterface.OnClickListener。
public class AlterDialogFragment extends DialogFragment implements DialogInterface.OnClickListener{
/*【步驟1】:通過newInstance()創建實例並返回* */
public static AlterDialogFragment newInstance(String title,String message){
… 略 …
}
private String getTitle(){
return getArguments().getString("alert-title");
}
private String getMessage(){
return getArguments().getString("alert-message");
}
/* 【步驟2】創建view可以通過兩個途徑,一是fragment中的onCreateView(),二是DialogFragment中的onCreateDialog()。
* 前者適合對自定義的layout進行設置,具有更大的靈活性
* 而後者適合對簡單dialog進行處理,可以利用Dialog.Builder直接返回Dialog對象
* 從生命週期的順序而言,先執行onCreateDialog(),後執行oonCreateView(),我們不應同時使用兩者。
* */
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder b = new AlertDialog.Builder(getActivity())
.setTitle(getTitle())
.setMessage(getMessage())
.setPositiveButton("OK", this)
//設置回調函數
.setNegativeButton("Cancel",this); //設置回調函數
return b.create();
}
@Override //按鍵觸發的回調函數
public void onClick(DialogInterface dialog, int which) {
boolean isCancel = false;
if(which == AlertDialog.BUTTON_NEGATIVE){
//判斷用戶所按何鍵
isCancel = true;
}
MyActivity act = (MyActivity) getActivity();
act.onDialogDone(getTag(), isCancel, "CLick OK, Alert dismissed");
}
}
提示框的按鈕Help,將觸發彈出新的幫助提示框。
幫助提示框的實現
幫助提示框的實現很簡單,利用重寫onCreateView( )的方式,點擊按鈕是執行dismiss(),關閉對話框即可。
代碼不在此重複。dialog fragment的關閉有兩種方式,一種是在dialog fragment中直接執行dismiss(),我們來看看DialogFragment的源代碼片段:
- /**
- * Dismiss the fragment and its dialog. If the fragment was added to the
- * back stack, all back stack state up to and including this entry will
- * be popped. Otherwise, a new transaction will be committed to remove
- * the fragment.
- */
- public void dismiss() {
- dismissInternal(false);
- }
- void dismissInternal(boolean allowStateLoss) {
- if (mDismissed) {
- return;
- }
- mDismissed = true;
- mShownByMe = false;
- if (mDialog != null) {
- mDialog.dismiss();
- mDialog = null;
- }
- mViewDestroyed = true;
- if (mBackStackId >= 0) {
- getFragmentManager().popBackStack(mBackStackId,
- FragmentManager.POP_BACK_STACK_INCLUSIVE);
- mBackStackId = -1;
- } else {
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.remove(this);
- if (allowStateLoss) {
- ft.commitAllowingStateLoss();
- } else {
- ft.commit();
- }
- }
- }
如果back stack堆棧有該dialog,將其pop出來,否則ft.remove(this); ft.commit();。估計pop的操作也包含ft.remove()和ft.commit()。調用dismiss()會觸發onDismiss()回調函數。跟蹤狀態,如下。
實現再彈框
在PromptDialogFragment中實現彈框的相關代碼如下。這裏採用另一種關閉dialog的方法,通過fragment transaction進行控制。
public void onClick(View v) {
... ...
switch(v.getId()){
case R.id.button_help:
FragmentTransaction ft = getFragmentManager().beginTransaction();
/* 如果不執行remove(),prompt dailog在下層,跟蹤狀態,系統即不會進入onDismiss()狀態。主要考慮美觀的問題,如果下面prompt對話框大於幫助框,視覺效果不好。下面左圖爲執行了remove()的效果,右圖爲不執行remove()的效果。
對於Dialog,container爲0或者null。 */
ft.remove(this);
/* 將當前的PromptDialogFragment加入到回退堆棧,當用戶按返回鍵,或者通過按幫助框的Close按鈕dismiss幫助框是,重新顯示提示框。
對於back stack的處理,系統具有一定的智能。例如:執行兩次addToStackStack(),實際不會重複壓棧。 有例如:註釋掉remove()語句,即提示框不消失,而是在幫助框的下面,如右圖,由於提示框存在,我們並不需要將提示框鍵入到back stack,但是在實驗中發現是否有addToBackStack()都不會結果有影響,系統能夠分析到對象存在,不需要壓棧。沒有去查源代碼,猜測通過mBackStackId比對來進行智能處理。*/
ft.addToBackStack(null);
HelpDialogFragment hdf = HelpDialogFragment.newInstance(R.string.help_message);
/* 對fragment的處理是通過fragment transaction,與在activity彈框一樣,通過show()方式實現。 在此之前,我們已經通過transaction將當前的fragment加入到back stack中。*/
hdf.show(ft,MainActivity.HELP_DIALOG_TAG);
break;
... ...
}
}
通過remove()和addToBackStack()使得fragment從UI中消失,當仍可以通過fragment管理器和回退堆棧獲取。
再談fragment管理器
通過fragment管理器或者fragment transaction,我們可以對dialog fragment進行具體地控制。show()就是在管理器中加入fragment,dismiss()就是從管理器中去掉fragment。我們不能先進行add(),然後在進行show(),因此一個fragment對象只能加入管理器一次。如果fragment被dismiss(),將從管理器中刪除,我們不能再通過管理器獲取該fragment的信息。因此,如果我們想保留被dismiss的dialog的一些狀態或信息,需要在dialog外進行保存,例如利用activity。總結:
編程思想:封裝接口
在小例子中,fragment會調用activity的onDialogDone()來顯示Toast等信息。在真正項目中,fragment的編寫並不需要了解activity的各類方法,好的編程風格是將fragment所涉及的方法以接口的方式封裝起來,如下:
public interface OnMyDialogClickListener {
public void onDialogDone(String tag, boolean cancelled, CharSequence message);
}
在activity中,增加接口的實現,如下:
public class MainActivity extends Activity implements OnMyDialogClickListener{
......
public void onDialogDone(String tag, boolean cancelled, CharSequence message) {
String s = tag + " responds with: " + message;
if(cancelled)
s = tag + " was cancelled by the user";
Toast.makeText(this, s, Toast.LENGTH_LONG).show();
showInfo(s);
}
}
相應地,在fragment中,對該方法的調用,可以寫爲:
OnMyDialogClickListener act = (OnMyDialogClickListener)getActivity();
act.onDialogDone(……);
對於一些大型項目,如果我們無法確定activity是否真的實現了接口,可以在fragment的早期,即剛關聯activity的階段進行檢測,如下:
@Override
public void onAttach(Activity activity) {
//onAttach()是合適的早期階段進行檢查MyActivity是否真的實現了接口。
//採用接口的方式,dialog無需詳細瞭解MyActivity,只需瞭解其所需的接口函數,這是真正項目中應採用的方式。
try{
OnMyDialogClickListener act = (OnMyDialogClickListener)activity;
}catch(ClassCastException e){
…... activity並不真正支持接口的異常處理......
}
super.onAttach(activity);
}
fragment和activity以其他fragment之間的通信
小例子演示了通過getActivity()獲取接口對象或者直接獲取activity的對象,實現兩者之間的通信。此外fragment也可以通過fragment管理器,通過tag,獲取其他fragment實例,從而進行fragment之間的通信。當然從編程思想的角度看,fragment之間的過多進行交叉調用,不利於程序的管控。