阅读 938

再谈Fragment

参考:

声明

Fragment 就是一个 Activity 布局的一部分,可以把 Activity 的一部分布局抽离出来到 Fragment 中,并且 Fragment 也可以执行逻辑。就是把 Activity 复杂的内容抽离成几个碎片,然后拼凑起来,在 Activity 布局中,只需要引入各个不同的 Fragment 就可以 了。

比如我们常见的布局,底部导航,然后每个模块对应不同的 Fragment。其实也是完全可以不使用 Fragment 的,一个页面中包含各个模块的布局,然后通过点击导航来决定隐藏哪些模块。这样写的话一个 Activity 中的代码逻辑会非常多,所有模块都融合到一个 Activity 中了,相当冗余,耦合。如果用 Fragment 就灵活多了,Activity 只需要根据导航显示对应的 Fragment 就可以了。

如果别的地方需要某一个模块,直接拿出对应的 Fragment 就可以了。如果都写在 Activity 中那抽离出来就费劲了,也增加了不确定性。

再比如一个例子:网易新闻 手机端 和平板端。

平板端是新闻的标题和详情都在一个页面中。手机是详情在单独的一个页面。这样我们就可以将 标题 和 详情写成两个 Fragment。充分利用了。

Fragment 相关的三个类

  • Fragment

    具体的 Fragment

  • FragmentManager

    是管理 Fragment 的

  • FragmentTransaction

    通过事务来进行添加 Fragment、隐藏、移除 等操作 Fragment 动作,事务保证了原子性

主要方法

  • transaction.add()

    向 Activity 中添加 Fragment,只是添加 Fragment,不影响之前 Fragment 的声明周期,这一点和 Activity 不一样,Activity 会因为新 Activity 而走生命周期。而 .add 不会。假如 FragmentA 已经在 Activity 中了,这个时候启动了 FragmentB,A 的生命周期是不会有任何变化的,还是处于 onResume ,并不会因为 B 处于 onResume 而有影响。

  • transaction.remove()

    从 Activity 中移除一个 Fragment 。如果这个 Fragment 没有被添加到回退栈中,则实例被销毁。如果添加到回退栈了,会执行 onDestroyView 实例并不会被销毁。

  • transaction.replace()

    使用 Fragment 替换当前,相当于先移除之前的 Fragment(remove()) 然后再 add()

  • transaction.hide()

    隐藏当前 Fragment。仅仅是设为不可见。不会销毁

  • transaction.show()

    显示之前隐藏的 Fragment

  • detach()

    会将 view 从 UI 中移除。和 remove 不同的是,此时 Fragment的状态依然由 FragmentManager 维护

  • attach()

    重建 view 视图,附加到UI上显示

  • .addToBackStack()

    是将事务放入回退栈,可以认为一个事务中可以有多个 Fragment .addToBackStack() 做的是将这个事务放入回退栈。

    类似于我们每添加一个 Activity 都会默认放入回退栈中,如果想要 Fragment 放入回退栈中,那么就需要借助启动 Fragment 的事务了,调用事务的 .addToBackStack() 方法。

    默认情况下,按下返回键的时候的操作是从回退栈中弹出一个事务。

    按下返回键的时候,是从返回栈中弹出一个事务(Fragment)。显示栈顶的事务(对应的 Fragment)

    如果当前栈顶的Fragment 已经执行过 destroyView 了,则会重新执行生命周期(从 createView 开始)

    如果任务栈中只有一个Fragment,则此时按下返回键,进行的操作是把这个 Fragment 销毁。

    在 Fragment A 中启动另一个 Fragment B 这个时候 .addToBackStack() 是将 A 加入栈中。

基本使用

静态添加

// 直接在 xml 中添加
    <fragment
        android:id="@+id/fragment"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:name="com.syd.good.feature.fragment.fragment.FragmentMy" />
复制代码

在代码中获取的这个 Fragment

通过

 Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment);
复制代码

注意静态添加 Fragment 的生命周期

E/FragmentMy: onAttach
E/FragmentMy: onCreate
E/FragmentMy: onCreateView
E/FragmentMy: onViewCreated
E/FragmentStaticActivity: onCreate()
E/FragmentMy: onActivityCreated
E/FragmentMy: onStart
E/FragmentStaticActivity: onStart()
E/FragmentStaticActivity: onResume()
E/FragmentMy: onResume
// 停止
E/FragmentMy: onPause
E/FragmentStaticActivity: onPause()
E/FragmentMy: onStop
E/FragmentStaticActivity: onStop()
E/FragmentMy: onDestroyView
E/FragmentMy: onDestroy
E/FragmentMy: onDetach()
E/FragmentStaticActivity: onDestroy()
复制代码

动态添加

动态添加,一般都会选择加载到 FrameLayout 布局上,是因为 FrameLayout 布局足够简单,可以减少不必要的布局加载。

Fragment 的布局只是附着在 FrameLayout 上,作为 FrameLayout 的 子 View ,并没有取代 FrameLayout

<FrameLayout
        android:id="@+id/fl"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
复制代码
getSupportFragmentManager().beginTransaction()
                .add(R.id.fl, new FragmentOne())
   				.add(R.id.fl, new FragmentTwo())
                .commit();
复制代码

Fragment 无论是否是全屏还是一半的,执行生命周期都一样

执行结果:

E/FragmentOne: onAttach
E/FragmentOne: onCreate
E/FragmentTwo: onAttach()
E/FragmentTwo: onCreate()
E/FragmentOne: onCreateView
E/FragmentOne: onViewCreated
E/FragmentOne: onActivityCreated
E/FragmentOne: onStart
E/FragmentOne: onResume
E/FragmentTwo: onCreateView
E/FragmentTwo: onActivityCreated
E/FragmentTwo: onStart
E/FragmentTwo: onResume
复制代码

返回

E/FragmentOne: onPause
E/FragmentTwo: onPause
E/FragmentOne: onStop
E/FragmentTwo: onStop
E/FragmentOne: onDestroyView
E/FragmentOne: onDestroy
E/FragmentOne: onDetach()
E/FragmentTwo: onDestroyView
E/FragmentTwo: onDestroy
E/FragmentTwo: onDetach()
复制代码

onHiddenChanged() 和普通的生命周期没有关系,只是调用 hidden 的时候会调用而已。

通过上面 .add 的方式添加 Fragment 只是将 Fragment 添加到指定的布局中,之后再添加 Fragment 是互相不影响的,在布局上看 FrameLayout 有两个子View(FragmentOne、FragmentTwo) 相互重叠。

关于.addToBackStack() 例子

例子1

主 Activity 中一次启动两个 Fragment ,并将这个事务加入到回退栈中。

getSupportFragmentManager().beginTransaction()
        .add(R.id.fl, new FragmentOne())
        .add(R.id.fl, new FragmentTwo())
        .addToBackStack(null)
        .commit();
复制代码

这个时候按下返回键的结果是,从回退栈中弹出这个事务,意味这销毁这两个 Fragment

执行结果

Fragment 从 Activity 中脱离,留下 Activity

E/FragmentOne: onAttach
E/FragmentOne: onCreate
E/FragmentTwo: onAttach()
E/FragmentTwo: onCreate()
E/FragmentOne: onCreateView
E/FragmentOne: onViewCreated
E/FragmentOne: onActivityCreated
E/FragmentOne: onStart
E/FragmentOne: onResume
E/FragmentTwo: onCreateView
E/FragmentTwo: onActivityCreated
E/FragmentTwo: onStart
E/FragmentTwo: onResume
// 按下返回键
E/FragmentTwo: onPause
E/FragmentTwo: onStop
E/FragmentTwo: onDestroyView
E/FragmentTwo: onDestroy
E/FragmentTwo: onDetach()
E/FragmentOne: onPause
E/FragmentOne: onStop
E/FragmentOne: onDestroyView
E/FragmentOne: onDestroy
E/FragmentOne: onDetach()    
复制代码

假如没有 addToBackStack() 的时候,按下返回键,则直接退出 Activity 了。

例子2

在上面的基础上,在启动的 FragmentTwo 中添加方法

        tvAdd.setOnClickListener((view1) -> {
            Log.e("add", "add");
            getFragmentManager().beginTransaction()
                    .add(R.id.fl, new FragmentThree())
                    .commit();
        });
复制代码

这个时候只是在 fl 中又添加了一个 FragmentThree ,并且这个事务没有加入到回退栈中,这个时候按下返回键,结果是:

页面没有发生任何改变,但是上一次保存在回退栈的事务被弹出了

 E/FragmentTwo: onPause
 E/FragmentTwo: onStop
 E/FragmentTwo: onDestroyView
 E/FragmentTwo: onDestroy
 E/FragmentTwo: onDetach()
 E/FragmentOne: onPause
 E/FragmentOne: onStop
 E/FragmentOne: onDestroyView
 E/FragmentOne: onDestroy
 E/FragmentOne: onDetach()
复制代码

这个时候再按下返回键

Activity 就会被销毁。

例子3

在例子1的基础上,在添加 FragmentTwo 中添加方法

tvAdd.setOnClickListener((view1) -> {
    Log.e("add", "add");
    getFragmentManager().beginTransaction()
            .add(R.id.fl, new FragmentThree())
        // 添加了将此事务入栈
            .addToBackStack(null)
            .commit();
});
复制代码

这个时候再按下返回键,是将这个栈顶的事务退出,这个时候例子1 中的事务就处于栈顶了。页面显示的就是例子1中的页面了。

再次按下返回键,是把例子1中的事务弹出,这个时候留下的就只有 Activity 了,再次按下返回键,弹出 Activity。

与 Activity 通信

  • Activity 中有 Fragment 的引用,可以直接操作 Fragment 中的方法
  • 每个 Fragment 都有一个唯一的 TAG或者 ID 可以通过 getFragmentManager.findFragmentByTag() 或者 findFragmentById() 获取
  • Fragment 中通过 getActivity 获取当前绑定的 Activity 实例

Fragment 不应该直接操作其他的 Fragment。应该由管理者 Activity 来进行。

采用接口回调的形式

总结

凡是加入了回退栈 .addToBackStack(null) 之前加入的 Fragment 都不会被销毁。

比如:

Activity A 中

getSupportFragmentManager().beginTransaction()
        .add(R.id.fl, new FragmentOne())
        .add(R.id.fl, new FragmentTwo())
        .commit();
复制代码

FragmentTwo 中

getSupportFragmentManager().beginTransaction()
        .replace(R.id.fl, new FragmentThree())
    	.addToBackStack(null)
        .commit();
复制代码

请注意 Activity A 中是没有 addToBackStack 的 ,即使这样在 FragmentTweoreplaceFragmentOneFragmentTwo 也不会被销毁,而是走 onDestroyView() 方法。按下返回键后,恢复 FragmentOneFragmentTwoonCreateView 开始

如果没有 .addToBackStack(null) 的话,那么 FragmentOneFragmentTwo 就会被销毁了。并且按下返回键的时候,就销毁掉 Activity 了。

页面1 addTo

页面2 没有

E/FragmentThree: onAttach() E/FragmentThree: onCreate() E/FragmentTwo: onPause E/FragmentTwo: onStop E/FragmentTwo: onDestroyView E/FragmentOne: onPause E/FragmentOne: onStop E/FragmentOne: onDestroyView E/FragmentThree: onCreateView E/FragmentThree: onActivityCreated E/FragmentThree: onStart E/FragmentThree: onResume E/FragmentTwo: onDestroy E/FragmentTwo: onDetach() E/FragmentOne: onDestroy E/FragmentOne: onDetach()