家资是何物,积帙列梁梠。这篇文章主要讲述《Android权威编程指南(The Big Nerd Ranch Guide)(第二版)》12.4挑战练习相关的知识,希望能为你提供帮助。
本书第12章是讲解Dialog。12.4挑战练习是在CriminalIntent项目中,再增加一个TimePickerFragment的对话框fragment。通过在CrimeFragment用户界面上添加的时间按钮,
弹出TimePickerFragment界面,允许用户使用TimePicker组件选择crime发生的具体时间。
我的修改思路是:
- 按照DatePickerFragment实现的步骤、方法实现实现TimePickerFragment;
- crime日期与时间是一个整体:
- DatePickerFragment仅可以调整:年月日,时分不变动;
- TimePickerFragment仅可以调整:时分,时分不变动;
- 故Activity切换时,交换数据(附加到Intent上的extra数据单元共用一个Date。
具体实现如下。请各位高手拍砖。
1、使用AppCompat兼容库
依据DatePickerFragment实现方式,仍使用AppCompat兼容库。它在实现DatePickerFragment时,已经添加到CriminalIntent项目中。
2、增加、更新资源文件
2.1)增加标题:“Time of crime:”在项目中,res\values\strings.xml增加 < string name="time_picker_title"> Time of crime:< /string>
即:
1 < resources> 2... ... 3 4< string name="time_picker_title"> Time of crime:< /string> 5 < /resources>
2.2)在CrimFragment界面上添加Time Button在CrimFragment界面上显示Time Button,需要在项目中res\layout\fragment_crime.xml文件,增加下列代码:
1 < ?xml version="1.0" encoding="utf-8"?> 2 < LinearLayout 3... ... 4 5< Button 6... ... 7/> 8 9< Button 10android:id="@+id/crime_time" 11android:layout_width="match_parent" 12android:layout_height="wrap_content" 13android:layout_marginLeft="16dp" 14android:layout_marginRight="16dp" 15/> 16 17< CheckBox 18... ... 19/> 20 21 < /LinearLayout>
2.3)为了保证设备旋转后仍然能正常显示还需在项目的res\layout-land\fragment_crime.xml文件增加类似上面代码:
1 < ?xml version="1.0" encoding="utf-8"?> 2 < LinearLayout 3... ... 4 5< Button 6... ... 7/> 8 9< Button 10android:id="@+id/crime_time" 11android:layout_width="wrap_content" 12android:layout_height="wrap_content" 13android:layout_weight="1"/> 14 15< CheckBox 16... ... 17/> 18 19... ... 20 21 < /LinearLayout>
3、创建新的TimePickerFragment
TimePickerFragment是DialogFragment的子类。然后,在TimePickerFragment中,创建并配置显示TimePicker组件的AlertDialog实例。TimePickerFragment同样由CrimePagerActivity托管。
3.1)使用android.support.v4.app.DialogFragment库创建TimePickerFragment类,并设置其超类DialogFragment,由android.support.v4.app.DialogFragment库支持。
在TimePickerFragment.java中,重载DialogFragment类的onCreateDialog(Bundle savedInstanceState)方法。由托管activity的FragmentManager会调用它,在屏幕上显示DialogFragment。
onCreateDialog(Bundle savedInstanceState)方法的实现代码,创建一个带标题栏和OK按钮的AlertDialog,代码如下。注意:导入AlertDialog时,还是选择AppCompat库中的版本:android.support.v7.app.AlertDialog。
1 public class TimePickerFragment extends DialogFragment { 2 3@Override 4public Dialog onCreateDialog(Bundle savedInstanceState) { 5 6return new AlertDialog.Builder(getActivity()) 7.setTitle(R.string.time_picker_title) 8.setPositiveButton(android.R.string.ok, null) 9.create(); 10} 11 }
这里类似DatePickerFragment,使用AlerDialog.Builder类,以Fluent Interface的方式创建AlertDialog实例。
3.2)显示TimeDialogFragment同DatePickerFragment一样,TimeDialogFragment实例也是由托管activity的FragmentManager管理。使用fragment实例的public void show(FragmentManager manager, String tag)方法,将TimePickerFragment添加给FragmentManager管理并放置到屏幕上。
在CrimeFragment(CrimeFragment.java)中,也为TimePickerFragment增加一个tag常量:
1 private static final String DIALOG_TIME = "DialogTime";
然后,在onCreateView(...)方法中,添加点击时间按钮展现TimePickerFragment界面,实现mTimeButton按钮的OnClickListener监听器接口,代码:
1 public class CrimeFragment extends Fragment { 2 3... ... 4private static final String DIALOG_TIME = "DialogTime"; 5 6... ... 7 8mTimeButton = (Button) v.findViewById(R.id.crime_time); 9mTimeButton.setText(DateFormat.format("h:mm a", mCrime.getDate())); 10mTimeButton.setOnClickListener(new View.OnClickListener() { 11@Override 12public void onClick(View v) { 13FragmentManager manager = getFragmentManager(); 14TimePickerFragment timeDialog = new TimePickerFragment(); 15timeDialog.show(manager, DIALOG_TIME); 16} 17}); 18 19... ... 20 }
这就可以显示:带标题(Time of crime:)和OK(确定)按钮的AlertDialog。
3.3)设置对话框的显示内容同DatePickerFragment.
这时需要在TimePirckerFragment(TimePirckerFragment.java)中,使用AlertDialog.Builder的setView(...)方法, 添加TimePicker组件给AlertDialog对话框:
1 public AlertDialog.Builder setView(View view)
该方法配置对话框,实现在标题栏与按钮之间显示传入的View对象 —— TimePicker。要展示TimePicker,需要在项目工具窗口中,以TimePicker为根元素,创建名为dialog_time.xml的布局文件:
1 < ?xml version="1.0" encoding="utf-8"?> 2 < TimePicker 3xmlns:android="http://schemas.android.com/apk/res/android" 4android:id="@+id/dialog_time_time_picker" 5android:layout_width="match_parent" 6android:layout_height="match_parent" 7android:calendarViewShown="false"> 8 < /TimePicker>
同时在TimePickerFragment.onCreateDialog(...)方法中,实例化DatePicker视图并添加给对话框:
1@Override 2public Dialog onCreateDialog(Bundle savedInstanceState) { 3 4View v = LayoutInflater.from(getActivity()) 5.inflate(R.layout.dialog_time, null); 6 7return new AlertDialog.Builder(getActivity()) 8.setView(v) 9.setTitle(R.string.time_picker_title) 10.setPositiveButton(android.R.string.ok, null) 11.create(); 12}
此时运行CriminalIntent,点击时间按钮,TimePicker就显示在对话框上。
4、数据传递
在书中例子中,DatePicker将修改的日期传递给CrimeFragment后,时间就被“清零”(时间回到0点),而时间按钮上的文字还是之前的时间。
我对DatePickerFragment进行修改,将原来
1@Override 2public Dialog onCreateDialog(Bundle savedInstanceState) { 3... ... 4Calendar calendar = Calendar.getInstance(); 5... ... 6 7return new AlertDialog.Builder(getActivity()) 8... ... 9.setPositiveButton(android.R.string.ok, 10new DialogInterface.OnClickListener() { 11@Override 12public void onClick(DialogInterface dialog, int which) { 13... ... 14 15Date date = new GregorianCalendar(year, month, day).getTime(); 16 17... ... 18} 19} 20) 21.create(); 22}
改为:
1@Override 2public Dialog onCreateDialog(Bundle savedInstanceState) { 3... ... 4final Calendar calendar = Calendar.getInstance(); 5... ... 6 7return new AlertDialog.Builder(getActivity()) 8... ... 9.setPositiveButton(android.R.string.ok, 10new DialogInterface.OnClickListener() { 11@Override 12public void onClick(DialogInterface dialog, int which) { 13... ... 14 15Date date = new GregorianCalendar(year, month, day, 16calendar.get(Calendar.HOUR_OF_DAY), 17calendar.get(Calendar.MINUTE), 18calendar.get(Calendar.SECOND)).getTime(); 19 20... ... 21} 22} 23) 24.create(); 25}
由于在inline函数DialogInterface.OnClickListener()的onClick(...)使用到calendar,这样就要将calender定义改为final,即:
1 final Calendar calendar = Calendar.getInstance();
这样在DatePicker传递修改后的日期回CrimeFragment后,时间持不变。我认为时间的改变正是由TimePicker来完成。就是说 crime的日期(date)和时间(time)是相互关联的。基于这一思路,参考DatePickerFragment,进一步添加时间值的传递。
4.1)把crime记录的时间传递给TimePickerFragment这就需要新建newInstance(Date)方法,然后将Date作为argument附加给fragment。
【《Android权威编程指南(The Big Nerd Ranch Guide)(第二版)》12.4挑战练习】
要新时间返回给CrimeFragment,且更新相应视图和模型层,这需将时间值打包为extra并附加到Intent上,然后调用CrimeFragment.onActivityResult(...)方法,并传入准备好的Intent参数。如前所属“crime的日期(date)和时间(time)是相互关联的”,Date类包含时间,这样时间extra就应该与DatePicker共用一个单元。
4.2)传递数据给TimePickerFragment如前所述,应该用含有时间的crime Date值保存到TimePickerFragment的argument bundle中,TimePickerFragment使可直接获取到它。这样就使用DatePrickerFragment的ARG_DATE标记(tag)。为此要import ARG_DATE,即:
1 import static com.example.bigzhg.criminalintent.DatePickerFragment.ARG_DATE;
在TimePickerFragment.java中,添加newInstance(Date)方法,完成创建和设置fragment argument。
1 public class TimePickerFragment extends DialogFragment { 2 3... ... 4 5public static TimePickerFragment newInstance(Date date) { 6Bundle args = new Bundle(); 7args.putSerializable(ARG_DATE, date); 8 9TimePickerFragment fragment = new TimePickerFragment(); 10fragment.setArguments(args); 11return fragment; 12} 13 14... ... 15 }
再在CrimeFragment中,用TimePickerFragment.newInstance(Date)方法替换掉TimePickerFragment的构造方法:
1@Override 2public View onCreateView( 3LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 4 5... ... 6 7mTimeButton.setOnClickListener(new View.OnClickListener() { 8@Override 9public void onClick(View v) { 10FragmentManager manager = getFragmentManager(); 11 12// TimePickerFragment timeDialog = new TimePickerFragment(); 13TimePickerFragment timeDialog = TimePickerFragment 14.newInstance(mCrime.getDate()); 15... ... 16} 17}); 18 19... ... 20 21}
同DatePickerFragment一样,使用Date中的信息来初始化TimePicker对象。
在onCreateDialog(...)方法内,从argument中获取crime日期(如前所述)的对象Date对象,再创建一个Calendar对象,然后用Date对象配置它,再从Calendar对象中取回所需信息(时、分),来为TimePicker进行初始化:
1 public class TimePickerFragment extends DialogFragment { 2 3private TimePicker mTimePicker; 4 5... ... 6 7@Override 8public Dialog onCreateDialog(Bundle savedInstanceState) { 9Date date = (Date) getArguments().getSerializable(ARG_DATE); 10 11Calendar calendar = Calendar.getInstance(); 12calendar.setTime(date); 13 14int hour = calendar.get(Calendar.HOUR_OF_DAY); 15int minute = calendar.get(Calendar.MINUTE); 16 17View v = LayoutInflater.from(getActivity()) 18.inflate(R.layout.dialog_time, null); 19 20mTimePicker = (TimePicker) v.findViewById(R.id.dialog_time_time_picker); 21mTimePicker.setIs24HourView(false); 22if (Build.VERSION.SDK_INT > = Build.VERSION_CODES.LOLLIPOP) { 23mTimePicker.setHour(hour); 24mTimePicker.setMinute(minute); 25} else { 26mTimePicker.setCurrentHour(hour); 27mTimePicker.setCurrentMinute(minute); 28} 29 30return new AlertDialog.Builder(getActivity()) 31.setView(v) 32.setTitle(R.string.time_picker_title) 33.setPositiveButton(android.R.string.ok, null) 34.create(); 35} 36 37... ... 38 39 }
在onCreateDialog(...)方法内,设置TimePicker是上下午(非24小时)格式:
1 mTimePicker.setIs24HourView(false);
由于我用于调试的手机时Galaxy Note II,系统为Android 4.4.2。因setCurrentHour()和setCurrentMinute()已在新系统中不再使用,故增加SDK的版本判断:
1if (Build.VERSION.SDK_INT > = Build.VERSION_CODES.LOLLIPOP) { 2mTimePicker.setHour(hour); 3mTimePicker.setMinute(minute); 4} else { 5mTimePicker.setCurrentHour(hour); 6mTimePicker.setCurrentMinute(minute); 7}
4.3)返回时间数据给CrimeFragmentCrimeFragment接收TimePickerFragment返回的时间数据,ActivityManager 负责跟踪管理父 activity与子activity间的关系。回传数据后,子activity被销毁,而ActivityManager 知道接收数据的是哪个activity。
4.3.1)设置目标fragment这就要将CrimeFragment设置成TimePickerFragment的目标fragment。即使是在CrimeFragment和TimePickerFragment被销毁和重建后,操作系统也会重新关联它们。调用以下Fragment方法可建立这种关联:
public void setTargetFragment(Fragment fragment, int requestCode)
在CrimeFragment.java中,增加时间请求代码常量:
1 private static final int REQUEST_TIME = 1;
然后将CrimeFragment设为TimePickerFragment实例的目标fragment:
1 timeDialog.setTargetFragment(CrimeFragment.this, REQUEST_TIME);
即:
1 public class CrimeFragment extends Fragment { 2 3... ... 4private static final int REQUEST_TIME = 1; 5 6... ... 7 8@Override 9public View onCreateView( 10LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 11 12... ... 13 14mTimeButton.setOnClickListener(new View.OnClickListener() { 15@Override 16public void onClick(View v) { 17... ... 18 19timeDialog.setTargetFragment(CrimeFragment.this, REQUEST_TIME); 20... ... 21 22} 23}); 24 25... ... 26 27return v; 28}
4.3.2)传递时间数据给目标fragment建立CrimeFragment与TimePickerFragment间的联系后,需将数据回传给CrimeFragment。回传时间将作为extra附加给Intent。
使用TimePickerFragment类调用CrimeFragment.onActivityResult(int 请求代码, int 结果代码, Intent)方法,实现时间数据的回传。
- 请求代码:与传入setTargetFragment(...)方法相匹配,告诉目标fragment返回结果来自哪里。
- 结果代码:决定下一步该采取什么行动。
- Intent:包含extra数据。
类似DatePickerFragment类,在TimePickerFragment类中,新建sendResult(...)私有方法,创建intent并将时间数据与crime Date构成新的Date数据,作为extra附加到intent上。最后调用CrimeFragment.onActivityResult(...)方法。
再就是使用sendResult(...)私有方法。用户点按对话框中的positive(确定)按钮时,需要从TimePicker中获取时间值并回传给CrimeFragment。在onCreateDialog(...)方法中,修改setPositiveButton(...),将null参数改DialogInterface.OnClickListener,并实现DialogInterface.OnClickListener监听器接口。在监听器接口的onClick(...)方法中,获取时间并调用sendResult(...)方法。
这里crime日期和时间一个“整体”,故公用DatePickerFragment的EXTRA_DATE:
1 import static com.example.bigzhg.criminalintent.DatePickerFragment.EXTRA_DATE;
其相关代码:
1 ... ... 2 import static com.example.bigzhg.criminalintent.DatePickerFragment.EXTRA_DATE; 3 4 5 public class TimePickerFragment extends DialogFragment { 6 7... ... 8 9 10@Override 11public Dialog onCreateDialog(Bundle savedInstanceState) { 12... ... 13 14final int year = calendar.get(Calendar.YEAR); 15final int month = calendar.get(Calendar.MONTH); 16final int day = calendar.get(Calendar.DAY_OF_MONTH); 17int hour = calendar.get(Calendar.HOUR_OF_DAY); 18int minute = calendar.get(Calendar.MINUTE); 19 20... ... 21 22return new AlertDialog.Builder(getActivity()) 23.setView(v) 24.setTitle(R.string.time_picker_title) 25// .setPositiveButton(android.R.string.ok, null) 26.setPositiveButton(android.R.string.ok, 27new DialogInterface.OnClickListener() { 28@Override 29public void onClick(DialogInterface dialog, int which) { 30int hour, minute; 31 32if (Build.VERSION.SDK_INT > = Build.VERSION_CODES.LOLLIPOP) { 33hour = mTimePicker.getHour(); 34minute = mTimePicker.getMinute(); 35} else { 36hour = mTimePicker.getCurrentHour(); 37minute = mTimePicker.getCurrentMinute(); 38} 39Date date = new GregorianCalendar( 40year, month, day, hour, minute).getTime(); 41sendResult(Activity.RESULT_OK, date); 42} 43}) 44.create(); 45} 46 47private void sendResult(int relustCode, Date date) { 48if (getTargetFragment() == null) { 49return; 50} 51 52Intent intent = new Intent(); 53intent.putExtra(EXTRA_DATE, date); 54 55getTargetFragment().onActivityResult(getTargetRequestCode(), relustCode, intent); 56} 57 }
这里,calendar在面谈过crime日期和时间是一个“整体”,由crime日期创建的,在TimePickerFragment保持日期值不变,仅仅运许用户调整时间。
再切换到CrimeFragment中,覆盖onActivityResult(...)方法,从extra中获取日期数据,增加“请求代码”的判断,依据“请求代码”:
- 对DatePicker值,设置对应Crime的记录日期,然后刷新日期按钮的显示;
- 对TimePIcker值,设置对应Crime的记录时间,然后刷新时间按钮的显示。
另外,同日期显示一样,为避免代码冗余,可以将时间按钮文字显示代码,封装到updateTiime()公共方法中,然后分别调用。
相关代码:
1 public class CrimeFragment extends Fragment { 2 3... ... 4 5@Override 6public View onCreateView( 7LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 8 9... ... 10 11mTimeButton = (Button) v.findViewById(R.id.crime_time); 12updateTime(); 13... ... 14 15} 16 17@Override 18public void onActivityResult(int requestCode, int resultCode, Intent data) { 19if (resultCode != Activity.RESULT_OK) { 20return; 21} 22 23Date date = (Date) data 24.getSerializableExtra(DatePickerFragment.EXTRA_DATE); 25mCrime.setDate(date); 26 27switch (requestCode) { 28case REQUEST_DATE: 29updateDate(); 30break; 31case REQUEST_TIME: 32updateTime(); 33break; 34} 35} 36 37private void updateDate() { 38mDateButton.setText(DateFormat.format("EEEE, MMMM d, yyyyy", mCrime.getDate())); 39} 40 41private void updateTime() { 42mTimeButton.setText(DateFormat.format("h:mm a", mCrime.getDate())); 43} 44 }
到此,12.4的跳转练习就完成了。完整的代码在GitHub上可以找到。
请高手指点这样添加是否存在什么隐患?谢谢!
推荐阅读
- android logger的使用
- Android程序中使用iconfont心得
- Android中集成第三方支付
- Android传感器系统架构
- 2017Android学习路线图,内附完整自学视频教程+工具经验
- Python检查列表中的所有值是否都大于给定值
- 找出包含k个不同元素的数组所需的最小变化
- C++中的std::is_destructible用法示例
- 如何应用CSS分页符来打印具有很多行的表()