zoukankan      html  css  js  c++  java
  • DialogFragment详解

    详解一:

    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之间的过多进行交叉调用,不利于程序的管控。




  • 相关阅读:
    文件I/O(二)
    linux学习之文件I/O篇(一)
    静态库和共享库
    vim-ide
    CentOS6 vsftpd 安装及优化方法
    Redmine2.5+CentOS6+Apache2
    分享一个TP5实现Create()方法的心得
    Windows证书的生成导出以及使用证书验证文件是否被修改
    如何设置程序UAC控制
    关于C#的可变长参数
  • 原文地址:https://www.cnblogs.com/bill-technology/p/4130913.html
Copyright © 2011-2022 走看看