《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>
即:
<resources>
... ... <string name="time_picker_title">Time of crime:</string>
</resources>
2.2)在CrimFragment界面上添加Time Button
在CrimFragment界面上显示Time Button,需要在项目中res\layout\fragment_crime.xml文件,增加下列代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
... ... <Button
... ...
/> <Button
android:id="@+id/crime_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
/> <CheckBox
... ...
/> </LinearLayout>
2.3)为了保证设备旋转后仍然能正常显示
还需在项目的res\layout-land\fragment_crime.xml文件增加类似上面代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
... ... <Button
... ...
/> <Button
android:id="@+id/crime_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/> <CheckBox
... ...
/> ... ... </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。
public class TimePickerFragment extends DialogFragment { @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { return new AlertDialog.Builder(getActivity())
.setTitle(R.string.time_picker_title)
.setPositiveButton(android.R.string.ok, null)
.create();
}
}
这里类似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常量:
private static final String DIALOG_TIME = "DialogTime";
然后,在onCreateView(...)方法中,添加点击时间按钮展现TimePickerFragment界面,实现mTimeButton按钮的OnClickListener监听器接口,代码:
public class CrimeFragment extends Fragment { ... ...
private static final String DIALOG_TIME = "DialogTime"; ... ... mTimeButton = (Button) v.findViewById(R.id.crime_time);
mTimeButton.setText(DateFormat.format("h:mm a", mCrime.getDate()));
mTimeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager manager = getFragmentManager();
TimePickerFragment timeDialog = new TimePickerFragment();
timeDialog.show(manager, DIALOG_TIME);
}
}); ... ...
}
这就可以显示:带标题(Time of crime:)和OK(确定)按钮的AlertDialog。
3.3)设置对话框的显示内容
同DatePickerFragment.
这时需要在TimePirckerFragment(TimePirckerFragment.java)中,使用AlertDialog.Builder的setView(...)方法, 添加TimePicker组件给AlertDialog对话框:
public AlertDialog.Builder setView(View view)
该方法配置对话框,实现在标题栏与按钮之间显示传入的View对象 —— TimePicker。要展示TimePicker,需要在项目工具窗口中,以TimePicker为根元素,创建名为dialog_time.xml的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<TimePicker
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dialog_time_time_picker"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:calendarViewShown="false">
</TimePicker>
同时在TimePickerFragment.onCreateDialog(...)方法中,实例化DatePicker视图并添加给对话框:
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) { View v = LayoutInflater.from(getActivity())
.inflate(R.layout.dialog_time, null); return new AlertDialog.Builder(getActivity())
.setView(v)
.setTitle(R.string.time_picker_title)
.setPositiveButton(android.R.string.ok, null)
.create();
}
此时运行CriminalIntent,点击时间按钮,TimePicker就显示在对话框上。
4、数据传递
在书中例子中,DatePicker将修改的日期传递给CrimeFragment后,时间就被“清零”(时间回到0点),而时间按钮上的文字还是之前的时间。
我对DatePickerFragment进行修改,将原来
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
... ...
Calendar calendar = Calendar.getInstance();
... ... return new AlertDialog.Builder(getActivity())
... ...
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
... ... Date date = new GregorianCalendar(year, month, day).getTime(); ... ...
}
}
)
.create();
}
改为:
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
... ...
final Calendar calendar = Calendar.getInstance();
... ... return new AlertDialog.Builder(getActivity())
... ...
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
... ... Date date = new GregorianCalendar(year, month, day,
calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE),
calendar.get(Calendar.SECOND)).getTime(); ... ...
}
}
)
.create();
}
由于在inline函数DialogInterface.OnClickListener()的onClick(...)使用到calendar,这样就要将calender定义改为final,即:
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,即:
import static com.example.bigzhg.criminalintent.DatePickerFragment.ARG_DATE;
在TimePickerFragment.java中,添加newInstance(Date)方法,完成创建和设置fragment argument。
public class TimePickerFragment extends DialogFragment { ... ... public static TimePickerFragment newInstance(Date date) {
Bundle args = new Bundle();
args.putSerializable(ARG_DATE, date); TimePickerFragment fragment = new TimePickerFragment();
fragment.setArguments(args);
return fragment;
} ... ...
}
再在CrimeFragment中,用TimePickerFragment.newInstance(Date)方法替换掉TimePickerFragment的构造方法:
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ... ... mTimeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager manager = getFragmentManager(); // TimePickerFragment timeDialog = new TimePickerFragment();
TimePickerFragment timeDialog = TimePickerFragment
.newInstance(mCrime.getDate());
... ...
}
}); ... ... }
同DatePickerFragment一样,使用Date中的信息来初始化TimePicker对象。
在onCreateDialog(...)方法内,从argument中获取crime日期(如前所述)的对象Date对象,再创建一个Calendar对象,然后用Date对象配置它,再从Calendar对象中取回所需信息(时、分),来为TimePicker进行初始化:
public class TimePickerFragment extends DialogFragment { private TimePicker mTimePicker; ... ... @Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Date date = (Date) getArguments().getSerializable(ARG_DATE); Calendar calendar = Calendar.getInstance();
calendar.setTime(date); int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE); View v = LayoutInflater.from(getActivity())
.inflate(R.layout.dialog_time, null); mTimePicker = (TimePicker) v.findViewById(R.id.dialog_time_time_picker);
mTimePicker.setIs24HourView(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mTimePicker.setHour(hour);
mTimePicker.setMinute(minute);
} else {
mTimePicker.setCurrentHour(hour);
mTimePicker.setCurrentMinute(minute);
} return new AlertDialog.Builder(getActivity())
.setView(v)
.setTitle(R.string.time_picker_title)
.setPositiveButton(android.R.string.ok, null)
.create();
} ... ... }
在onCreateDialog(...)方法内,设置TimePicker是上下午(非24小时)格式:
mTimePicker.setIs24HourView(false);
由于我用于调试的手机时Galaxy Note II,系统为Android 4.4.2。因setCurrentHour()和setCurrentMinute()已在新系统中不再使用,故增加SDK的版本判断:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mTimePicker.setHour(hour);
mTimePicker.setMinute(minute);
} else {
mTimePicker.setCurrentHour(hour);
mTimePicker.setCurrentMinute(minute);
}
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中,增加时间请求代码常量:
private static final int REQUEST_TIME = 1;
然后将CrimeFragment设为TimePickerFragment实例的目标fragment:
timeDialog.setTargetFragment(CrimeFragment.this, REQUEST_TIME);
即:
public class CrimeFragment extends Fragment { ... ...
private static final int REQUEST_TIME = 1; ... ... @Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ... ... mTimeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
... ... timeDialog.setTargetFragment(CrimeFragment.this, REQUEST_TIME);
... ... }
}); ... ... return v;
}
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:
import static com.example.bigzhg.criminalintent.DatePickerFragment.EXTRA_DATE;
其相关代码:
... ...
import static com.example.bigzhg.criminalintent.DatePickerFragment.EXTRA_DATE; public class TimePickerFragment extends DialogFragment { ... ... @Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
... ... final int year = calendar.get(Calendar.YEAR);
final int month = calendar.get(Calendar.MONTH);
final int day = calendar.get(Calendar.DAY_OF_MONTH);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE); ... ... return new AlertDialog.Builder(getActivity())
.setView(v)
.setTitle(R.string.time_picker_title)
// .setPositiveButton(android.R.string.ok, null)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int hour, minute; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
hour = mTimePicker.getHour();
minute = mTimePicker.getMinute();
} else {
hour = mTimePicker.getCurrentHour();
minute = mTimePicker.getCurrentMinute();
}
Date date = new GregorianCalendar(
year, month, day, hour, minute).getTime();
sendResult(Activity.RESULT_OK, date);
}
})
.create();
} private void sendResult(int relustCode, Date date) {
if (getTargetFragment() == null) {
return;
} Intent intent = new Intent();
intent.putExtra(EXTRA_DATE, date); getTargetFragment().onActivityResult(getTargetRequestCode(), relustCode, intent);
}
}
这里,calendar在面谈过crime日期和时间是一个“整体”,由crime日期创建的,在TimePickerFragment保持日期值不变,仅仅运许用户调整时间。
再切换到CrimeFragment中,覆盖onActivityResult(...)方法,从extra中获取日期数据,增加“请求代码”的判断,依据“请求代码”:
- 对DatePicker值,设置对应Crime的记录日期,然后刷新日期按钮的显示;
- 对TimePIcker值,设置对应Crime的记录时间,然后刷新时间按钮的显示。
另外,同日期显示一样,为避免代码冗余,可以将时间按钮文字显示代码,封装到updateTiime()公共方法中,然后分别调用。
相关代码:
public class CrimeFragment extends Fragment { ... ... @Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ... ... mTimeButton = (Button) v.findViewById(R.id.crime_time);
updateTime();
... ... } @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
return;
} Date date = (Date) data
.getSerializableExtra(DatePickerFragment.EXTRA_DATE);
mCrime.setDate(date); switch (requestCode) {
case REQUEST_DATE:
updateDate();
break;
case REQUEST_TIME:
updateTime();
break;
}
} private void updateDate() {
mDateButton.setText(DateFormat.format("EEEE, MMMM d, yyyyy", mCrime.getDate()));
} private void updateTime() {
mTimeButton.setText(DateFormat.format("h:mm a", mCrime.getDate()));
}
}
到此,12.4的跳转练习就完成了。完整的代码在GitHub上可以找到。
请高手指点这样添加是否存在什么隐患?谢谢!
《Android权威编程指南(The Big Nerd Ranch Guide)(第二版)》12.4挑战练习的更多相关文章
- Android权威编程指南读书笔记(1-2章)
第一章 Android应用初体验 1.4用户界面设计 <?xml version="1.0" encoding="utf-8"?> ADT21开发版 ...
- 看 《android权威编程指南》 的笔记
Android 编译工具 确保ant已安装并正常运行,android sdk的tools/和platform-tools目录包含在可执行文件的搜索路径中 切换到项目目录并执行以下命令: android ...
- Objective-C Programming The Big Nerd Ranch Guide 笔记 19-37
Properties are either atomic or nonatomic, The difference has to do with multithreading. atomic is t ...
- 安卓权威编程指南 - 第五章学习笔记(两个Activity)
学习安卓编程权威指南第五章的时候自己写了个简单的Demo来加深理解两个Activity互相传递数据的问题,然后将自己的学习笔记贴上来,如有错误还请指正. IntentActivityDemo学习笔记 ...
- Android: Fragment编程指南
本文来自于www.lanttor.org Fragment代表了Activity里的一个行为,或者Activity UI的一部分.你可以在一个activity里构造多个Fragment,也可以在多个a ...
- 安卓权威编程指南-笔记(第21章 XML drawable)
在Andorid的世界里,凡事要在屏幕上绘制的东西都可以叫drawable,比如抽象图形,Drawable的子类,位图图形等,我们之前用来封装图片的BitmapDrawable就是一种drawable ...
- 安卓权威编程指南-笔记(第27章 broadcast intent)
本章需求:首先,让应用轮询新结果并在有所发现时及时通知用户,即使用户重启设备后还没有打开过应用.其次,保证用户在使用应用时不出现新结果通知. 1. 一般intent和broadcast intent ...
- 安卓权威编程指南 挑战练习(第26章 在 Lollipop 设备上使用 JobService)
26.11 挑战练习:在 Lollipop 设备上使用 JobService 请创建另一个 PollService 实现版本.新的 PollService 应该继承 JobService 并使用 Jo ...
- 安卓权威编程指南-笔记(第24章 Looper Handler 和 HandlerThread)
AsyncTask是执行后台线程的最简单方式,但它不适用于那些重复且长时间运行的任务. 1. Looper Android中,线程拥有一个消息队列(message queue),使用消息队列的线程叫做 ...
随机推荐
- cesium 加载倾斜摄影模型(这里有一坑)
代码如下: // Construct the default list of terrain sources. var terrainModels = Cesium.createDefaultTerr ...
- 测试笔记:本地存储localstorage与sessionstorage
最近测试的投票项目开发说用的是localstorage.查了下是h5的本地存储.还有个sessionstorage,区别在于sessonstorage关闭页面后清空,localstorage保留. 以 ...
- while循环小例
# 使用while 循环输入 1 2 3 4 5 6 8 9 10 n = 1 while n <= 10: if n == 7: pass else: print(n) n = n + 1 # ...
- mysql索引长度
http://blog.csdn.net/qsc0624/article/details/51335632 大家应该知道InnoDB单列索引长度不能超过767bytes,联合索引还有一个限制是长度不能 ...
- 认识http客户端
最简单的http客户端就是我们的浏览器,浏览器地址输入baidu.com,就会返回响应内容,打开network,都是http请求,第一个就是www.baidu.com的请求,旁边第一个General就 ...
- 97: Address family not supported by protocol,nginx服务启动失败
1.启动nginx服务报错 环境:centos 6.9,yum安装的nginx,启动报错 [root@lnmp ~]# nginx -tnginx: the configuration file /e ...
- 富文本 文字图片点击,(TextView)
textview上的富文本支持 文字,图片的点击事件 - (void)protocolIsSelect:(BOOL)select { NSMutableAttributedString *attrib ...
- 2017.11.18 C语言的算法分析题目
算法分析 1. 选定实验题目,仔细阅读实验要求,设计好输入输出,按照分治法的思想构思算法,选取合适的存储结构实现应用的操作. 2. 设计的结果应在Visual C++ 实验环境下实现并进行调试.(也可 ...
- Java自定义异常信息
通常在开发过程中,会遇到很多异常,对于一些知道异常的原因,这时候想要返回给浏览器,就需要自定义系统的异常 1.Spring 注入异常处理类 <bean id ="commonExce ...
- pycharm界面美化,个人喜欢
进入file-setting选项 界面设置主要是在appearance和editor里面.appearance主要是整个pycharm的主题设置,比如文件管理窗口的颜色,其实就是软件本身的主题设置.我 ...