zoukankan      html  css  js  c++  java
  • 《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:”

    在项目中,resvaluesstrings.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,需要在项目中reslayoutfragment_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)为了保证设备旋转后仍然能正常显示

    还需在项目的reslayout-landfragment_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上可以找到。

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

  • 相关阅读:
    vue学习第四天 ------ 临时笔记
    vue学习第三天 ------ 临时笔记
    vue学习第二天 ------ 临时笔记
    vue学习第一天 ------ 临时笔记
    vue ------ 安装和引入
    swagger-tools QuickStart
    build-your-microservices-api-with-swagger
    test-doubles-fakes-mocks-and-stubs
    swaggerhub 文档
    React Server Side Rendering
  • 原文地址:https://www.cnblogs.com/figozhg/p/6498471.html
Copyright © 2011-2022 走看看