第 10 章 使用 fragment argument

请参考教材,全面理解和完成本章节内容... ...

复制工程ch9,将工程目录改名为ch10。

本章,我们将实现CriminalIntent「陋习手记」应用的列表与明细部分的关联。用户点击某个「陋习」crime列表项时,会生成一个负责托管CrimeFragment的CrimeActivity,并显示出某特定Crime实例的明细信息。如图10-1所示。

image

图10-1 从CrimeListActivity中启动CrimeActivity

10.1 从 fragment 中启动 activity

从fragment中启动activity的实现方式,基本等同于从activity中启动另一activity的实现方式。我们调用Fragment.startActivity(Intent)方法,该方法在后台会调用对应的Activity方法。

CrimeListFragmentonListItemClick(...)实现方法里,用启动CrimeActivity实例的代码,替换日志记录crime标题的代码,如代码清单10-1所示。(暂时忽略Crime变量未使用的提示信息,下一节会使用它)

代码清单10-1 启动CrimeActivity(CrimeListFragment.java)

image

以上代码中,指定要启动的activity为CrimeActivityCrimeListFragment创建了一个显式intent。在CrimeListFragment中使用getActivity()方法传入它的托管activity,此activityIntent构造方法需要的Context对象。

运行CriminalIntent应用。点击任意列表项,屏幕上会出现一个托管CrimeFragmentCrimeActivity,如图10-2所示。

image

图10-2 空白的CrimeFragment

由于不知道该显示哪个Crime对象的信息,CrimeFragment也就没有显示出特定Crime对象的数据信息。

10.1.1 附加extra信息

通过将mCrimeId值附加到Intent的extra上,我们可以告知CrimeFragment应显示的Crime。在onListItemClick(...)方法中,将用户所选CrimemCrimeId值附加到用来启动CrimeActivity的intent上。输入代码清单10-2所示代码,IDE会报告一个错误信息,这是因为CrimeFragment.EXTRA_CRIME_ID的key值还没有创建。暂时忽略该条错误信息,稍后我们会创建它。

代码清单10-2 启动附加extra的CrimeActivity(CrimeListFragment.java)

image

创建了显式intent后,调用putExtra( )方法,传入匹配mCrimeId的字符串key与key值,完成extra信息的准备。这里,由于UUIDSerializable对象,我们调用了可接受Serializable对象的putExtra( )方法,即putExtra(String, Serializable)方法。

10.1.2 获取extra信息

简单获取extra的方法是,返回至CrimeFragment类,为extra添加key。然后,在onCreate(Bundle)方法中,得到CrimeActivity的intent内的extra信息后,再使用它获取Crime对象,如代码清单10-3所示。

代码清单10-3 获取extra信息并取得Crime对象(CrimeFragment.java)

image

在代码清单10-3中,除了getActivity()方法的调用,获取extra数据的实现代码与activity里获取extra数据的代码一样。getIntent()方法返回用来启动CrimeActivityIntent。然后调用IntentgetSerializableExtra(String)方法获取UUID并存入变量中。

取得Crime的ID后,利用该ID从CrimeLab单例中调取Crime对象。使用CrimeLab.get(...)方法需要Context对象,因此CrimeFragment传入了CrimeActivity

10.1.3 使用Crime数据更新CrimeFragment视图

既然CrimeFragment获取了Crime对象,它的视图便可显示该Crime对象的数据。参照代码清单10-4,更新onCreateView(...)方法,显示Crime对象的标题及解决状态。(显示日期的代码早已就绪)

代码清单10-4 更新视图对象(CrimeFragment.java)

image
运行CriminalIntent应用。选中Crime #4,查看显示了正确crime数据信息的CrimeFragment实例,如图10-3所示。
image

图10-3 Crime #4列表项的明细内容

10.1.4 直接获取extra信息方式的缺点

只需几行简单的代码,就可实现让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

10.2  fragment argument

每个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);

10.2.1 附加argumentfragment

附加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)

image

现在,当CrimeActivity创建CrimeFragment时,应调用CrimeFragment.newInstance(UUID)方法,并传入从它的extra中获取的UUID参数值。回到CrimeActivity类中,在createFragment()方法里,从CrimeActivity的intent中获取extra数据信息,并将之传入CrimeFragment.newInstance(UUID)方法,如代码清单10-6所示。

代码清单10-6 使用newInstance(UUID)方法(CrimeActivity.java)

image

注意,交互的activity和fragment不需要也无法同时保持通用独立性。CrimeActivity必须了解CrimeFragment的内部细节,比如知晓它内部有一个newInstance(UUID)方法。这很正常。托管activity就应该知道有关托管fragment方法的细节,但fragment则不必知道其托管activity的细节问题。至少在需要保持fragment通用独立性的时候是如此。

10.2.2 获取argument

fragment在需要获取它的argument时,会先调用Fragment类的getArguments()方法,接着再调用Bundle的限定类型的“get”方法,如getSerializable(...)方法。

现在回到CrimeFragment.onCreate(...)方法中,调整代码,改为从fragment的argument中获取UUID,如代码清单10-7所示。

代码清单10-7 从argument中获取crime ID(CrimeFragment.java)

image

运行CriminalIntent应用。虽然运行结果仍与之前一致,但我们应该感到由衷地高兴。因为我们不仅保持了CrimeFragment类的独立性,又为下一章实现CriminalIntent应用更为复杂的列表项导航打下了良好基础。

10.3 重新加载显示列表项

运行CriminalIntent应用,点击某个列表项,然后修改对应的Crime明细信息。这些修改的数据被保存至模型层,但返回列表后,列表视图并没有发生改变。下面我们来处理这个问题。

如模型层保存的数据发生改变(或可能发生改变),应通知列表视图的adapter,以便其及时获取最新数据并重新加载显示列表项。在适当的时点,与系统的ActivityManager回退栈协同运作,可以完成列表项的刷新。

CrimeListFragment启动CrimeActivity实例后,CrimeActivity被置于回退栈顶。这导致原先处于栈顶的CrimeListActivity实例被暂停并停止。

用户点击后退键返回到列表项界面,CrimeActivity随即被弹出栈外并被销毁。CrimeListActivity继而被重新启动并恢复运行。应用的回退栈如图10-4所示。

image

图10-4 CriminalIntent应用的回退栈

CrimeListActivity恢复运行状态后,操作系统会向它发出调用onResume()生命周期方法的指令。CrimeListActivity接到指令后,它的FragmentManager会调用当前被activity托管的fragment的onResume()方法。这里,CrimeListFragment即唯一的目标fragment。

在CrimeListFragment中,覆盖onResume()方法刷新显示列表项,如代码清单10-8所示。

代码清单10-8 在onResume()方法中刷新列表项(CrimeListFragment.java)

image

为什么选择覆盖onResume()方法来刷新列表项显示,而非onStart()方法呢?当一个activity位于我们的activity之前时,我们无法保证自己的activity是否会被停止。如前面的activity是透明的,则我们的activity可能只会被暂停。对于此场景下暂停的activity,onStart()方法中的更新代码是不会起作用的。一般来说,要保证fragment视图得到刷新,在onResume()方法内更新代码是最安全的选择。

运行CriminalIntent应用。选择某个crime项并修改其明细内容。然后返回到列表项界面,如预期那样,列表项立即刷新反映了更改的内容。

经过前两章的开发,CriminalIntent应用已获得大幅更新。现在,我们来看看更新后的应用对象图解,如图10-5所示。

image

图10-5 应用对象图解更新版

posted @ 2015-08-23 20:56  jlxuqiang  阅读(861)  评论(0编辑  收藏  举报