03. fragment

176 阅读9分钟

[toc]

基本概念

介绍

可以把 Fragment 想象成一个小型的 Activity,它通常管理一个独立的部分 UI,并处理与该 UI 相关的用户交互。你可以把Fragment看作是管理一部分独立UI的小型的Activity。

当其存在时,Activity 提供了一个容器(fragment_container),Fragment 在这个容器中显示具体内容,并负责处理用户交互。

特性

依赖于 ActivityFragment 不能独立存在,它必须依附于 Activity

一个 Activity 里可以包含多个 Fragment:这使得 Fragment 成为创建多窗口、多面板用户界面的理想工具,尤其是在大屏设备(如平板电脑)上表现优异。

可重用性:同一个 Fragment 可以在多个 Activity 中复用,降低代码冗余,增强应用的灵活性。

独立的生命周期和事件处理Fragment 拥有自己的生命周期方法(类似 Activity),并且可以接收用户的输入事件,这使得它能独立管理自己的视图和行为。

动态管理:在 Activity 运行时,可以通过 FragmentManager 动态添加、移除或替换 Fragment,使得界面能够根据用户操作进行灵活调整。

Fragment核心类

Fragment

  • Fragment 是所有 Fragment 的基类。任何 Fragment 都必须继承自这个类。Fragment 提供了生命周期方法和处理视图、交互等相关的功能。

FragmentManager

  • FragmentManager 负责管理和维护 Fragment,例如添加、移除、替换 Fragment 等操作。FragmentManager 是一个抽象类,其具体实现类为 FragmentManagerImpl

FragmentTransaction

  • Fragment 的任何操作(如添加、移除、替换)都必须通过 FragmentTransaction 完成。FragmentTransaction 是一个抽象类,具体实现类为 BackStackRecord。通过事务的方式来确保 Fragment 的操作是原子性的,并能够将操作添加到返回栈中。

嵌套 Fragment

  • 从 Android 4.2 开始,Fragment 支持嵌套其他 Fragment,允许在一个 Fragment 中嵌套子 Fragment。通过 getChildFragmentManager() 可以获得管理子 FragmentFragmentManager。同时,子 Fragment 可以通过 getParentFragment() 获取到它的父 Fragment

补充:

  • Activity 中操作 Fragment 时,需要使用 SupportFragmentManager,它提供了一组 API 来管理 Fragment 的事务。
  • Fragment 内部嵌套子 Fragment 时,则使用 ChildFragmentManager 来进行子 Fragment 的管理。
  • 如果需要获取父 Fragment,可以通过 getParentFragment() 来找到当前 Fragment 的父 Fragment
  • 具体的 Fragment 操作(如添加、移除、替换等)都是通过 FragmentManager 中的 FragmentTransaction 类来实现的,它保证了对 Fragment 的操作以事务的方式进行,从而保持操作的原子性和一致性。

Fragmentmanager拥有FragmentTransaction

getSupportFragmentManageractivity关联,可以将其视为 activityFragmentManager getChildFragmentManagerfragment关联,可以将其视为fragmentFragmentManager getParentFragmentManager情况稍微复杂,正常情况返回的是该fragment 依附的activityFragmentManager。如果该fragment是另一个fragment的子fragment,则返回的是其父fragmentgetChildFragmentManager

因此

  • activity 中使用 ViewPagerBottomSheetFragmentDialogFragment 时,都应使用 getSupportFragmentManager
  • fragment 中使用 ViewPager 时应该使用getChildFragmentManager

基本使用

静态添加 Fragment

  • 方式:通过 XML 直接在布局文件中定义 Fragment。

    <fragment
        android:id="@+id/fragment1"
        android:name="com.example.Fragment1"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    
  • 缺点:一旦 Fragment 被添加,就无法在运行时动态移除或替换。它的存在是固定的,不能满足动态调整 UI 的需求。

动态添加 Fragment**

动态添加则允许我们在运行时对 Fragment 进行灵活的操作,如添加、移除、替换、加入返回栈等。

1. 在 Activity 布局中添加一个 FrameLayout 容器

在布局文件中,通常我们会用一个 FrameLayout 作为 Fragment 的容器。

<FrameLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

2. 在 onCreate() 中动态添加 Fragment

ActivityonCreate() 方法中,通过 FragmentManagerFragmentTransactionFragment 添加到 FrameLayout 中。

if (savedInstanceState == null) { // 防止Fragment重叠问题
    getSupportFragmentManager().beginTransaction()
        .add(R.id.container, Fragment1.newInstance("hello world"), "f1")
        //.addToBackStack("fname") // 可选的回退栈操作
        .commit();
}

注意事项:

  1. getSupportFragmentManager()
    • 因为使用了 support 库的 Fragment,所以需要使用 getSupportFragmentManager() 获取 FragmentManager。如果是原生的 Fragment,使用 getFragmentManager()
  2. **add()remove()replace()**这些是对 Fragment 的操作方法:
    • add():将 Fragment 添加到指定的容器中。
    • remove():将 Fragment 从容器中移除。
    • replace():替换容器中的 Fragment
  3. commit()commitNow()
    • commit() 是异步的,会将事务加入队列,稍后处理。
    • commitNow() 是同步的,立即执行事务。
  4. addToBackStack()
    • 使用 addToBackStack() 可以将事务添加到返回栈中,当用户按下返回按钮时,会撤销这个事务。如果不使用 addToBackStack(),按返回键会直接关闭 Activity

生命周期

基本生命周期如下:

onAttach():Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。

onCreate():Fragment被创建时调用。

onCreateView():创建Fragment的布局。

onActivityCreated():当Activity完成onCreate()时调用。

onStart():当Fragment可见时调用。

onResume():当Fragment可见且可交互时调用。

onPause():当Fragment不可交互但可见时调用。

onStop():当Fragment不可见时调用。

onDestroyView():当Fragment的UI从视图结构中移除时调用。

onDestroy():销毁Fragment时调用。

onDetach():当Fragment和Activity解除关联时调用。

和Activity生命周期的关系

如下图:

image.png

Activity 启动时

  • Activity 启动时,它首先进入 onCreate(),此时 Fragment 也会被添加,进入 onAttach()onCreate() 阶段。
  • ActivityonCreate() 中,Fragment 的视图会通过 onCreateView() 方法被创建。

Activity 进入 onStart()onResume()

  • Activity 进入 onStart()onResume() 时,Fragment 也会依次进入 onStart()onResume()
  • 在这两个阶段,ActivityFragment 都会变得可见且可以与用户交互。

Activity 被暂停时 (onPause())

  • 如果 Activity 进入 onPause(),意味着它不再处于前台,Fragment 也会进入 onPause() 阶段,停止与用户的交互。

Activity 停止时 (onStop())

  • Activity 不再可见并进入 onStop() 阶段时,Fragment 也会进入 onStop(),停止所有前台操作。

Activity 被销毁时 (onDestroy())

  • 如果 Activity 被销毁,它会调用 onDestroy(),此时 Fragment 也会相应地进入 onDestroyView(),销毁其视图。
  • 接着,Fragment 会进入 onDestroy(),然后执行 onDetach()Activity 解除关联。

Fragment 添加到返回栈中的情况

假设有两个 FragmentF1F2

  • F1:在 ActivityonCreate() 中被添加。
  • F2:在点击 F1 的按钮时,通过 replace() 方法将 F1 替换为 F2

在不使用addToBackStack()时,此时使用 replace() 替换 F1 ,由于没有添加到返回栈,F1 将会执行完整的销毁流程,包括 onDestroyView()onDestroy()onDetach(),意味着该 Fragment 被彻底移除。

在使用addToBackStack()时,Fragment生命周期会产生些许变动。F1 并未完全销毁。它只执行到了 onDestroyView(),意味着其视图被移除,但 Fragment 实例仍然被保留在内存中。onDestroy()onDetach() 并不会被调用。此时当用户按下返回按钮,F1 会从返回栈中恢复,重新调用 onCreateView()onStart()onResume(),从而恢复到之前的状态。

FragmentTransaction 的基本方法与生命周期

  • add():会从 onAttach() 开始,直到 onResume() 结束。这个方法用于将 Fragment 添加到布局中。
  • remove():会触发 onPause() -> onStop() -> onDestroyView() -> onDestroy() -> onDetach()。这个方法用于将 Fragment 从布局中移除,并销毁它。
  • replace():相当于调用了先移除旧 Fragment (remove()),再添加新 Fragment (add()) 的操作,因此两个 Fragment 的生命周期都会受到影响。
  • show()hide():不调用任何生命周期方法,只是改变 FragmentsetVisibility(),用于显示或隐藏已添加的 Fragment
  • detach():会调用 onPause() -> onStop() -> onDestroyView(),但 Fragment 实例仍然存在于 FragmentManager 中,只是视图被销毁了。
  • attach():当重新关联已被 detach()Fragment 时,会重新创建视图,执行 onCreateView() -> onStart() -> onResume()

Fragment通信

1. Fragment 向 Activity 传递数据

通过接口通信

  • 定义接口:在 Fragment 中定义一个接口,并让宿主 Activity 实现该接口,用于从 FragmentActivity 传递数据。

  • onAttach() 中设置接口: 在 FragmentonAttach() 方法中,将 Context 转换为接口的实现,确保 Activity 正确实现了该接口。

    public interface OnFragmentInteractionListener {
        void onItemClick(String str);
    }
    
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }
    
  • 调用接口方法传递数据: 在合适的地方调用接口方法,将数据从 Fragment 传递到 Activity

    mListener.onItemClick("hello");
    

使用 FABridge 进行通信

  • FABridge 简化通信: 使用 FABridge 可以通过注解的方式简化 FragmentActivity 之间的数据传递,避免手动定义接口。

    1. 添加依赖: 在 build.gradle 中添加 FABridge 依赖。
    2. Activity 中定义方法: 使用 @FCallbackId 注解为 Activity 中的方法定义一个 ID。
    @FCallbackId(id = FAB_ITEM_CLICK)
    public void onItemClick(String str) {
        Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
    }
    
    1. Fragment 中调用: 使用 Fabridge.call() 方法调用指定的 ID 对应的方法,并传递数据。
    Fabridge.call(mActivity, FAB_ITEM_CLICK, "data");
    

2. Activity 向 Fragment 传递数据

  • 通过调用 Fragment 的方法传递数据: 在 Fragment 中定义公开的方法,Activity 可以通过调用这些方法向 Fragment 传递数据。

    public void setString(String str) {
        this.str = str;
    }
    

    然后在 Activity 中调用这个方法:

    fragment.setString("hello");
    

3. Fragment 之间的通信

  • 通过 Activity 作为中介: 由于 Fragment 之间没有直接的依赖关系,建议通过 Activity 作为中介进行通信。
    • Fragment A 向 Activity 发送数据: 通过接口或其他方法传递数据给 Activity
    • Activity 向 Fragment B 发送数据Activity 接收到数据后,再调用 Fragment B 的方法,完成数据的传递。

ViewPager+Fragment相关

  • ViewPager 是一个用于实现界面滑动切换的 ViewGroup,通常用于结合 Fragment 来显示多个页面。
  • 适配器选择:
    • FragmentPagerAdapterFragmentStatePagerAdapterViewPager 的两个常用适配器类,它们专门用于管理 Fragment 页面。
    • FragmentPagerAdapter:适用于页面数量较少的情况,适配器会将所有 Fragment 保存在内存中,即使页面不可见时,Fragment 实例也不会被销毁。
    • FragmentStatePagerAdapter:适用于页面数量较多的情况,不可见的 Fragment 会被销毁,只保留其状态,这样可以节省内存。

常见的重写方法:

  • getItem(int position):返回指定位置的 Fragment,必须重写。
  • getCount():返回页面的总数,必须重写。
  • instantiateItem()destroyItem():管理 Fragment 的实例化和销毁。
  • getItemPosition():决定 Fragment 的刷新策略,如果返回 POSITION_UNCHANGED,表示不刷新;返回 POSITION_NONE 表示该 Fragment 需要重新创建。