前言
我们之前的应用实现了从 activity 中启动 activity 的功能。现在我们要实现从 fragment 中启动 activity。在本例中,就是从CrimeListFragment 中启动 CrimeActivity 实例。

在本例中,我们将关联 CriminalIntent 应用的列表与明细部分。用户点击某个 Cirme 列表项时,会创建一个托管 CrimeFragment 的CrimeActivity。
10.1 从 fragment 中启动activity
- 要从
fragment中启动activity,类似于activity中启动activity- 我们调用
Fragment.startActivity(Intent)方法 - 由它在后台再调用对应的
Activity方法
- 我们调用
本例中,我们将在 CrimeListFragment 的CrimeHolder类里,用启动 CrimeActivity 实例的代码,替换 toast 消息处理代码。
启动CrimeActivity(CrimeListFragment.java):
1 | private class CrimeHolder extends RecyclerView.ViewHolder |
上述代码和 activity 启动 activity 相当类似。
- 指定要启动的
activity为CrimeActivityCrimeListFragment创建了一个显式itent
CrimeListFragment通过getActivity()方法传入他托管的activity来获取构造Intent所需要的Context对象
附加 extra 信息
为了实现能分辨是哪一个 fragmen 启动的 activity,我们需要在启动CrimeActivity 时,传递附加到 Intent extra 上的crime ID。
- 需要使用
CrimeActivity中新增newIntent
创建 newIntent 方法(CrimeActivity.java):
1 | public class CrimeActivity extends SingleFragmentActivity { |
- 创建了显式
intent后,调用putExtra(...)方法,传入匹配的crimeId字符串键与键值- 此处
UUID是Serializable对象,所以调用putExtra(String, Serializable)方法
- 此处
注意,上面我们是在 CtimreActivity 中编写的代码,还记得我们之前学习 Intent 的时候说过的知识点吗?
如何在使用
intent并传递数据的时候,不考虑传递数据的细节和有效提高复用性?
啊哈!就是这里啦。我们在被调用的 activity 内创建静态方法newIntent,让这个被调用者自己把控要传入的数据类型和个数,让调用者们直接调用而不需考虑太多;还可以提高复用性呢!
接下来看看我们如何使用 newIntent 方法吧。
传递 Crime 实例(CrimeListFragment.java):
1 | private class CrimeHolder extends RecyclerView.ViewHolder |
wow! 简直绝赞!
获取 extra 信息
我们已经把 crimeId 信息安全地存储到 CrimeActivity 的intent中了。不过,要使用和获取 extra 信息的是 CrimeFragment 类。
fragment有两种方法获取 intent 中的数据:
- 简单直接的方法
CrimeFragment直接使用getActivity()方法获取CrimeActivity的intent- 回到
CrimeFragment.java文件,取到CrimeActivity的intent内的extra信息 - 再用它获取
Crime对象
1 | public class CrimeFragment extends Fragment { |
我们需要理解的是,CrimeFragment是由 CrimeActivity 创建出来的。
所以其使用 getActivity() 就可以获取到 CrimeActivity 实例。接着通过该实例获取 intent,由intent 里的 crimeId 来从 CrimeLab 单例中获取特定 Crime 对象。
使用 Crime 数据更新 CrimeFragment 视图
获取了 Crime 对象,CrimeFragment视图便可以显示该 Crime 对象的数据了。
我们需要更新 onCreateView(...) 方法,显示 Crime 对象的标题以及解决状态。
更新视图对象(CrimeFragment.java):
1 |
|
直接获取 extra 信息的缺点
在上述方式中,我们简单使用几行代码,便可以让 fragment 直接从托管 activity 的intent中获取信息。
然而,这样的方式破坏了 fragment 的封装。
CrimeFragment不再是可复用的构建单元- 因为它现在由某个特定的
activity托管着 - 该特定的
activity的Intent又定义了名为com.bignerdranch.android.criminalintent.crime_id的extra
- 因为它现在由某个特定的
CrimeFragment再也无法被其他activity使用
一个更好的做法,是将 crimeID 存储在属于 CrimeFragment 的某个地方,而不是保存在 CrimeActivity 的私有空间里。这样,无需依赖 CrimeActivity 的intent内的 extra,CrimeFragment 就能获取自己所需的 extra 数据。
一般,那个某个地方就是实际上就是 fragment 的argument bundle。
10.2 fragment argument
- 每一个
fragment实例都可以附带一个Bundle对象- 该
Bundle包含键值对,方便我们像Activity的Intent中那样使用它们 - 一个键值对就是一个
argument
- 该
创建 fragment argument 的方法:
- 创建
Bundle对象 - 使用
Bundle限定类型的的put方法,将argument添加到bundle中
1 | Bundle args = new Bundle(); |
附加 argument 到fragment
- 调用
Fragment.setArguement(Bundle)方法来附加argument bundle给fragment- 附加时机需要在
fragment创建后,添加给acticity之前 - 比如本例子中,我们使用的是
activity继承SingleFragmentActivity.java来创建具体fragment的方法;那么你查看一下SingleFragmentActivity就知道,内部先调用activity实现的createFragment(),然后才在FragmentManager事务中使用add操作添加给activity
- 附加时机需要在
- 合适的做法:
newInstance()- 添加一个名为
newInstance()的静态方法给Fragment类 - 使用该方法完成
fragment实例以及Bundle对象的创建 - 然后将
argument放入bundle中 - 最后附加给
fragment
- 添加一个名为
我们之前需要使用 fragment 实例时,使用 activity调用 Fragment 构造方法。现在我们转而调用 newInstance() 方法,既可以创建 fragment 实例,activity又可以给 newInstance() 方法传入任何需要的参数。
CrimeFragment.java
1 | public static CrimeFragment newInstance(UUID crimeId) { |
这里:
- 创建了一个属于
CrimeFrgament.java的静态方法newInstance() - 在里面完成了附加
argument到fragment的一系列工作
使用:
CrimeActivity调用CrimeFragment.newInstance(UUID)并传入从它的extra获取的UUID参数值
回到 CrimeActivity 类中,在 createFragment() 方法里,从 CrimeActivity 的intent中获取 extra 数据:
CrimeActivity.java
1 |
|
这里:
- 托管
activity知道CrimeFragment内部细节,这是必须的 fragment不一定需要知道activity内部细节,特别是保持fragment通用独立的时候
获取argument
fragment要获取argument,会先调用Fragment类的getArgument()方法- 再调用
Bundle限定类型的get方法
CrimeFragment.java
1 |
|
10.3 刷新显示列表项
我们进入了具体列表项,然后修改数据(或者本身进入的这个行为已经是一种数据),退出去之后需要更新列表项以反映出变化。
常见的例子,比如点击过的列表项,先改变其颜色来表示已访问;修改列表项中的某些状态,比如本例子,那就要在列表中反映出来。
- 模型层保存的数据若有变化,应该通知
RecyclerView的Adapter,以便其及时获取最新数据并刷新显示列表项- 在恰当的时机,与系统的
ActicityManager回退栈协同运作 ,可实现列表项的刷新功能
- 在恰当的时机,与系统的
本例子中的回退栈流程为:
CrimeListFragment启动CrimeActivity实例后CrimeActivity被置于回退栈顶CrimeListActiciy实例被暂停并停止
- 用户点击后退键回到列表项界面
CrimeActivity随机弹出栈并被销毁CrimeListActivity立即重新启动并恢复运行
CrimeListActivity恢复运行之后,操作系统发出调用onResume()生命周期方法的指令CrimeListActivity接到指令之后 ,其FragmentManager调用当前被activity托管的fragment的onResume()方法- 本例中为
CrimeListFragment
- 本例中为
- 在
CrimeListFragment中,覆盖onResume()方法,触发调用updateUI()方法刷新显示列表项- 如果已配置好
CrimeAdapter,就调用notifyDataSetChange()方法来修改updateUI()方法
- 如果已配置好
CrimeListFragemnt.java
1 | ... |
10.4 通过 fragment 获取返回结果
记得我们之前使用 intent 的时候吗?传递完数据之后,我们可以在 Activity.startActivityForResult(...) 方法中获得返回结果。
对比着来看的话,fragment argument也是同样的道理哦。
- 调用
fragment.startActivityResult(...) - 覆盖
fragment.startActivityResult(...)方法
CrimeListFragment.java
1 |
|
- 结果处理
fragment能够从activity中接受返回结果,但是 其本身无法持有返回结果- 只有
activity拥有返回结果
- 只有
fragment没有setResult()方法
我们应该让托管 activity 返回结果值:
1 | public class CrimeFragment extends Fragment { |
注意:结果处理指的是接受数据的 activity 和fragment来完成的步骤,本例中指的是被启动的 CrimeFragment.java 哦。
10.5 深入学习:为何使用 fragment argument
直接在 CrimeFragment 里创建一个实例变量有什么坏处吗?
在本例中:在 CrimeFragment.java 中创建一个实例变量 CrimeID,然后CrimeListFragment 通过 setCrimeID() 方法来传递数据。这样做的缺点:
- 操作系统再重建
fragment时,用户暂时离开当前 应用(系统按需回收内存),任何实例变量都将不复存在- 尤其是内存不够的时候,操作系统强制杀掉应用
那么实例状态保存机制呢?
本例中:在 CrimeFragment.java, 将crimeID 赋值给实例变量,然后在 onSaveInstanceState(Bundle) 方法中保存下来;要使用时再从 onCreate(Bundle) 方法中的 Bundle 中取回
- 维护成本高
- 我们总是需要把其他
fragment的argument添加到onSaveInstanceState(Bundle)里面,这样导致代码结构不清晰,不利于理解代码 - 与之相比,在专门的方法 (
newInstance()) 一揽子解决这些问题,以后看起来就会清晰很多了。
- 我们总是需要把其他