Fragment - 图1

1. 什么是Fragment?

可以简单的理解为,Fragment是显示在Activit中的Activity。它可以显示在Activity中,然后它也可以显示出一些内容。因为它拥有自己的生命周期,可以接受处理用户事件,并且你可以在一个Activity中动态的添加、替换、移除不同的Fragment,因此对于信息的展示具有很大的便利性。

2. Fragment生命周期

因为Fragment是依附于Activity存在的,因此它的生命周期收到Activity的生命周期影响。fragment_lifecycle.png fragment_lifecycle2.png
Fragment比Activity多了几个生命周期的回调方法

  • onAttact(Activity) 当Fragment与Activity发生关联的时候调用。
  • onCreateView(LayoutInflater,ViewGroup,Bundle)创建该Fragmant的视图。
  • onActivityCreateed(Bundle) 对Activity的onCreate方法返回时调用。
  • onDestroyView()与onCreateView()方法相对应,当Fragment的视图被移除时调用。
  • onDetach()与onAttach()方法相对应,当Fragment与Activity取消关联时调用。

注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对应方法的实现。

3. Fragment的使用方式

3.1. 静态使用

① 创建一个类继承Fragment,重写OonCreateView方法,并确定Fragment显示的布局。
② 在Activity中申明该类,与普通的View对象一样。

  1. public class MyFragment extends Fragemnt{
  2. public View onCreateView(LayoutInflater inflater,ViewGroup
  3. container,Bundle savedInstanceState){
  4. /**
  5. * 参数1:布局文件的id
  6. * 参数2:容器
  7. * 参数3:是否将这个生成的View添加到这个容器中去
  8. * 作用是将布局文件封装在一个View对象中,并填充到此Fragment中
  9. **/
  10. View v = inflater.inflate(R.layout.item_fragment, contai ner, false);
  11. return v;
  12. }
  13. }

③ Activity对应的布局文件中使用

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/ android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical"
  6. tools:context="com.usher.fragment.MainActivity">
  7. <TextView
  8. android:layout_width="match_parent"
  9. android:layout_height="100dp"
  10. android:gravity="center"
  11. android:text="Good Boy" />
  12. <fragment
  13. android:id="@+id/myfragment"
  14. android:name="com.usher.fragment.MyFragment"
  15. android:layout_width="match_parent"
  16. android:layout_height="match_parent" />
  17. </LinearLayout>

3.2 动态使用

核心代码

  1. // 初始化FragmentManager对象
  2. FragmentManager fragmentManager = getSupportFragmentManager();
  3. // 使用FragmentManager对象用来开启一个Fragment事务
  4. FragmentTransaction transaction = fragmentManager.beginTransaction();
  5. // 显示FirstFragment
  6. transaction.add(R.id.fl_content, new FirstFragment()).commit();
  7. // 切换显示
  8. transaction.replace(R.id.myframelayout, fragment 1).commit();
  • transaction.add() 向Activity中添加一个Fragment
  • transaction.remove() 从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁
  • transaction.replace() 使用另一个Fragment替换当前的,实际上就是remove() 然后add()的合体
  • transaction.hide() 隐藏当前的Fragment,仅仅是设为不可见,并不会销毁
  • transaction.show() 显示之前隐藏的Fragment
  • detach() 会将view从UI中移除,和remove()不同,此时fragment的状态依然由 FragmentManager维护
  • attach() 重建view视图,附加到UI上并显示
  • ransatcion.commit() 提交事务

在add/replace/hide/show以后都要commit其效果才会在屏幕上显示出来

4. 什么是Fragment的回退栈

Fragment的回退栈是用来保存每一次Fragment事务发生的变化,如果你将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次保存的Fragment。一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则会退出当前Activity。

5. Fragment与Activity之间的通信

Fragment依附于Activity存在,因此与Activity之间的通信可以归纳为以下几点:

  • 如果Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment

的public方法。

  • 如果Activity中未保存任何Fragment的引用,那么每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者getFragmentManager.findFragementById()获得任何Fragment示例,然后进行操作。
  • Fragment中可以通过getActicity()得到当前绑定的Activity的实例,然后进行操作。

6. Fragment与Activity通信的优化

因为要考虑Fragment的复用,所以必须降低Fragment与Activity之间的耦合,而且Fragment更不应该直接操作别的Fragment,毕竟Fragment操作应该由它的管理者Activity来决定。
虽然Fragment和Activity可以通过getActivity 与findFragmentByTag或者findFragmentById,进行任何操作,甚至在Fragment里面操作另外的Fragment,但是没有特殊理由是绝对不提倡的。Activity担任的是Fragment间类似总线一样的角色,应当由它决定Fragment如何操作。另外虽然Fragment不能响应Intent打开,但是Activity可以,Activity可以接收Intent,然后根据参数判断显示哪个Fragment。

7. 如何处理运行时配置发生变化(Fragment 重叠问题)

在Activity的学习中我们都知道,当屏幕旋转时,是对屏幕上的视图进行了重绘。因为当屏幕发生旋转,Activity发生重新启动,默认的Activity中的Fragment也会跟着Activity重新创建。所以,不断的旋转就会不断的绘制,这是一种很消耗内存资源的操作,那么如何来进行优化?
通过检查onCreate的参数Bundle savedInstanceState就可以判断,当前是否发生Activity的重新创建。默认的savedInstanceState会存储一些数据,包括Fragment的实例。所以,我们简单修改下代码,判断只有在savedInstanceState==null时,才进行创建Fragment实例。

8. onActivityResult()

Fragment 类提供了 startActivityForResult 方法用于 Activity 间的页面跳转和数据回传,其实内部也是调用了 Activity 的对应方法,但是在页面返回时 Fragment 没有提供 setResult 方法,但是可以通过拿宿主 Activity 实现。
但是仍要注意的一点是,嵌套的 Fragment 需要一级一级的分发。

9. 源码分析

9.1 FragmentManager

我们通过在 Activity 在 getSupportFragmentManager 获取的 FragmentManager 其实是FragmentManagerImpl,它也是抽象类 FragmentManager 的唯一实现类。
FragmentManagerImpl 里面有两个重要方法:

  1. beginTransaction()
  1. public FragmentTransaction beginTransaction() {
  2. return new BackStackRecord(this);
  3. }

这里 BackStackRecord 也是抽象类 FragmentTransaction 的唯一实现。

  1. popBackStack()
  1. public void popBackStack() {
  2. enqueueAction(new PopBackStackState(null, -1, 0), false);
  3. }
  4. public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
  5. if (!allowStateLoss) {
  6. checkStateLoss();
  7. }
  8. synchronized (this) {
  9. if (mPendingActions == null) {
  10. mPendingActions = new ArrayList<>();
  11. }
  12. mPendingActions.add(action);
  13. scheduleCommit();
  14. }
  15. }

9.2 BackStackRecord

  1. final class BackStackRecord extends FragmentTransaction implements
  2. FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {
  3. final FragmentManagerImpl mManager;
  4. // 构造 Op 的标识位
  5. static final int OP_NULL = 0;
  6. static final int OP_ADD = 1;
  7. static final int OP_REPLACE = 2;
  8. static final int OP_REMOVE = 3;
  9. //...
  10. static final class Op {
  11. //add、replace、remove 标志位
  12. int cmd;
  13. Fragment fragment;
  14. //进入退出动画
  15. int enterAnim;
  16. int exitAnim;
  17. int popEnterAnim;
  18. int popExitAnim;
  19. Op() {
  20. }
  21. Op(int cmd, Fragment fragment) {
  22. this.cmd = cmd;
  23. this.fragment = fragment;
  24. }
  25. }
  26. //操作集合
  27. ArrayList<Op> mOps = new ArrayList<>();
  28. }

我们通过 FragmentTransaction add Fragment 时:

  1. public FragmentTransaction add(int containerViewId, Fragment fragment){
  2. doAddOp(containerViewId, fragment, null, OP_ADD);
  3. return this;
  4. }
  5. private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
  6. fragment.mFragmentManager = mManager;
  7. addOp(new Op(opcmd, fragment));
  8. }
  9. void addOp(Op op) {
  10. mOps.add(op);
  11. op.enterAnim = mEnterAnim;
  12. op.exitAnim = mExitAnim;
  13. op.popEnterAnim = mPopEnterAnim;
  14. op.popExitAnim = mPopExitAnim;
  15. }