请参考教材,全面理解和完成本章节内容... ...java
复制工程ch9,将工程目录更名为ch10。安全
本章,咱们将实现CriminalIntent「陋习手记」应用的列表与明细部分的关联。用户点击某个「陋习」crime列表项时,会生成一个负责托管CrimeFragment的CrimeActivity,并显示出某特定Crime实例的明细信息。如图10-1所示。编码
图10-1 从CrimeListActivity中启动CrimeActivityspa
从fragment中启动activity的实现方式,基本等同于从activity中启动另外一activity的实现方式。咱们调用Fragment.startActivity(Intent)
方法,该方法在后台会调用对应的Activity
方法。操作系统
在CrimeListFragment
的onListItemClick(...)
实现方法里,用启动CrimeActivity
实例的代码,替换日志记录crime标题的代码,如代码清单10-1所示。(暂时忽略Crime
变量未使用的提示信息,下一节会使用它)3d
代码清单10-1 启动CrimeActivity
(CrimeListFragment.java)日志
以上代码中,指定要启动的activity为CrimeActivity
,CrimeListFragment
建立了一个显式intent。在CrimeListFragment
中使用getActivity()
方法传入它的托管activity
,此activity
是Intent
构造方法须要的Context
对象。code
运行CriminalIntent应用。点击任意列表项,屏幕上会出现一个托管CrimeFragment
的CrimeActivity
,如图10-2所示。对象
图10-2 空白的CrimeFragmentblog
因为不知道该显示哪一个Crime
对象的信息,CrimeFragment
也就没有显示出特定Crime
对象的数据信息。
经过将mCrimeId
值附加到Intent
的extra上,咱们能够告知CrimeFragment
应显示的Crime
。在onListItemClick(...)
方法中,将用户所选Crime
的mCrimeId
值附加到用来启动CrimeActivity
的intent上。输入代码清单10-2所示代码,IDE会报告一个错误信息,这是由于CrimeFragment.EXTRA_CRIME_ID
的key值尚未建立。暂时忽略该条错误信息,稍后咱们会建立它。
代码清单10-2 启动附加extra的CrimeActivity
(CrimeListFragment.java)
建立了显式intent后,调用putExtra(
)
方法,传入匹配mCrimeId
的字符串key与key值,完成extra信息的准备。这里,因为UUID
是Serializable
对象,咱们调用了可接受Serializable
对象的putExtra(
)
方法,即putExtra(String, Serializable)
方法。
简单获取extra的方法是,返回至CrimeFragment
类,为extra添加key。而后,在onCreate(Bundle)
方法中,获得CrimeActivity
的intent内的extra信息后,再使用它获取Crime
对象,如代码清单10-3所示。
代码清单10-3 获取extra信息并取得Crime
对象(CrimeFragment.java)
在代码清单10-3中,除了getActivity()
方法的调用,获取extra数据的实现代码与activity里获取extra数据的代码同样。getIntent()
方法返回用来启动CrimeActivity
的Intent
。而后调用Intent
的getSerializableExtra(String)
方法获取UUID
并存入变量中。
取得Crime
的ID后,利用该ID从CrimeLab
单例中调取Crime
对象。使用CrimeLab.get(...)
方法须要Context
对象,所以CrimeFragment
传入了CrimeActivity
。
Crime
数据更新CrimeFragment
视图既然CrimeFragment
获取了Crime
对象,它的视图即可显示该Crime
对象的数据。参照代码清单10-4,更新onCreateView(...)
方法,显示Crime
对象的标题及解决状态。(显示日期的代码早已就绪)
代码清单10-4 更新视图对象(CrimeFragment.java)
运行应用。选中Crime #4,查看显示了正确crime数据信息的CrimeFragment实例,如图10-3所示。CriminalIntent
图10-3 Crime #4列表项的明细内容
只需几行简单的代码,就可实现让fragment直接获取托管activity的intent。然而,这种方式是以牺牲fragment的封装性为代价的。CrimeFragment
再也不是可复用的构建单元,由于它老是须要由某个具体activity托管着,该activity的Intent
又定义了名为EXTRA_CRIME_ID
的extra。
就CrimeFragment
类来讲,这看起来合情合理。但这也意味着,按照当前的编码实现,CrimeFragment
便再也没法用于任何其余的activity了。
一个比较好的作法是,将mCrimeId
存储(Stash)在CrimeFragment
的某个地方,而不是将它保存在CrimeActivity
的私有空间里。这样,无需依赖于CrimeActivity
的intent内指定extra的存在,CrimeFragment
就能(本身)获取本身所需的extra数据信息。fragment的“某个地方”实际就是它的arguments bundle。
每一个fragment实例均可附带一个Bundle
对象。该bundle可含有多个key-value对,咱们能够如同附加extra到Activity
的intent中那样使用它们。一个key-value对即一个argument。
要建立fragment argument,首先需建立Bundle
对象。而后,使用Bundle
限定类型的“put”方法(相似于Intent
的方法),将argument添加到bundle中(如如下代码所示)。
Bundle args = new Bundle();
args.putSerializable(EXTRA_MY_OBJECT, myObject);
args.putInt(EXTRA_MY_INT, myInt);
args.putCharSequence(EXTRA_MY_STRING, myString);
附加argument bundle给fragment,需调用Fragment.setArguments(Bundle)
方法。注意,该任务必须在fragment建立后、添加给activity前完成。
为知足以上苛刻的要求,Android开发者遵循的习惯作法是:添加名为newInstance()
的静态方法给Fragment
类。使用该方法,完成fragment实例及bundle对象的建立,而后将argument放入bundle中,最后再附加给fragment。
托管activity须要fragment实例时,需调用newInstance()
方法,而非直接调用其构造方法。并且,为知足fragment建立argument的要求,activity可传入任何须要的参数给newInstance()
方法。
如代码清单10-5所示,在CrimeFragment
类中,编写能够接受UUID
参数的newInstance(UUID)
方法,经过该方法,完成arguments bundle以及fragment实例的建立,最后附加argument给fragment。
代码清单10-5 编写newInstance(UUID)
方法(CrimeFragment.java)
如今,当CrimeActivity
建立CrimeFragment
时,应调用CrimeFragment.newInstance(UUID)
方法,并传入从它的extra中获取的UUID
参数值。回到CrimeActivity
类中,在createFragment()
方法里,从CrimeActivity
的intent中获取extra数据信息,并将之传入CrimeFragment.newInstance(UUID)
方法,如代码清单10-6所示。
代码清单10-6 使用newInstance(UUID)
方法(CrimeActivity.java)
注意,交互的activity和fragment不须要也没法同时保持通用独立性。CrimeActivity
必须了解CrimeFragment
的内部细节,好比知晓它内部有一个newInstance(UUID)
方法。这很正常。托管activity就应该知道有关托管fragment方法的细节,但fragment则没必要知道其托管activity的细节问题。至少在须要保持fragment通用独立性的时候是如此。
fragment在须要获取它的argument时,会先调用Fragment
类的getArguments()
方法,接着再调用Bundle
的限定类型的“get”方法,如getSerializable(...)
方法。
如今回到CrimeFragment.onCreate(...)
方法中,调整代码,改成从fragment的argument中获取UUID
,如代码清单10-7所示。
代码清单10-7 从argument中获取crime ID(CrimeFragment.java)
运行CriminalIntent应用。虽然运行结果仍与以前一致,但咱们应该感到由衷地高兴。由于咱们不只保持了CrimeFragment
类的独立性,又为下一章实现CriminalIntent应用更为复杂的列表项导航打下了良好基础。
运行CriminalIntent应用,点击某个列表项,而后修改对应的Crime明细信息。这些修改的数据被保存至模型层,但返回列表后,列表视图并无发生改变。下面咱们来处理这个问题。
如模型层保存的数据发生改变(或可能发生改变),应通知列表视图的adapter,以便其及时获取最新数据并从新加载显示列表项。在适当的时点,与系统的ActivityManager回退栈协同运做,能够完成列表项的刷新。
CrimeListFragment启动CrimeActivity实例后,CrimeActivity被置于回退栈顶。这致使原先处于栈顶的CrimeListActivity实例被暂停并中止。
用户点击后退键返回到列表项界面,CrimeActivity随即被弹出栈外并被销毁。CrimeListActivity继而被从新启动并恢复运行。应用的回退栈如图10-4所示。
图10-4 CriminalIntent应用的回退栈
CrimeListActivity恢复运行状态后,操做系统会向它发出调用onResume()生命周期方法的指令。CrimeListActivity接到指令后,它的FragmentManager会调用当前被activity托管的fragment的onResume()方法。这里,CrimeListFragment即惟一的目标fragment。
在CrimeListFragment中,覆盖onResume()方法刷新显示列表项,如代码清单10-8所示。
代码清单10-8 在onResume()方法中刷新列表项(CrimeListFragment.java)
为何选择覆盖onResume()方法来刷新列表项显示,而非onStart()方法呢?当一个activity位于咱们的activity以前时,咱们没法保证本身的activity是否会被中止。如前面的activity是透明的,则咱们的activity可能只会被暂停。对于此场景下暂停的activity,onStart()方法中的更新代码是不会起做用的。通常来讲,要保证fragment视图获得刷新,在onResume()方法内更新代码是最安全的选择。
运行CriminalIntent应用。选择某个crime项并修改其明细内容。而后返回到列表项界面,如预期那样,列表项当即刷新反映了更改的内容。
通过前两章的开发,CriminalIntent应用已得到大幅更新。如今,咱们来看看更新后的应用对象图解,如图10-5所示。
图10-5 应用对象图解更新版