请参考教材,全面理解和完成本章节内容... ...
复制工程ch11,将工程目录改名为ch12。
对话框既能引起用户的注意也可接收用户的输入。在提示重要信息或提供用户选项方面,它都非常有用。本章,我们将CriminalIntent(陋习手记)应用添加一个对话框,以供用户改变crime记录日期。点击CrimeFragment上的日期按钮,即可弹出对话框,如图12-1所示。
图12-1 可供选择crime日期的对话框
图12-1所示的AlertDialog视图封装在DialogFragment(Fragment的子类)实例中。不使用DialogFragment,也可显示AlertDialog视图,但Android开发原则不推荐这种做法。使用FragmentManager管理对话框,可使用更多配置选项来显示对话框。
就CriminalIntent应用来说,我们首先会创建一个名为DatePickeFragment的DialogFragment子类。然后,在DatePickeFragment中,创建并配置一个显示DatePicker组件的AlertDialog实例。DatePickeFragment也将交给CrimePagerActivity来托管。
图12-2展示了以上各对象间的关系。
图12-2 两个由CrimePagerActivity托管的fragment对象图解
总结下来,要实现对话框显示,首先应完成以下任务:
- 创建DatePickeFragment类;
- 创建AlertDialog;
- 通过FragmentManager在屏幕上显示对话框。
在本章的后面,我们将配置使用DatePicker,并实现CrimeFragment和DatePickerFragment之间必要数据的传递。
继续学习之前,请参照代码清单12-1添加所需的字符串资源。
代码清单12-1 为对话框标题添加字符串资源(values/strings.xml)
12.1 创建 DialogFragment
创建一个名为DatePickerFragment
的新类,并设置其DialogFragment
超类为支持库中的android.support.v4.app.DialogFragment
类。
DialogFragment
类有如下方法:
public Dialog onCreateDialog(Bundle savedInstanceState)
在屏幕上显示DialogFragment
时,托管activity的FragmentManager
会调用onCreateDialog
方法。
在DatePickerFragment.java中,添加onCreateDialog(
)
方法的实现代码,创建一个带标题栏和OK按钮的AlertDialog
,如代码清单12-2所示。(DatePicker
组件稍后会添加)
注意AlertDialog需要import android.support.v7.app.AlertDialog;
代码清单12-2 创建DialogFragment
(DatePickerFragment.java)
如代码清单12-2所示,使用AlertDialog.Builder
类,以流接口(fluent interface)的方式创建了一个AlertDialog
实例。
12.1.1 显示DialogFragment
和其他fragment一样,DialogFragment
实例也是由托管activity的FragmentManager
管理的。
在CrimeFragment
中,为DatePickerFragment
添加一个tag常量。然后,在onCreateView(
)
方法中,删除禁用日期按钮的代码。为实现用户点击日期按钮展现DatePickerFragment
界面,实现mDateButton
按钮的OnClickListener
监听器接口,如代码清单12-3所示。
代码清单12-3 显示DialogFragment
(CrimeFragment.java)
运行CriminalIntent应用。点击日期按钮弹出对话框,单击OK按钮消除对话框,如图12-3所示。
图12-3 带有标题和OK按钮的AlertDialog
12.1.2 设置对话框的显示内容
接下来,使用下列AlertDialog.Builder
的setView(...)
方法,添加DatePicker
组件到AlertDialog
对话框:
public AlertDialog.Builder setView(View view)
该方法配置对话框,实现在标题栏与按钮之间显示传入的View
对象。
在layout目录下,创建名为dialog_date.xml的布局文件(Layout XML File),注意,将其根元素改为DatePicker
。该布局仅包含一个View
对象,即我们生成并传给setView(...)
方法的DatePicker
视图。
参照图12-4,配置好DatePicker
的XML布局文件。
图12-4 DatePicker
布局(layout/dialog_date.xml)
在DatePickerFragment.onCreateDialog( )
方法中,生成DatePicker
视图并添加到对话框中,如代码清单12-4所示。
代码清单12-4 添加DatePicker
给AlertDialog
(DatePickerFragment.java)
运行应用, 点击日期按钮,确认对话框上是否出现了DatePicker
视图,如图12-5所示。
图12-5 显示DatePicker
的AlertDialog
至此,将对话框显示在屏幕上的工作就完成了。下一节,我们会将DatePicker
同Crime
的日期关联起来,并支持用户对其进行修改。
12.2 fragment 间的数据传递
前面,我们已经实现了activity之间以及基于fragment的activity之间的数据传递。现在需实现由同一activity托管的两个fragment,即CrimeFragment
和DatePickerFragment
间的数据传递,如图12-6。
图12-6 CrimeFragment
与DatePickerFragment
间的对话
要传递crime的记录日期给DatePickerFragment
,需实现一个newInstance(Date)
方法,然后将Date
作为argument附加给fragment。
为返回新日期给CrimeFragment
,并实现模型层以及对应视图的更新,需将日期打包为extra并附加到Intent
上,然后调用CrimeFragment.onActivityResult( )
方法,并传入准备好的Intent
参数,如图12-7所示。
图12-7 CrimeFragment
和DatePickerFragment
间的事件流
但接下来,我们并没有选择调用托管activity的Activity.onActivityResult( )
方法,而是调用了Fragment.onActivityResult(...)
方法,这似乎有点奇怪?事实上,通过调用onActivityResult( )
方法将数据从一个fragment返还给另一个fragment,这种做法不仅行得通,而且可以更灵活地展现对话框fragment。
12.2.1 传递数据给DatePickerFragment
要传递crime记录日期给DatePickerFragment
,需将记录日期保存在DatePickerFragment
的argument bundle中,这样,DatePickerFragment
便可直接获取到它。
回顾第10章的内容,我们知道,替代fragment的构造方法,创建和设置fragment argument通常是在一个newInstance()方法中完成的。在DatePickerFragment.java中,添加newInstance(Date)
方法,如代码清单12-5所示。
代码清单12-5 添加newInstance(Date)
方法(DatePickerFragment.java)
然后,在CrimeFragment
中,用DatePickerFragment.newInstance(Date)
方法替换掉DatePickerFragment
的构造方法,如代码清单12-6所示。
代码清单12-6 添加newInstance()
方法(CrimeFragment.java)
DatePickerFragment
需使用Date
中的信息来初始化DatePicker
对象。然而,DatePicker
对象的初始化需整数形式的月、日、年。Date
就是个时间戳,它无法直接提供整数形式的月、日、年。
要想获得所需的整数数值,必须首先创建一个Calendar
对象,然后用Date
对象对其进行配置,即可从Calendar
对象中取回所需信息。
在onCreateDialog(...)
方法内,从argument中获取Date
对象,然后使用它和Calendar
对象完成DatePicker
的初始化工作,如代码清单12-7所示。
代码清单12-7 获取Date
对象并初始化DatePicker
(DatePickerFragment.java)
如代码所示,初始化DatePicker
对象时,同时也在该对象上设置了OnDateChangedListener
监听器。这样,用户改变DatePicker
内的日期后,Date
对象即可得到同步更新。下一节,我们将把该Date
对象回传给CrimeFragment
。
为防止设备旋转时发生Date
数据的丢失,在onDateChanged( )
方法的尾部,我们将Date
对象回写保存到了fragment argument中。如发生设备旋转,而DatePickerFragment
正显示在屏幕上,那么FragmentManager
会销毁当前实例并产生一个新的实例。新实例创建后,FragmentManager
会调用它的onCreateDialog( )
方法,这样新实例便可从argument中获得保存的日期数据。相比以前使用onSaveInstanceState( )
方法保存状态,在fragment argument中保存数据应对设备旋转显然更简单。
(如经常使用fragment,可能会困惑为何不直接保存DatePickerFragment
?使用保留的fragment处理设备旋转问题(详见第14章)确实是个好办法。但不幸的是,目前DialogFragment
类有个bug,会导致保存的实例行为异常,因此,尝试保存DatePickerFragment
现在还不是个好的选择。)
现在,CrimeFragment
可成功将要显示的日期传递给DatePickerFragment
。运行CriminalIntent应用,查看最终效果。
12.2.2 返回数据给 CrimeFragment
为使CrimeFragment
接收到DatePickerFragment
返回的日期,需以某种方式追踪记录二者间的关系。
对于activity的数据回传,我们调用startActivityForResult( )
方法,ActivityManager
负责跟踪记录父activity与子activity间的关系。当子activity回传数据后被销毁了,ActivityManager
知道接收返回数据的应为哪一个activity。
设置目标fragment
类似于activity间的关联,可将CrimeFragment
设置成DatePickerFragment
的目标 fragment。要建立这种关联,可调用以下Fragment
方法:
public void setTargetFragment(Fragment fragment, int requestCode)
该方法接受目标fragment以及一个类似于传入startActivityForResult( )
方法的请求代码作为参数。随后,目标fragment可使用该请求代码通知是哪一个fragment在返回数据信息。
目标fragment以及请求代码由FragmentManager
负责跟踪记录,我们可调用fragment(设置目标fragment的fragment)的getTargetFragment()
和getTargetRequestCode()
方法获取它们。
在CrimeFragment.java中,创建一个请求代码常量,然后将CrimeFragment
设为DatePickerFragment
实例的目标fragment,如代码清单12-8所示。
代码清单12-8 设置目标fragment(CrimeFragment.java)
传递数据给目标fragment
建立CrimeFragment
与DatePickerFragment
间的联系后,需将数据返还给CrimeFragment
。返回的日期数据将作为extra附加给Intent
。
在DatePickerFragment
类中,新建一个sendResult()
私有方法。通过该方法,创建一个intent,将日期数据作为extra附加到intent上。最后调用CrimeFragment.onActivityResult()
方法。在onCreateDialog()
方法中,取代setPositiveButton()
的null
参数,实现一个DialogInterface.OnClickListener
监听器接口。然后在监听器接口的onClick()
方法中,调用新建的sendResult()
私有方法并传入结果代码,如代码清单12-9所示。
代码清单12-9 回调目标fragment(DatePickerFragment.java)
在CrimeFragment中,覆盖onActivityResult(...)方法,从extra中获取日期数据,设置对应Crime的记录日期,然后刷新日期按钮的显示,如代码清单12-10所示。
代码清单12-10 响应DatePicker对话框(CrimeFragment.java)
在onCreateView()
与onActivityResult()
方法中,设置按钮上显示信息的代码完全一样。因此,为避免代码冗余,将其封装到一个公有的updateDate()
方法中,然后分别在两个方法中调用它,如代码清单12-11所示。
代码清单12-11 使用公共的updateDate()
方法(CrimeFragment.java)
日期数据的双向传递完成了。运行CriminalIntent应用,确保可以控制日期的传递与显示。修改某项Crime
的日期,确认CrimeFragment
视图显示了新的日期。然后返回crime列表项界面,查看对应Crime
的日期,确认模型层数据已得到更新。
还有一个问题, 日期显示的格式是不好看,改成“标准”格式比较耐看,参考代码清单12-12修改updateDate()函数。
代码清单12-12 修改updateDate()
方法,显示北京时间(CrimeFragment.java)
同样,ListView 的日期显示的格式也是“不好看“,参考代码清单12-13改成“标准”格式
代码清单12-13 ListView 的日期,显示北京时间(CrimeFragment.java)