DialogFragment詳解

轉自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的源代碼片段:

  1. /** 
  2.  * Dismiss the fragment and its dialog.  If the fragment was added to the 
  3.  * back stack, all back stack state up to and including this entry will 
  4.  * be popped.  Otherwise, a new transaction will be committed to remove 
  5.  * the fragment. 
  6.  */  
  7. public void dismiss() {  
  8.     dismissInternal(false);  
  9. }  
  10.   
  11. void dismissInternal(boolean allowStateLoss) {  
  12.     if (mDismissed) {  
  13.         return;  
  14.     }  
  15.     mDismissed = true;  
  16.     mShownByMe = false;  
  17.     if (mDialog != null) {  
  18.         mDialog.dismiss();  
  19.         mDialog = null;  
  20.     }  
  21.     mViewDestroyed = true;  
  22.     if (mBackStackId >= 0) {  
  23.         getFragmentManager().popBackStack(mBackStackId,  
  24.                 FragmentManager.POP_BACK_STACK_INCLUSIVE);  
  25.         mBackStackId = -1;  
  26.     } else {  
  27.         FragmentTransaction ft = getFragmentManager().beginTransaction();  
  28.         ft.remove(this);   
  29.         if (allowStateLoss) {  
  30.             ft.commitAllowingStateLoss();  
  31.         } else {  
  32.             ft.commit();  
  33.         }  
  34.     }  
  35. }  

如果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之間的過多進行交叉調用,不利於程序的管控。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章