您的位置:首页 > 产品设计 > UI/UE

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

2017-03-06 19:51 525 查看
本书第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
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上可以找到。

 

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

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐