Fragment 简介
Fragment (简称碎片)是 Android 3.0(API 11)提出的。为了兼容低版本 support-v4 库中也开发了一套Fragment API 最低兼容到 Android 1.6 的版本。
目录
模块化
Fragment 允许您将界面划分为离散的区块,从而将模块化和可重用性引入 Activity 的界面。Activity 是围绕应用的界面放置全局元素(如抽屉式导航栏)的理想位置。相反,Fragment 更适合定义和管理单个屏幕或部分屏幕的界面。
假设有一个响应各种屏幕尺寸的应用。在较大的屏幕上,该应用应显示一个静态抽屉式导航栏和一个采用网格布局的列表。在较小的屏幕上,该应用应显示一个底部导航栏和一个采用线性布局的列表。在 Activity 中管理所有这些变化因素可能会很麻烦。将导航元素与内容分离可使此过程更易于管理。然后,Activity 负责显示正确的导航界面,而 Fragment 采用适当的布局显示列表。
过去 support-v4 库是一个 jar 包,从 24.2.0 版本开始,将 support-v4 库模块化为多个 jar 包。包含 support-fragment、 support-ui、support-media-compat 等。这么做是为了减少 APK 包大小,项目中需要用哪个模块就引入哪个模块。
// 引入整个 support-v4 库 compile 'com.android.support:support-v4:24.2.1' //只引入 support-fragment 库 compile 'com.android.support:support-fragment:24.2.1'
因为 support 库是不断更新的,因此推荐使用 support 库中的 android.support.v4.app.Fragment
,而不要用系统自带的 android.app.Fragment
。如果使用 support 库的 Fragment,Activity 就必须要继承 FragmentActivity(AppCompatActivity 是 FragmentActivity 的子类)。
Fragment 的特点
-
Fragment 是依赖于 Activity 的,不能独立存在的。
-
一个 Activity 里可以有多个 Fragment。
-
一个 Fragment 可以被多个 Activity 重用。
-
Fragment 有自己的生命周期,并能接收输入事件。
-
可以在 Activity 运行时动态地添加或删除 Fragment。
Fragment 的优势
-
模块化(Modularity):我们不必把所有代码全部写在 Activity 中,可以把代码写在各自的 Fragment 中。
-
可重用(Reusability):多个 Activity 可以重用一个 Fragment。
-
可适配(Adaptability):根据硬件的屏幕尺寸、屏幕方向,能够方便地实现不同的布局,这样用户体验更好。
Fragment 生命周期
- onAttach:Fragment和Activity相关联时调用,可以通过该方法获取Activity引用。
- onCreate:Fragment被创建时调用。
- onCreateView:创建Fragment的布局。
- onActivityCreated:当关联的Activity完成onCreate方法后调用。
- onStart:当Fragment可见时调用。
- onResume:当Fragment可见且可交互时调用。
- onPause:当Fragment可见但不可交互时调用。
- onStop:当Fragment不可见且不可交互时调用。
- onDestroyView:当Fragment的视图结构从Fragment中移除时调用。
- onDestroy:销毁Fragment时调用。
- onDetach:移除与Activity时调用。
Fragment 的状态
Fragment状态与Activity类似,也存在如下4种状态:
运行:当前Fmgment位于前台,用户可见,可以获得焦点。
暂停:其他Activity位于前台,该Fragment依然可见,只是不能获得焦点。
停止:该Fragment不可见,失去焦点。
销毁:该Fragment被完全删除,或该Fragment所在的Activity被结束。
Fragment 使用方式
这里给出 Fragment 最基本的使用方式。首先,创建继承 Fragment 的类,名为 BlankFragment
public class BlankFragment extends Fragment { private static final String ARG_PARAM = "param_key"; private String mParam; public BlankFragment() { } public static BlankFragment newInstance(String param) { BlankFragment fragment = new BlankFragment(); Bundle args = new Bundle(); args.putString(ARG_PARAM, param); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { mParam = getArguments().getString(ARG_PARAM); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_blank, container, false); // View 初始化,findViewById() 等操作 return view; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // 初始化数据,加载数据等... } }
Fragment有两种使用方式,分别是静态和动态
静态添加
通过 xml 的方式添加,缺点是一旦添加就不能在运行时删除。
<fragment android:id="@+id/fg_content" android:layout_width="match_parent" android:layout_height="match_parent" android:name="com.jeanboy.text.ui.fragment.BlankFragment" />
动态添加
运行时添加,这种方式比较灵活,因此建议使用这种方式。
这里只给出动态添加的方式。首先 Activity 需要有一个容器存放 Fragment,一般是 FrameLayout,因此在 Activity 的布局文件中加入 FrameLayout:
<FrameLayout android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" />
然后在 onCreate()
中,通过以下代码将 Fragment 添加进Activity中。
getSupportFragmentManager().beginTransaction() .add(R.id.container, BlankFragment.newInstance("hello world"), "f1") .commit();
这里需要注意几点:
-
因为我们使用了support库的Fragment,因此需要使用
getSupportFragmentManager()
获取 FragmentManager。 -
add()
是对 Fragment 众多操作中的一种,还有remove()
,replace()
等。第一个参数是根容器的 id(FrameLayout 的 id,即
@id/container
),第二个参数是 Fragment 对象,第三个参数是 Fragment 的 tag 名,指定 tag 的好处是后续我们可以通过:Fragment1 frag = getSupportFragmentManager().findFragmentByTag("f1");
从 FragmentManager 中查找 Fragment 对象。
-
在一次事务中,可以做多个操作,比如同时做
add().remove().replace()
。 -
commit()
操作是异步的,内部通过mManager.enqueueAction()
加入处理队列。对应的同步方法为
commitNow()
,commit()
内部会有checkStateLoss()
操作,如果开发人员使用不当(比如commit()
操作在onSaveInstanceState()
之后),可能会抛出异常。而commitAllowingStateLoss()
方法则是不会抛出异常版本的commit()
方法,但是尽量使用commit()
,而不要使用commitAllowingStateLoss()
。 -
addToBackStack("fname")
是可选的。FragmentManager 拥有回退栈(BackStack),类似于 Activity 的任务栈,如果添加了该语句,就把该事务加入回退栈,当用户点击返回按钮,会回退该事务(回退指的是如果事务是
add(frag1)
,那么回退操作就是remove(frag1)
);如果没添加该语句,用户点击返回按钮会直接销毁 Activity。
FragmentManager
Fragment的状态保存入口有三个:
-
Activity的状态保存, 在Activity的
onSaveInstanceState()
里, 调用了FragmentManger的saveAllState()
方法, 其中会对mActive中各个Fragment的实例状态和View状态分别进行保存. -
FragmentManager还提供了public方法:
saveFragmentInstanceState()
, 可以对单个Fragment进行状态保存, 这是提供给我们用的, 后面会有例子介绍这个. 其中调用的saveFragmentBasicState()
方法即为情况一中所用, 图中已画出标记. -
FragmentManager的
moveToState()
方法中, 当状态回退到ACTIVITY_CREATED
, 会调用saveFragmentViewState()
方法, 保存View的状态.
moveToState()
方法中有很长的switch case, 中间不带break, 基本是根据新状态和当前状态的比较, 分为正向创建和反向销毁两个方向, 一路沿着多个case走下去.
Fragment状态恢复入口:
三个恢复的入口和三个保存的入口刚好对应.
-
在Activity重新创建的时候, 恢复所有的Fragment状态.
-
如果调用了FragmentManager的方法:
saveFragmentInstanceState()
, 返回值得到的状态可以用Fragment的setInitialSavedState()
方法设置给新的Fragment实例, 作为初始状态. -
FragmentManager的
moveToState()
方法中, 当状态正向创建到CREATED
时, Fragment自己会恢复View的状态.
这三个入口分别对应的情况是:
入口1对应系统销毁和重建新实例. 入口2对应用户自定义销毁和创建新Fragment实例的状态传递. 入口3对应同一Fragment实例自身的View状态重建.
Fragment 通信
Fragment 向 Activity 传递数据
首先,在 Fragment中 定义接口,并让 Activity 实现该接口。
public interface OnFragmentCallback { void onCallback(String value); }
在 Fragment 的 onAttach()
中,将参数 Context 强转为 OnFragmentCallback 对象:
@Override public void onAttach(Context context) { super.onAttach(context); if (context instanceof OnFragmentCallback) { callback = (OnFragmentCallback) context; } else { throw new RuntimeException(context.toString() + " must implement OnFragmentCallback"); } }
Activity 向 Fragment 传递数据
Activity 向 Fragment 传递数据比较简单,获取 Fragment 对象,并调用 Fragment 的方法即可。比如要将一个字符串传递给 Fragment,则在 Fragment 中定义方法:
public void setString(String data) { this.data = data; }
并在 Activity 中调用 fragment.setString("hello")
即可。
Fragment 之间通信
由于 Fragment 之间是没有任何依赖关系的,因此如果要进行 Fragment 之间的通信,建议通过 Activity 作为中介,不要 Fragment 之间直接通信。
DialogFragment
DialogFragment 是 Android 3.0 提出的,代替了 Dialog,用于实现对话框。它的优点是:即使旋转屏幕,也能保留对话框状态。
如果要自定义对话框样式,只需要继承 DialogFragment,并重写 onCreateView()
,该方法返回对话框 UI。这里我们举个例子,实现进度条样式的圆角对话框。
public class ProgressDialogFragment extends DialogFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { //消除Title区域 getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); //将背景变为透明 getDialog().getWindow() .setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); //点击外部不可取消 setCancelable(false); View root = inflater.inflate(R.layout.fragment_progress_dialog, container); return root; } public static ProgressDialogFragment newInstance() { return new ProgressDialogFragment(); } }
然后通过下面代码显示对话框:
ProgressDialogFragment fragment = ProgressDialogFragment.newInstance(); fragment.show(getSupportFragmentManager(), "tag");//显示对话框 fragment.dismiss();//关闭对话框