《Android权威编程指南(The Big Nerd Ranch Guide)(第二版)》12.4挑战练习

本书第12章是讲解Dialog。12.4挑战练习是在CriminalIntent项目中,再增加一个TimePickerFragment的对话框fragment。通过在CrimeFragment用户界面上添加的时间按钮,
弹出TimePickerFragment界面,允许用户使用TimePicker组件选择crime发生的具体时间。

 

我的修改思路是:

  1. 按照DatePickerFragment实现的步骤、方法实现实现TimePickerFragment;
  2. crime日期与时间是一个整体:
    1. DatePickerFragment仅可以调整:年月日,时分不变动;
    2. TimePickerFragment仅可以调整:时分,时分不变动;
    3. 故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
10         android:id="@+id/crime_time"
11         android:layout_width="match_parent"
12         android:layout_height="wrap_content"
13         android:layout_marginLeft="16dp"
14         android: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
10             android:id="@+id/crime_time"
11             android:layout_width="wrap_content"
12             android:layout_height="wrap_content"
13             android: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
 4     public Dialog onCreateDialog(Bundle savedInstanceState) {
 5 
 6         return 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         ... ...
 4         private static final String DIALOG_TIME = "DialogTime";
 5 
 6         ... ...
 7 
 8         mTimeButton = (Button) v.findViewById(R.id.crime_time);
 9         mTimeButton.setText(DateFormat.format("h:mm a", mCrime.getDate()));
10         mTimeButton.setOnClickListener(new View.OnClickListener() {
11             @Override
12             public void onClick(View v) {
13                 FragmentManager manager = getFragmentManager();
14                 TimePickerFragment timeDialog = new TimePickerFragment();
15                 timeDialog.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
3     xmlns:android="http://schemas.android.com/apk/res/android"
4     android:id="@+id/dialog_time_time_picker"
5     android:layout_width="match_parent"
6     android:layout_height="match_parent"
7     android:calendarViewShown="false">
8 </TimePicker>

 

同时在TimePickerFragment.onCreateDialog(...)方法中,实例化DatePicker视图并添加给对话框:

 1     @Override
 2     public Dialog onCreateDialog(Bundle savedInstanceState) {
 3 
 4         View v = LayoutInflater.from(getActivity())
 5                 .inflate(R.layout.dialog_time, null);
 6 
 7         return 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
 2     public Dialog onCreateDialog(Bundle savedInstanceState) {
 3         ... ...
 4         Calendar calendar = Calendar.getInstance();
 5         ... ...
 6 
 7         return new AlertDialog.Builder(getActivity())
 8                 ... ...
 9                 .setPositiveButton(android.R.string.ok,
10                         new DialogInterface.OnClickListener() {
11                             @Override
12                             public void onClick(DialogInterface dialog, int which) {
13                                 ... ...
14 
15                                 Date date = new GregorianCalendar(year, month, day).getTime();
16 
17                                 ... ...
18                             }
19                         }
20                 )
21                 .create();
22     }

 

改为:

 1     @Override
 2     public Dialog onCreateDialog(Bundle savedInstanceState) {
 3         ... ...
 4         final Calendar calendar = Calendar.getInstance();
 5         ... ...
 6 
 7         return new AlertDialog.Builder(getActivity())
 8                 ... ...
 9                 .setPositiveButton(android.R.string.ok,
10                         new DialogInterface.OnClickListener() {
11                             @Override
12                             public void onClick(DialogInterface dialog, int which) {
13                                 ... ...
14 
15                                 Date date = new GregorianCalendar(year, month, day,
16                                         calendar.get(Calendar.HOUR_OF_DAY),
17                                         calendar.get(Calendar.MINUTE),
18                                         calendar.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。

 

要新时间返回给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 
 5     public static TimePickerFragment newInstance(Date date) {
 6         Bundle args = new Bundle();
 7         args.putSerializable(ARG_DATE, date);
 8 
 9         TimePickerFragment fragment = new TimePickerFragment();
10         fragment.setArguments(args);
11         return fragment;
12     }
13 
14     ... ...
15 }

 

再在CrimeFragment中,用TimePickerFragment.newInstance(Date)方法替换掉TimePickerFragment的构造方法:

 1     @Override
 2     public View onCreateView(
 3             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 4 
 5         ... ...
 6 
 7         mTimeButton.setOnClickListener(new View.OnClickListener() {
 8             @Override
 9             public void onClick(View v) {
10                 FragmentManager manager = getFragmentManager();
11 
12                 // TimePickerFragment timeDialog = new TimePickerFragment();
13                 TimePickerFragment 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 
 3     private TimePicker mTimePicker;
 4 
 5     ... ...
 6 
 7     @Override
 8     public Dialog onCreateDialog(Bundle savedInstanceState) {
 9         Date date = (Date) getArguments().getSerializable(ARG_DATE);
10 
11         Calendar calendar = Calendar.getInstance();
12         calendar.setTime(date);
13 
14         int hour = calendar.get(Calendar.HOUR_OF_DAY);
15         int minute = calendar.get(Calendar.MINUTE);
16 
17         View v = LayoutInflater.from(getActivity())
18                 .inflate(R.layout.dialog_time, null);
19 
20         mTimePicker = (TimePicker) v.findViewById(R.id.dialog_time_time_picker);
21         mTimePicker.setIs24HourView(false);
22         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
23             mTimePicker.setHour(hour);
24             mTimePicker.setMinute(minute);
25         } else {
26             mTimePicker.setCurrentHour(hour);
27             mTimePicker.setCurrentMinute(minute);
28         }
29 
30         return 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的版本判断:

1         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
2             mTimePicker.setHour(hour);
3             mTimePicker.setMinute(minute);
4         } else {
5             mTimePicker.setCurrentHour(hour);
6             mTimePicker.setCurrentMinute(minute);
7         }

 

4.3)返回时间数据给CrimeFragment

CrimeFragment接收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     ... ...
 4     private static final int REQUEST_TIME = 1;
 5 
 6     ... ...
 7 
 8     @Override
 9     public View onCreateView(
10             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
11 
12         ... ...
13 
14         mTimeButton.setOnClickListener(new View.OnClickListener() {
15             @Override
16             public void onClick(View v) {
17                 ... ...
18 
19                 timeDialog.setTargetFragment(CrimeFragment.this, REQUEST_TIME);
20                 ... ...
21 
22             }
23         });
24 
25         ... ...
26 
27         return 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
11     public Dialog onCreateDialog(Bundle savedInstanceState) {
12         ... ...
13 
14         final int year = calendar.get(Calendar.YEAR);
15         final int month = calendar.get(Calendar.MONTH);
16         final int day = calendar.get(Calendar.DAY_OF_MONTH);
17         int hour = calendar.get(Calendar.HOUR_OF_DAY);
18         int minute = calendar.get(Calendar.MINUTE);
19 
20         ... ...
21 
22         return 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,
27                         new DialogInterface.OnClickListener() {
28                             @Override
29                             public void onClick(DialogInterface dialog, int which) {
30                                 int hour, minute;
31 
32                                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
33                                     hour = mTimePicker.getHour();
34                                     minute = mTimePicker.getMinute();
35                                 } else {
36                                     hour = mTimePicker.getCurrentHour();
37                                     minute = mTimePicker.getCurrentMinute();
38                                 }
39                                 Date date = new GregorianCalendar(
40                                         year, month, day, hour, minute).getTime();
41                                 sendResult(Activity.RESULT_OK, date);
42                             }
43                         })
44                 .create();
45     }
46 
47     private void sendResult(int relustCode, Date date) {
48         if (getTargetFragment() == null) {
49             return;
50         }
51 
52         Intent intent = new Intent();
53         intent.putExtra(EXTRA_DATE, date);
54 
55         getTargetFragment().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
 6     public View onCreateView(
 7             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 8 
 9         ... ...
10 
11         mTimeButton = (Button) v.findViewById(R.id.crime_time);
12         updateTime();
13         ... ...
14 
15     }
16 
17     @Override
18     public void onActivityResult(int requestCode, int resultCode, Intent data) {
19         if (resultCode != Activity.RESULT_OK) {
20             return;
21         }
22 
23         Date date = (Date) data
24                 .getSerializableExtra(DatePickerFragment.EXTRA_DATE);
25         mCrime.setDate(date);
26 
27         switch (requestCode) {
28             case REQUEST_DATE:
29                 updateDate();
30                 break;
31             case REQUEST_TIME:
32                 updateTime();
33                 break;
34         }
35     }
36 
37     private void updateDate() {
38         mDateButton.setText(DateFormat.format("EEEE, MMMM d, yyyyy", mCrime.getDate()));
39     }
40 
41     private void updateTime() {
42         mTimeButton.setText(DateFormat.format("h:mm a", mCrime.getDate()));
43     }
44 }

 

到此,12.4的跳转练习就完成了。完整的代码在GitHub上可以找到。

 

请高手指点这样添加是否存在什么隐患?谢谢!

 

转载于:https://www.cnblogs.com/figozhg/p/6498471.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Title: Android Programming: The Big Nerd Ranch Guide, 3rd Edition Author: Bill Phillips, Chris Stewart Length: 624 pages Edition: 3 Language: English Publisher: Big Nerd Ranch Guides Publication Date: 2017-02-09 ISBN-10: 0134706056 ISBN-13: 9780134706054 Table of Contents Chapter 1. Your First Android Application Chapter 2. Android and Model-View-Controller Chapter 3. The Activity Lifecycle Chapter 4. Debugging Android Apps Chapter 5. Your Second Activity Chapter 6. Android SDK Versions and Compatibility Chapter 7. UI Fragments and the Fragment Manager Chapter 8. Displaying Lists with RecyclerView Chapter 9. Creating User Interfaces with Layouts and Widgets Chapter 10. Using Fragment Arguments Chapter 11. Using ViewPager Chapter 12. Dialogs Chapter 13. The Toolbar Chapter 14. SQLite Databases Chapter 15. Implicit Intents Chapter 16. Taking Pictures with Intents Chapter 17. Two-Pane Master-Detail Interfaces Chapter 18. Localization Chapter 19. Accessibility Chapter 20. Data Binding and MVVM Chapter 21. Unit Testing and Audio Playback Chapter 22. Styles and Themes Chapter 23. XML Drawables Chapter 24. More About Intents and Tasks Chapter 25. HTTP and Background Tasks Chapter 26. Loopers, Handlers, and HandlerThread Chapter 27. Search Chapter 28. Background Services Chapter 29. Broadcast Intents Chapter 30. Browsing the Web and WebView Chapter 31. Custom Views and Touch Events Chapter 32. Property Animation Chapter 33. Locations and Play Services Chapter 34. Maps Chapter 35. Material Design Chapter 36. Afterword

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值