详解一:

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. void dismissInternal(boolean allowStateLoss) {
  11. if (mDismissed) {
  12. return;
  13. }
  14. mDismissed = true;
  15. mShownByMe = false;
  16. if (mDialog != null) {
  17. mDialog.dismiss();
  18. mDialog = null;
  19. }
  20. mViewDestroyed = true;
  21. if (mBackStackId >= 0) {
  22. getFragmentManager().popBackStack(mBackStackId,
  23. FragmentManager.POP_BACK_STACK_INCLUSIVE);
  24. mBackStackId = -1;
  25. } else {
  26. FragmentTransaction ft = getFragmentManager().beginTransaction();
  27. ft.remove(this);
  28. if (allowStateLoss) {
  29. ft.commitAllowingStateLoss();
  30. } else {
  31. ft.commit();
  32. }
  33. }
  34. }

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

DialogFragment详解的更多相关文章

  1. Android学习笔记-Dialog详解

    1.对话框的使用 1.1AlertDialog的显示 简单对话框以及监听的设置:重点掌握三个按钮(也就是三上单词): PositiveButton(确认按钮);NeutralButton(忽略按钮) ...

  2. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

  3. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  4. EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

    前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...

  5. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

  6. Android Notification 详解(一)——基本操作

    Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...

  7. Android Notification 详解——基本操作

    Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...

  8. Git初探--笔记整理和Git命令详解

    几个重要的概念 首先先明确几个概念: WorkPlace : 工作区 Index: 暂存区 Repository: 本地仓库/版本库 Remote: 远程仓库 当在Remote(如Github)上面c ...

  9. Drawable实战解析:Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)

    Android XML shape 标签使用详解   一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 circle 作为一个 View 的背景. ...

随机推荐

  1. 【Coursera】Third Week(1)

    The Early World-Wide-Web 关于CERN 欧洲核子研究组织,除了它为世界物理学所作出的卓越贡献,它还是世界上第一个网站,第一个网络服务器,第一个浏览器的诞生地. Robert C ...

  2. Java I/O学习 文件读写工具

    import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import jav ...

  3. MyBatis3-基于注解的示例

    在基于注解的示例中,可以简化编写XML的过程,全部采用注解方式进行编写,并在注解上写SQL语句,语句和XML的语句保持一致,并且可以省略掉XML文件不用引入的好处.但还有一点,基于注解的方式还没有百分 ...

  4. 20170728xlVba还是这个混蛋

    Public Sub Main22() If Now() >= #1/1/2018# Then Exit Sub Dim strText As String Dim Reg As Object, ...

  5. 20170716xlVba销售明细转销售单据

    Sub CreateSaleList() AppSettings On Error GoTo ErrHandler Dim StartTime As Variant '开始时间 Dim UsedTim ...

  6. 20170706xlVBA城中村改造汇总

    Public Sub GatherDataPicker() Application.ScreenUpdating = False Application.DisplayAlerts = False A ...

  7. Java 历史

    James Gosling 最初开始 Java 语言项目是在 1991 年的 7 月.Java 被用在他的许多 set-top box 工程中.这个语言最开始的时候被称为 Oka,这个是因为 Jame ...

  8. spfa毒瘤算法

    终于知道怎么卡spfa(不优化)这一毒瘤算法了 下面就是造数据代码,点数才1e5,边数379980 随便测了一组数据: count: 831841219(入队次数) 68917.096 ms(足够t到 ...

  9. ubuntu64位库

    安装 12.04ubuntu32位库:sudo apt-get install ia32-libs

  10. 组播IP地址

    组播IP地址组播IP地址用于标识一个IP组播组.IANA(internet assigned number authority)把D类地址空间分配给IP组播,其范围是从224.0.0.0到239.25 ...