前言
我们之前的应用实现了从 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
为CrimeActivity
CrimeListFragment
创建了一个显式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()
) 一揽子解决这些问题,以后看起来就会清晰很多了。
- 我们总是需要把其他