玩转 Android Fragment:从入门到实战,搞定页面模块化开发(附完整示例)

Android Fragment全面解析

目录

前言:为什么 Fragment 是 Android 页面开发的 “模块化神器”?

一、Fragment 基础认知:到底什么是碎片?

1.1 一句话搞懂 Fragment 的核心作用

1.2 必须澄清的 3 个常见误解

1.3 Fragment 与 Activity 的核心区别

1.4 Fragment 的典型适用场景

二、Fragment 生命周期:与 Activity 的联动逻辑(实战解析)

2.1 Fragment 的 11 个核心生命周期回调

2.2 生命周期联动流程:用代码实测

示例 1:Fragment 与 Activity 生命周期联动日志

2.3 生命周期核心原则:“资源对称” 与 “状态保存”

1. 资源对称原则

2. 状态保存:避免配置变更后数据丢失

方式 1:onSaveInstanceState ()(轻量级数据)

方式 2:ViewModel(重量级数据,推荐)

三、Fragment 的两种加载方式:静态加载 vs 动态加载

3.1 静态加载(声明式加载,简单场景)

示例 2:静态加载 Fragment

静态加载的优缺点:

3.2 动态加载(代码式加载,主流方式)

核心 API:FragmentManager 与 FragmentTransaction

动态加载的 5 个核心操作

1. 添加 Fragment(add)

2. 替换 Fragment(replace)

3. 移除 Fragment(remove)

4. 隐藏 / 显示 Fragment(hide/show)

5. 回退 Fragment(addToBackStack)

完整示例:底部 Tab 切换(3 个 Fragment)

关键说明:

四、Fragment 通信:组件间数据传递全攻略

4.1 场景 1:Fragment → Activity(数据传递)

方案 1:接口回调(推荐,低耦合)

示例 6:Fragment 通过接口回调向 Activity 传值

方案 2:ViewModel(复杂数据,跨组件共享)

4.2 场景 2:Activity → Fragment(数据传递)

方案 1:Bundle 传参(初始化 Fragment 时)

关键注意事项:

方案 2:ViewModel(动态更新数据)

4.3 场景 3:Fragment → Fragment(数据传递)

方案 1:Activity 中转(通过接口回调 + Bundle)

方案 2:ViewModel 共享(推荐,低耦合)

4.4 3 种通信方式对比(选择指南)

五、Fragment 进阶用法:ViewPager2 结合、懒加载、特殊 Fragment

5.1 ViewPager2 + Fragment(滑动切换,主流组合)

示例 8:ViewPager2 + Fragment 实现滑动切换

ViewPager2 核心特性:

5.2 Fragment 懒加载(按需加载数据,优化性能)

示例 9:ViewPager2 + Fragment 实现懒加载

关键说明:

5.3 特殊 Fragment:DialogFragment、BottomSheetFragment

1. DialogFragment(弹窗 Fragment,替代 Dialog)

示例 10:DialogFragment 实现登录弹窗

2. BottomSheetFragment(底部弹窗 Fragment)

六、Fragment 常见坑点与避坑指南(实战经验总结)

6.1 坑点 1:Fragment 重叠问题(屏幕旋转后)

6.2 坑点 2:Fragment 获取 Activity 为 null

6.3 坑点 3:FragmentTransaction.commit () 报错(状态非法)

6.4 坑点 4:ViewPager2 预加载导致懒加载失效

6.5 坑点 5:Fragment 内存泄漏

七、总结:Fragment 核心知识点图谱


class 卑微码农:
    def __init__(self):
        self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
        self.发量 = 100  # 初始发量
        self.咖啡因耐受度 = '极限'
        
    def 修Bug(self, bug):
        try:
            # 试图用玄学解决问题
            if bug.严重程度 == '离谱':
                print("这一定是环境问题!")
            else:
                print("让我看看是谁又没写注释...哦,是我自己。")
        except Exception as e:
            # 如果try块都救不了,那就...
            print("重启一下试试?")
            self.发量 -= 1  # 每解决一个bug,头发-1
 
 
# 实例化一个我
我 = 卑微码农()

前言:为什么 Fragment 是 Android 页面开发的 “模块化神器”?

刚入门 Android 时,曾用 Activity 堆砌所有页面 —— 一个 APP 写了十几个 Activity,跳转逻辑混乱,相同 UI 组件重复编写,屏幕旋转后数据还容易丢失。直到接触了 Fragment,才发现它的核心价值:把页面拆分成独立的 “模块” ,可复用、可组合、可管理,彻底解决了 Activity 臃肿、耦合度高的问题。

Fragment 就像 “迷你版 Activity”,有自己的布局和生命周期,却必须依附于 Activity 存在。它能让你在一个 Activity 中切换多个 UI 模块(如首页的 “首页 / 发现 / 我的”),也能在不同 Activity 中复用同一个模块(如商品详情页在购物 APP 和电商小程序中复用)。

但很多开发者用不好 Fragment,要么遇到 “切换时 Fragment 重叠”“生命周期混乱”“通信失败” 的问题,要么不知道什么时候该用 Fragment、什么时候该用 Activity。其实 Fragment 的逻辑不复杂,关键是掌握 “生命周期联动”“动态操作”“组件通信” 这三大核心。

本文会把 Fragment 的基础概念、生命周期、创建使用、通信方式、进阶用法、避坑指南全讲透。全文包含 10 个完整可运行的原创示例,从基础入门到进阶实战,新手能直接上手,进阶开发者能夯实基础、避开坑点。

建议先收藏,再跟着示例一步步实操,遇到问题可以在评论区交流~


一、Fragment 基础认知:到底什么是碎片?

1.1 一句话搞懂 Fragment 的核心作用

Fragment(简称 “碎片”)是 Android 3.0(API 11)引入的组件,核心作用是实现页面模块化拆分,支持 UI 复用与灵活组合

简单说:Fragment 是 “可嵌入 Activity 的独立 UI 模块”—— 它有自己的布局(layout)、生命周期回调,能处理用户交互(如按钮点击),但不能单独存在,必须依附于 Activity。就像搭积木,Activity 是 “积木底板”,Fragment 是 “不同形状的积木”,可以按需组合、替换,搭建出多样化的页面。

举个实际场景:某信首页的 “某信 / 通讯录 / 发现 / 我” 四个模块,每个都是一个 Fragment,通过底部 Tab 切换时,Activity 不变,只替换中间的 Fragment,既高效又灵活。

1.2 必须澄清的 3 个常见误解

  • 误解 1:Fragment 是 Activity 的 “子页面”?—— 错!Fragment 是独立的模块,与 Activity 是 “合作关系” 而非 “父子关系”,可在多个 Activity 中复用;
  • 误解 2:Fragment 比 Activity 轻量,所以尽量多用?—— 错!Fragment 的生命周期更复杂(与 Activity 联动),滥用会导致逻辑混乱,需根据场景选择;
  • 误解 3:Fragment 只能在手机上用?—— 错!Fragment 最初是为平板设计的(适配大屏幕多窗口),现在手机端模块化开发中更常用。

1.3 Fragment 与 Activity 的核心区别

特性FragmentActivity
独立存在性不能单独存在,必须依附 Activity可单独存在,是四大组件之一
布局加载通过onCreateView()加载布局通过setContentView()加载布局
生命周期受 Activity 生命周期联动影响独立生命周期,受系统直接管理
启动方式不能直接启动,需通过 Activity 加载可通过 Intent 直接启动
核心作用页面模块化拆分、UI 复用作为页面容器,管理 Fragment 和组件
配置变更(如旋转)默认随 Activity 重建(可通过 ViewModel 保存数据)默认重建(可配置configChanges避免)

1.4 Fragment 的典型适用场景

  • 多 Tab 页面:如首页底部 Tab 切换(微信、抖音、淘宝);
  • UI 复用场景:如商品详情页、评论列表在多个 Activity 中复用;
  • 大屏幕适配:如平板的 “列表 + 详情” 双栏布局(左侧 Fragment 显示列表,右侧显示详情);
  • 动态页面组合:如根据用户权限显示不同的 Fragment(登录后显示个人中心,未登录显示登录 Fragment);
  • 复杂页面拆分:如一个包含 “表单 + 列表 + 图表” 的复杂页面,拆分成 3 个 Fragment 分别管理,降低耦合。

二、Fragment 生命周期:与 Activity 的联动逻辑(实战解析)

Fragment 的生命周期比 Activity 复杂,核心原因是它的生命周期会与依附的 Activity “联动”——Activity 的状态变化会直接影响 Fragment 的状态。这是掌握 Fragment 的关键,也是最容易踩坑的地方。

2.1 Fragment 的 11 个核心生命周期回调

Fragment 的生命周期回调比 Activity 多,核心有 11 个,每个都与 Activity 的生命周期对应,我们结合实际场景理解:

方法名调用时机核心作用与 Activity 的联动关系
onAttach(Context)Fragment 与 Activity 关联时(仅调用 1 次)获取 Activity 引用,初始化全局数据早于 Activity 的 onCreate ()
onCreate(Bundle)Fragment 创建时(仅调用 1 次)初始化非 UI 数据(如 ViewModel、数据请求)与 Activity 的 onCreate () 同步或稍晚
onCreateView()Fragment 创建视图时(加载布局)加载 Fragment 的布局(inflater.inflate ())在 Activity 的 onCreate () 之后、onStart () 之前
onViewCreated()Fragment 视图创建完成后绑定控件、设置监听(如按钮点击)紧跟 onCreateView () 之后
onActivityCreated()依附的 Activity 创建完成后(API 28 后废弃)访问 Activity 的控件或数据(已过时,用 onViewCreated 替代)Activity 的 onCreate () 执行完成后
onStart()Fragment 可见时启动动画、注册非前台监听器与 Activity 的 onStart () 同步
onResume()Fragment 可见且可交互时启动前台服务、恢复播放与 Activity 的 onResume () 同步
onPause()Fragment 即将失去焦点时暂停动画、保存临时数据与 Activity 的 onPause () 同步
onStop()Fragment 完全不可见时释放大内存资源、注销监听器与 Activity 的 onStop () 同步
onDestroyView()Fragment 视图销毁时解绑控件引用、释放视图相关资源在 Activity 的 onDestroy () 之前
onDestroy()Fragment 销毁时(仅调用 1 次)彻底释放非视图资源(如 ViewModel、异步任务)在 onDestroyView () 之后
onDetach()Fragment 与 Activity 解除关联时(仅调用 1 次)清空 Activity 引用(避免内存泄漏)晚于 Fragment 的 onDestroy (),早于 Activity 的 onDestroy ()

2.2 生命周期联动流程:用代码实测

光看表格不够直观,我们通过一个示例,打印 Fragment 和 Activity 的生命周期回调,观察它们的联动关系。

示例 1:Fragment 与 Activity 生命周期联动日志
  1. 创建宿主 Activity(FragmentHostActivity):
public class FragmentHostActivity extends AppCompatActivity {
    private static final String TAG = "LifecycleTest";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment_host);
        Log.d(TAG, "Activity onCreate");

        // 加载Fragment(静态加载,后续讲动态加载)
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.fragment_container, new TestFragment())
                    .commit();
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "Activity onStart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "Activity onResume");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "Activity onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "Activity onStop");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "Activity onDestroy");
    }
}
  1. 创建 TestFragment:
public class TestFragment extends Fragment {
    private static final String TAG = "LifecycleTest";

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        Log.d(TAG, "Fragment onAttach");
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "Fragment onCreate");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(TAG, "Fragment onCreateView");
        // 加载Fragment布局(简单布局:仅一个TextView)
        return inflater.inflate(R.layout.fragment_test, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Log.d(TAG, "Fragment onViewCreated");
        // 绑定控件
        TextView tv = view.findViewById(R.id.tv_fragment);
        tv.setText("测试Fragment生命周期");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.d(TAG, "Fragment onStart");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "Fragment onResume");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG, "Fragment onPause");
    }

    @Override
    public void onStop() {
        super.onStop();
        Log.d(TAG, "Fragment onStop");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d(TAG, "Fragment onDestroyView");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "Fragment onDestroy");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.d(TAG, "Fragment onDetach");
    }
}
  1. 对应的布局文件:
  • activity_fragment_host.xml(Activity 布局,包含 Fragment 容器):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

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

</LinearLayout>
  • fragment_test.xml(Fragment 布局):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <TextView
        android:id="@+id/tv_fragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp" />

</LinearLayout>
  1. 运行测试,观察 Logcat 日志(首次启动 Activity):
D/LifecycleTest: Fragment onAttach
D/LifecycleTest: Activity onCreate
D/LifecycleTest: Fragment onCreate
D/LifecycleTest: Fragment onCreateView
D/LifecycleTest: Fragment onViewCreated
D/LifecycleTest: Activity onStart
D/LifecycleTest: Fragment onStart
D/LifecycleTest: Activity onResume
D/LifecycleTest: Fragment onResume
  1. 关键场景的生命周期流程:
  • 场景 1:按 Home 键返回桌面(Activity 进入后台):
D/LifecycleTest: Activity onPause
D/LifecycleTest: Fragment onPause
D/LifecycleTest: Activity onStop
D/LifecycleTest: Fragment onStop
  • 场景 2:从桌面重新打开应用(Activity 恢复前台):
D/LifecycleTest: Activity onStart
D/LifecycleTest: Fragment onStart
D/LifecycleTest: Activity onResume
D/LifecycleTest: Fragment onResume
  • 场景 3:按返回键退出 Activity(Fragment 与 Activity 销毁):
D/LifecycleTest: Activity onPause
D/LifecycleTest: Fragment onPause
D/LifecycleTest: Activity onStop
D/LifecycleTest: Fragment onStop
D/LifecycleTest: Fragment onDestroyView
D/LifecycleTest: Fragment onDestroy
D/LifecycleTest: Fragment onDetach
D/LifecycleTest: Activity onDestroy
  • 场景 4:屏幕旋转(Activity 与 Fragment 默认重建):
// 销毁流程
D/LifecycleTest: Activity onPause
D/LifecycleTest: Fragment onPause
D/LifecycleTest: Activity onStop
D/LifecycleTest: Fragment onStop
D/LifecycleTest: Fragment onDestroyView
D/LifecycleTest: Fragment onDestroy
D/LifecycleTest: Fragment onDetach
D/LifecycleTest: Activity onDestroy
// 重建流程(与首次启动一致)
D/LifecycleTest: Fragment onAttach
D/LifecycleTest: Activity onCreate
D/LifecycleTest: Fragment onCreate
D/LifecycleTest: Fragment onCreateView
D/LifecycleTest: Fragment onViewCreated
...

2.3 生命周期核心原则:“资源对称” 与 “状态保存”

1. 资源对称原则

与 Activity 类似,Fragment 的资源申请与释放需遵循 “对称原则”,避免内存泄漏:

  • onAttach ():获取 Activity 引用、初始化全局数据(如 SP);
  • onCreate ():初始化非视图资源(如 ViewModel、Repository);
  • onViewCreated ():绑定控件、设置监听、初始化视图相关资源(如 RecyclerView 适配器);
  • onDestroyView ():解绑控件引用、释放视图资源(如 RecyclerView、动画);
  • onDetach ():清空 Activity 引用(避免内存泄漏)。

反例:在 onCreateView () 中注册广播接收器,却未在 onDestroyView () 中注销,会导致 Fragment 视图销毁后仍持有广播引用,造成内存泄漏。

2. 状态保存:避免配置变更后数据丢失

屏幕旋转等配置变更时,Fragment 会随 Activity 重建,临时数据(如 EditText 输入内容、列表滚动位置)会丢失,需通过以下两种方式保存:

方式 1:onSaveInstanceState ()(轻量级数据)

Fragment 的onSaveInstanceState(Bundle outState)方法会在重建前调用,可保存简单数据:

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    // 保存EditText输入内容
    EditText etInput = getView().findViewById(R.id.et_input);
    outState.putString("input_text", etInput.getText().toString());
}

// 在onViewCreated中恢复数据
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    EditText etInput = view.findViewById(R.id.et_input);
    if (savedInstanceState != null) {
        String inputText = savedInstanceState.getString("input_text", "");
        etInput.setText(inputText);
    }
}
方式 2:ViewModel(重量级数据,推荐)

ViewModel 的生命周期独立于 Activity 和 Fragment 的重建,适合保存复杂数据(如列表数据、网络请求结果):

// 1. 创建ViewModel
public class MyViewModel extends ViewModel {
    private MutableLiveData<List<String>> dataList = new MutableLiveData<>();

    public LiveData<List<String>> getDataList() {
        return dataList;
    }

    public void setDataList(List<String> list) {
        dataList.setValue(list);
    }
}

// 2. 在Fragment中使用ViewModel
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    // 获取ViewModel(与Activity共享,确保重建后数据不丢失)
    MyViewModel viewModel = new ViewModelProvider(requireActivity()).get(MyViewModel.class);
    // 观察数据变化
    viewModel.getDataList().observe(getViewLifecycleOwner(), list -> {
        // 刷新UI
    });
}

三、Fragment 的两种加载方式:静态加载 vs 动态加载

Fragment 的加载方式分为静态和动态两种,静态简单但不灵活,动态是开发中的主流方式,支持灵活切换、替换 Fragment。

3.1 静态加载(声明式加载,简单场景)

静态加载是在 Activity 的布局文件中直接声明 Fragment,适用于无需动态切换的场景(如固定的双栏布局)。

示例 2:静态加载 Fragment
  1. 编写 Fragment(StaticFragment):
public class StaticFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_static, container, false);
    }
}
  1. 在 Activity 布局中声明 Fragment:
<!-- activity_static_host.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <!-- 左侧Fragment(占1/3宽度) -->
    <fragment
        android:id="@+id/fragment_left"
        android:name="com.example.fragment.StaticFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <!-- 右侧Fragment(占2/3宽度) -->
    <fragment
        android:id="@+id/fragment_right"
        android:name="com.example.fragment.StaticFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="2" />

</LinearLayout>
  1. 宿主 Activity(无需额外代码,布局加载时自动初始化 Fragment):
public class StaticHostActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_static_host);
    }
}
静态加载的优缺点:
  • 优点:实现简单,无需手动管理 Fragment 的创建和生命周期;
  • 缺点:不支持动态切换、替换 Fragment,灵活性极低;
  • 适用场景:平板双栏布局、固定不变的 UI 模块。

3.2 动态加载(代码式加载,主流方式)

动态加载是通过FragmentManagerFragmentTransaction在代码中动态添加、替换、移除 Fragment,支持灵活的页面切换(如底部 Tab、侧边栏)。

核心 API:FragmentManager 与 FragmentTransaction
  • FragmentManager:Fragment 的 “管理器”,负责 Fragment 的添加、查找、回退等操作,通过getSupportFragmentManager()(AndroidX)获取;
  • FragmentTransaction:Fragment 的 “事务”,封装了 Fragment 的一系列操作(add、replace、remove、hide、show),需通过beginTransaction()创建,最后调用commit()提交。
动态加载的 5 个核心操作
1. 添加 Fragment(add)

将 Fragment 添加到 Activity 的容器中,可添加多个 Fragment 叠加显示:

// 示例3:动态添加Fragment
private void addFragment() {
    // 1. 获取FragmentManager
    FragmentManager manager = getSupportFragmentManager();
    // 2. 开启事务
    FragmentTransaction transaction = manager.beginTransaction();
    // 3. 创建Fragment实例
    DynamicFragment fragment = new DynamicFragment();
    // 4. 添加Fragment到容器(参数:容器ID、Fragment实例、标签(用于查找))
    transaction.add(R.id.fragment_container, fragment, "dynamic_fragment");
    // 5. 提交事务
    transaction.commit();
}
2. 替换 Fragment(replace)

移除容器中所有已添加的 Fragment,再添加新的 Fragment(常用语 Tab 切换):

// 示例4:动态替换Fragment(底部Tab切换核心)
private void replaceFragment(Fragment fragment) {
    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction transaction = manager.beginTransaction();
    // 替换容器中的Fragment(参数:容器ID、新Fragment)
    transaction.replace(R.id.fragment_container, fragment);
    // 可选:添加到回退栈(按返回键可回退到上一个Fragment)
    transaction.addToBackStack(null);
    transaction.commit();
}

// 调用示例(切换到首页Fragment)
findViewById(R.id.tab_home).setOnClickListener(v -> {
    replaceFragment(new HomeFragment());
});
3. 移除 Fragment(remove)

从容器中移除指定的 Fragment(移除后会执行 onDestroyView→onDestroy→onDetach):

private void removeFragment() {
    FragmentManager manager = getSupportFragmentManager();
    // 查找已添加的Fragment(通过标签)
    DynamicFragment fragment = (DynamicFragment) manager.findFragmentByTag("dynamic_fragment");
    if (fragment != null) {
        FragmentTransaction transaction = manager.beginTransaction();
        transaction.remove(fragment);
        transaction.commit();
    }
}
4. 隐藏 / 显示 Fragment(hide/show)

隐藏 Fragment(不销毁视图,仅不可见),显示已隐藏的 Fragment(适合频繁切换,避免重建):

// 示例5:隐藏/显示Fragment(比replace更高效)
private void hideAndShowFragment() {
    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction transaction = manager.beginTransaction();
    // 隐藏首页Fragment
    transaction.hide(homeFragment);
    // 显示发现Fragment
    transaction.show(discoverFragment);
    transaction.commit();
}
5. 回退 Fragment(addToBackStack)

将事务添加到回退栈,按返回键可回退到上一个 Fragment 状态:

// 添加到回退栈(参数:事务名称,可为null)
transaction.addToBackStack("replace_home");
完整示例:底部 Tab 切换(3 个 Fragment)
  1. Activity 布局(包含底部 Tab 和 Fragment 容器):
<!-- activity_tab_host.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- Fragment容器 -->
    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <!-- 底部Tab(3个按钮) -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">

        <Button
            android:id="@+id/tab_home"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="首页" />

        <Button
            android:id="@+id/tab_discover"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="发现" />

        <Button
            android:id="@+id/tab_mine"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="我的" />

    </LinearLayout>

</LinearLayout>
  1. 3 个简单的 Fragment(HomeFragment、DiscoverFragment、MineFragment,结构一致):
public class HomeFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_home, container, false);
        TextView tv = view.findViewById(R.id.tv_home);
        tv.setText("首页Fragment");
        return view;
    }
}
  1. 宿主 Activity(实现 Tab 切换逻辑):
public class TabHostActivity extends AppCompatActivity {
    private HomeFragment homeFragment;
    private DiscoverFragment discoverFragment;
    private MineFragment mineFragment;
    private Fragment currentFragment; // 当前显示的Fragment

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tab_host);

        // 初始化3个Fragment
        homeFragment = new HomeFragment();
        discoverFragment = new DiscoverFragment();
        mineFragment = new MineFragment();

        // 默认显示首页Fragment
        switchFragment(homeFragment);

        // 底部Tab点击事件
        findViewById(R.id.tab_home).setOnClickListener(v -> switchFragment(homeFragment));
        findViewById(R.id.tab_discover).setOnClickListener(v -> switchFragment(discoverFragment));
        findViewById(R.id.tab_mine).setOnClickListener(v -> switchFragment(mineFragment));
    }

    // 切换Fragment(核心方法:hide当前,show目标)
    private void switchFragment(Fragment targetFragment) {
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();

        // 如果目标Fragment未添加,先添加
        if (!targetFragment.isAdded()) {
            transaction.add(R.id.fragment_container, targetFragment);
        }

        // 隐藏当前Fragment,显示目标Fragment
        if (currentFragment != null) {
            transaction.hide(currentFragment);
        }
        transaction.show(targetFragment);

        // 更新当前Fragment
        currentFragment = targetFragment;

        // 提交事务
        transaction.commit();
    }
}
关键说明:
  • 用 hide/show 切换比 replace 更高效,因为 Fragment 的视图不会销毁,避免重复 inflate 布局;
  • 初始化 Fragment 时只创建一次实例,避免频繁创建导致的性能问题;
  • 无需添加到回退栈(底部 Tab 切换通常不需要回退),如需回退可添加addToBackStack()

四、Fragment 通信:组件间数据传递全攻略

Fragment 的通信是开发中的核心难点,常见场景有 3 种:Fragment 与 Activity、Fragment 与 Fragment、Fragment 与 ViewModel,每种场景都有对应的最优方案。

4.1 场景 1:Fragment → Activity(数据传递)

Fragment 向 Activity 传递数据,推荐用 “接口回调”(简单场景)或 “ViewModel”(复杂场景)。

方案 1:接口回调(推荐,低耦合)
示例 6:Fragment 通过接口回调向 Activity 传值
  1. 在 Fragment 中定义回调接口:
public class DataFragment extends Fragment {
    // 回调接口(定义要传递的数据类型)
    public interface OnDataSendListener {
        void onDataSend(String data); // 传递字符串数据
    }

    private OnDataSendListener mListener;

    // Fragment与Activity关联时,获取接口实例
    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        // 验证Activity是否实现了接口
        if (context instanceof OnDataSendListener) {
            mListener = (OnDataSendListener) context;
        } else {
            throw new IllegalStateException("Activity必须实现OnDataSendListener接口");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_data, container, false);
        // 按钮点击:传递数据给Activity
        view.findViewById(R.id.btn_send).setOnClickListener(v -> {
            if (mListener != null) {
                mListener.onDataSend("Fragment传递的数据");
            }
        });
        return view;
    }

    // 解除关联时,清空接口引用(避免内存泄漏)
    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }
}
  1. Activity 实现接口,接收数据:
public class DataHostActivity extends AppCompatActivity implements DataFragment.OnDataSendListener {
    private TextView tvReceived;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_data_host);
        tvReceived = findViewById(R.id.tv_received);

        // 加载DataFragment
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fragment_container, new DataFragment())
                .commit();
    }

    // 实现接口方法,接收Fragment传递的数据
    @Override
    public void onDataSend(String data) {
        tvReceived.setText("收到Fragment数据:" + data);
    }
}
方案 2:ViewModel(复杂数据,跨组件共享)

ViewModel 可在 Activity 和多个 Fragment 之间共享数据,适合复杂场景(如多个 Fragment 向 Activity 传值):

// 1. 创建共享ViewModel
public class SharedViewModel extends ViewModel {
    private MutableLiveData<String> sharedData = new MutableLiveData<>();

    public void setSharedData(String data) {
        sharedData.setValue(data);
    }

    public LiveData<String> getSharedData() {
        return sharedData;
    }
}

// 2. Fragment中发送数据
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    SharedViewModel viewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
    // 发送数据
    view.findViewById(R.id.btn_send).setOnClickListener(v -> {
        viewModel.setSharedData("Fragment传递的数据");
    });
}

// 3. Activity中接收数据
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_data_host);
    TextView tvReceived = findViewById(R.id.tv_received);

    SharedViewModel viewModel = new ViewModelProvider(this).get(SharedViewModel.class);
    viewModel.getSharedData().observe(this, data -> {
        tvReceived.setText("收到Fragment数据:" + data);
    });
}

4.2 场景 2:Activity → Fragment(数据传递)

Activity 向 Fragment 传递数据,推荐用 “Bundle 传参”(初始化时)或 “ViewModel”(动态更新)。

方案 1:Bundle 传参(初始化 Fragment 时)

这是最常用的方式,通过setArguments(Bundle)传递数据,Fragment 在onCreate()onViewCreated()中接收:

// 示例7:Activity向Fragment传递数据
public class SendToFragmentActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_send_to_fragment);

        // 1. 创建Bundle,存储数据
        Bundle bundle = new Bundle();
        bundle.putString("name", "张三");
        bundle.putInt("age", 25);
        bundle.putBoolean("isStudent", true);

        // 2. 创建Fragment,设置Arguments
        ReceiveFragment fragment = new ReceiveFragment();
        fragment.setArguments(bundle);

        // 3. 加载Fragment
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fragment_container, fragment)
                .commit();
    }
}

// Fragment接收数据
public class ReceiveFragment extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 接收数据(getArguments()获取Bundle)
        if (getArguments() != null) {
            String name = getArguments().getString("name");
            int age = getArguments().getInt("age");
            boolean isStudent = getArguments().getBoolean("isStudent");
            Log.d("FragmentTest", "姓名:" + name + ",年龄:" + age + ",是否学生:" + isStudent);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_receive, container, false);
        // 显示数据
        TextView tv = view.findViewById(R.id.tv_data);
        if (getArguments() != null) {
            String name = getArguments().getString("name");
            tv.setText("收到Activity数据:" + name);
        }
        return view;
    }
}
关键注意事项:
  • 传递的数据必须是可序列化的(基本数据类型、String、Parcelable、Serializable);
  • 不要直接通过 Fragment 的构造方法传参(Activity 重建时,Fragment 会通过无参构造方法重建,参数会丢失);
  • 推荐用静态工厂方法创建 Fragment(封装传参逻辑):
// Fragment中添加静态工厂方法(推荐)
public static ReceiveFragment newInstance(String name, int age) {
    ReceiveFragment fragment = new ReceiveFragment();
    Bundle bundle = new Bundle();
    bundle.putString("name", name);
    bundle.putInt("age", age);
    fragment.setArguments(bundle);
    return fragment;
}

// Activity中创建Fragment
ReceiveFragment fragment = ReceiveFragment.newInstance("张三", 25);
方案 2:ViewModel(动态更新数据)

如果需要在 Fragment 创建后,Activity 动态向其传递数据(如用户操作后更新),推荐用 ViewModel:

// 1. 共享ViewModel(与Activity和Fragment共享)
public class UpdateViewModel extends ViewModel {
    private MutableLiveData<String> updateData = new MutableLiveData<>();

    public void updateData(String data) {
        updateData.setValue(data);
    }

    public LiveData<String> getUpdateData() {
        return updateData;
    }
}

// 2. Activity中动态更新数据
public class DynamicUpdateActivity extends AppCompatActivity {
    private UpdateViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dynamic_update);
        viewModel = new ViewModelProvider(this).get(UpdateViewModel.class);

        // 加载Fragment
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fragment_container, new UpdateFragment())
                .commit();

        // 按钮点击:动态更新数据
        findViewById(R.id.btn_update).setOnClickListener(v -> {
            viewModel.updateData("Activity动态更新的数据:" + System.currentTimeMillis());
        });
    }
}

// 3. Fragment中观察数据变化
public class UpdateFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_update, container, false);
        TextView tvUpdate = view.findViewById(R.id.tv_update);

        UpdateViewModel viewModel = new ViewModelProvider(requireActivity()).get(UpdateViewModel.class);
        // 观察数据变化,自动更新UI
        viewModel.getUpdateData().observe(getViewLifecycleOwner(), data -> {
            tvUpdate.setText(data);
        });

        return view;
    }
}

4.3 场景 3:Fragment → Fragment(数据传递)

两个 Fragment 之间传递数据,禁止直接持有对方引用(会导致强耦合和内存泄漏),推荐通过 “Activity 中转” 或 “ViewModel 共享”。

方案 1:Activity 中转(通过接口回调 + Bundle)

流程:Fragment A → Activity → Fragment B

// 1. Fragment A定义回调接口,向Activity传值
public class FragmentA extends Fragment {
    public interface OnADataSendListener {
        void onADataSend(String data);
    }

    private OnADataSendListener mListener;

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        mListener = (OnADataSendListener) context;
    }

    // 发送数据到Activity
    private void sendDataToActivity() {
        if (mListener != null) {
            mListener.onADataSend("Fragment A的数据");
        }
    }
}

// 2. Activity实现接口,接收数据并传递给Fragment B
public class FragmentHostActivity extends AppCompatActivity implements FragmentA.OnADataSendListener {
    private FragmentB fragmentB;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment_host);

        // 初始化两个Fragment
        FragmentA fragmentA = new FragmentA();
        fragmentB = new FragmentB();

        // 加载两个Fragment
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fragment_container_a, fragmentA)
                .add(R.id.fragment_container_b, fragmentB)
                .commit();
    }

    // 接收Fragment A的数据,传递给Fragment B
    @Override
    public void onADataSend(String data) {
        // Fragment B通过方法接收数据
        fragmentB.receiveDataFromA(data);
    }
}

// 3. Fragment B提供方法,接收数据
public class FragmentB extends Fragment {
    private TextView tvData;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_b, container, false);
        tvData = view.findViewById(R.id.tv_data);
        return view;
    }

    // 提供公共方法,接收Fragment A的数据
    public void receiveDataFromA(String data) {
        tvData.setText("收到Fragment A的数据:" + data);
    }
}
方案 2:ViewModel 共享(推荐,低耦合)

两个 Fragment 通过 Activity 共享同一个 ViewModel,直接传递数据,无需 Activity 中转:

// 1. 共享ViewModel
public class FragmentShareViewModel extends ViewModel {
    private MutableLiveData<String> shareData = new MutableLiveData<>();

    public void setShareData(String data) {
        shareData.setValue(data);
    }

    public LiveData<String> getShareData() {
        return shareData;
    }
}

// 2. Fragment A发送数据
public class FragmentA extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_a, container, false);
        // 获取共享ViewModel(通过requireActivity(),与Activity共享)
        FragmentShareViewModel viewModel = new ViewModelProvider(requireActivity()).get(FragmentShareViewModel.class);

        // 发送数据
        view.findViewById(R.id.btn_send).setOnClickListener(v -> {
            viewModel.setShareData("Fragment A的数据");
        });

        return view;
    }
}

// 3. Fragment B接收数据
public class FragmentB extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_b, container, false);
        TextView tvData = view.findViewById(R.id.tv_data);

        // 获取共享ViewModel
        FragmentShareViewModel viewModel = new ViewModelProvider(requireActivity()).get(FragmentShareViewModel.class);
        // 观察数据变化
        viewModel.getShareData().observe(getViewLifecycleOwner(), data -> {
            tvData.setText("收到Fragment A的数据:" + data);
        });

        return view;
    }
}

4.4 3 种通信方式对比(选择指南)

通信场景推荐方式优点缺点
Fragment → Activity接口回调 / ViewModel低耦合、灵活接口回调需手动管理引用
Activity → FragmentBundle 传参 / ViewModelBundle 简单直接,ViewModel 支持动态更新Bundle 仅支持初始化传参
Fragment → FragmentViewModel 共享低耦合、无需 Activity 中转需依赖 ViewModel 组件

五、Fragment 进阶用法:ViewPager2 结合、懒加载、特殊 Fragment

5.1 ViewPager2 + Fragment(滑动切换,主流组合)

ViewPager2 是 AndroidX 提供的滑动容器,与 Fragment 结合可实现 “滑动切换 + 底部 Tab” 的经典布局(如抖音、微信公众号),支持预加载、无限滑动等功能。

示例 8:ViewPager2 + Fragment 实现滑动切换
  1. 添加 ViewPager2 依赖(AndroidX):
implementation 'androidx.viewpager2:viewpager2:1.0.0'
  1. Activity 布局(ViewPager2 + 底部 Tab):
<!-- activity_viewpager2_host.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- ViewPager2容器 -->
    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager2"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <!-- 底部Tab(3个TextView) -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tab1"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:text="页面1"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/tab2"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:text="页面2"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/tab3"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:text="页面3"
            android:textSize="16sp" />

    </LinearLayout>

</LinearLayout>
  1. 创建 Fragment 适配器(FragmentStateAdapter):
// ViewPager2的Fragment适配器(替代旧版FragmentPagerAdapter)
public class MyFragmentAdapter extends FragmentStateAdapter {
    // Fragment列表
    private List<Fragment> fragmentList;

    public MyFragmentAdapter(@NonNull FragmentActivity fragmentActivity, List<Fragment> fragments) {
        super(fragmentActivity);
        this.fragmentList = fragments;
    }

    // 返回Fragment实例
    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return fragmentList.get(position);
    }

    // 返回Fragment数量
    @Override
    public int getItemCount() {
        return fragmentList.size();
    }
}
  1. 宿主 Activity(绑定 ViewPager2 与 Tab):
public class ViewPager2HostActivity extends AppCompatActivity {
    private ViewPager2 viewPager2;
    private TextView tab1, tab2, tab3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_viewpager2_host);

        // 初始化ViewPager2和Tab
        viewPager2 = findViewById(R.id.view_pager2);
        tab1 = findViewById(R.id.tab1);
        tab2 = findViewById(R.id.tab2);
        tab3 = findViewById(R.id.tab3);

        // 初始化Fragment列表
        List<Fragment> fragments = new ArrayList<>();
        fragments.add(new Page1Fragment());
        fragments.add(new Page2Fragment());
        fragments.add(new Page3Fragment());

        // 设置适配器
        MyFragmentAdapter adapter = new MyFragmentAdapter(this, fragments);
        viewPager2.setAdapter(adapter);

        // Tab点击事件:切换ViewPager2页面
        tab1.setOnClickListener(v -> viewPager2.setCurrentItem(0));
        tab2.setOnClickListener(v -> viewPager2.setCurrentItem(1));
        tab3.setOnClickListener(v -> viewPager2.setCurrentItem(2));

        // ViewPager2滑动事件:同步更新Tab状态
        viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                // 重置所有Tab颜色
                tab1.setTextColor(Color.BLACK);
                tab2.setTextColor(Color.BLACK);
                tab3.setTextColor(Color.BLACK);
                // 选中Tab设置红色
                switch (position) {
                    case 0:
                        tab1.setTextColor(Color.RED);
                        break;
                    case 1:
                        tab2.setTextColor(Color.RED);
                        break;
                    case 2:
                        tab3.setTextColor(Color.RED);
                        break;
                }
            }
        });

        // 默认选中第一个Tab
        tab1.setTextColor(Color.RED);
    }
}
ViewPager2 核心特性:
  • 支持横向 / 纵向滑动(通过setOrientation()设置);
  • 预加载机制:默认预加载相邻 1 个 Fragment,可通过setOffscreenPageLimit()调整;
  • 懒加载:需结合FragmentTransaction.setMaxLifecycle()实现(下文讲解);
  • 适配 RecyclerView:内部基于 RecyclerView 实现,支持 RecyclerView 的所有特性。

5.2 Fragment 懒加载(按需加载数据,优化性能)

ViewPager2 默认会预加载相邻的 Fragment,导致未显示的 Fragment 也会执行onCreateView()onResume(),浪费资源(如提前请求网络)。懒加载可实现 “Fragment 显示时才加载数据”。

示例 9:ViewPager2 + Fragment 实现懒加载
  1. 创建懒加载基类 Fragment(LazyLoadFragment):
public abstract class LazyLoadFragment extends Fragment {
    private boolean isLoaded = false; // 是否已加载数据
    private boolean isVisible = false; // 是否可见
    private boolean isViewCreated = false; // 视图是否创建完成

    // 视图创建完成
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        isViewCreated = true;
        // 检查是否满足懒加载条件
        checkLazyLoad();
    }

    // Fragment可见状态变化(ViewPager2切换时触发)
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        isVisible = isVisibleToUser;
        checkLazyLoad();
    }

    // 检查懒加载条件:视图创建完成 + 可见 + 未加载数据
    private void checkLazyLoad() {
        if (isViewCreated && isVisible && !isLoaded) {
            // 执行懒加载(子类实现)
            lazyLoadData();
            isLoaded = true; // 标记为已加载
        }
    }

    // 懒加载数据(子类必须实现)
    protected abstract void lazyLoadData();

    // 重置加载状态(如Fragment被隐藏后重新显示,需再次加载)
    public void resetLazyLoad() {
        isLoaded = false;
        checkLazyLoad();
    }
}
  1. 子类 Fragment 继承基类,实现懒加载:
public class LazyPage1Fragment extends LazyLoadFragment {
    private TextView tvLazy;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_lazy_page, container, false);
        tvLazy = view.findViewById(R.id.tv_lazy);
        return view;
    }

    // 实现懒加载方法:显示时才加载数据
    @Override
    protected void lazyLoadData() {
        Log.d("LazyLoadTest", "页面1开始懒加载数据");
        // 模拟网络请求(子线程)
        new Thread(() -> {
            try {
                Thread.sleep(1000);
                // 切换到主线程更新UI
                requireActivity().runOnUiThread(() -> {
                    tvLazy.setText("页面1懒加载数据完成");
                });
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
  1. 配置 ViewPager2 适配器,设置 MaxLifecycle:
public class LazyFragmentAdapter extends FragmentStateAdapter {
    private List<LazyLoadFragment> fragmentList;

    public LazyFragmentAdapter(@NonNull FragmentActivity fragmentActivity, List<LazyLoadFragment> fragments) {
        super(fragmentActivity);
        this.fragmentList = fragments;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return fragmentList.get(position);
    }

    @Override
    public int getItemCount() {
        return fragmentList.size();
    }

    // 关键:设置Fragment的最大生命周期,控制懒加载
    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        ViewPager2 viewPager2 = (ViewPager2) recyclerView.getParent();
        viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                // 重置其他Fragment的加载状态
                for (int i = 0; i < fragmentList.size(); i++) {
                    if (i != position) {
                        fragmentList.get(i).resetLazyLoad();
                    }
                }
            }
        });
    }
}
关键说明:
  • 懒加载的核心是setUserVisibleHint()(Fragment 可见状态变化回调);
  • 基类通过isViewCreated“isVisible”“isLoaded” 三个状态控制懒加载时机;
  • 适用于网络请求、大数据加载等场景,优化 APP 启动速度和内存占用。

5.3 特殊 Fragment:DialogFragment、BottomSheetFragment

Android 提供了几种特殊的 Fragment,用于实现弹窗、底部弹窗等场景,比传统 Dialog 更灵活、易复用。

1. DialogFragment(弹窗 Fragment,替代 Dialog)

DialogFragment 是封装了 Dialog 的 Fragment,支持屏幕旋转后自动恢复,适合实现复杂弹窗(如登录弹窗、筛选弹窗)。

示例 10:DialogFragment 实现登录弹窗
public class LoginDialogFragment extends DialogFragment {
    private EditText etUsername, etPassword;
    private OnLoginListener mListener;

    // 回调接口:登录成功后通知Activity
    public interface OnLoginListener {
        void onLoginSuccess(String username);
    }

    public void setOnLoginListener(OnLoginListener listener) {
        mListener = listener;
    }

    // 设置弹窗样式(可选)
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 设置为对话框样式(无标题)
        setStyle(STYLE_NO_TITLE, android.R.style.Theme_DeviceDefault_Dialog);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // 加载弹窗布局
        View view = inflater.inflate(R.layout.fragment_login_dialog, container, false);
        etUsername = view.findViewById(R.id.et_username);
        etPassword = view.findViewById(R.id.et_password);

        // 登录按钮点击事件
        view.findViewById(R.id.btn_login).setOnClickListener(v -> {
            String username = etUsername.getText().toString().trim();
            String password = etPassword.getText().toString().trim();
            if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) {
                // 模拟登录成功
                if (mListener != null) {
                    mListener.onLoginSuccess(username);
                }
                dismiss(); // 关闭弹窗
            } else {
                Toast.makeText(getContext(), "请输入用户名和密码", Toast.LENGTH_SHORT).show();
            }
        });

        // 取消按钮点击事件
        view.findViewById(R.id.btn_cancel).setOnClickListener(v -> dismiss());

        return view;
    }

    // 设置弹窗大小(可选)
    @Override
    public void onStart() {
        super.onStart();
        Dialog dialog = getDialog();
        if (dialog != null) {
            // 设置弹窗宽度为屏幕宽度的80%
            Window window = dialog.getWindow();
            WindowManager.LayoutParams params = window.getAttributes();
            params.width = (int) (getResources().getDisplayMetrics().widthPixels * 0.8);
            window.setAttributes(params);
        }
    }
}

//  Activity中显示弹窗
public class DialogHostActivity extends AppCompatActivity implements LoginDialogFragment.OnLoginListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dialog_host);

        // 点击按钮显示登录弹窗
        findViewById(R.id.btn_show_login).setOnClickListener(v -> {
            LoginDialogFragment dialogFragment = new LoginDialogFragment();
            dialogFragment.setOnLoginListener(this);
            // 显示弹窗(必须用show()方法)
            dialogFragment.show(getSupportFragmentManager(), "login_dialog");
        });
    }

    // 登录成功回调
    @Override
    public void onLoginSuccess(String username) {
        Toast.makeText(this, "登录成功,欢迎" + username, Toast.LENGTH_SHORT).show();
    }
}
2. BottomSheetFragment(底部弹窗 Fragment)

BottomSheetFragment 是 Material Design 提供的底部弹窗 Fragment,支持滑动展开 / 收起,适合实现底部菜单、筛选列表等场景(需添加 Material 依赖):

gradle

implementation 'com.google.android.material:material:1.9.0'
public class BottomSheetFragment extends BottomSheetDialogFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_bottom_sheet, container, false);
        // 底部弹窗布局(如菜单列表)
        ListView lvMenu = view.findViewById(R.id.lv_menu);
        String[] menuItems = {"选项1", "选项2", "选项3", "选项4"};
        ArrayAdapter<String> adapter = new ArrayAdapter<>(
                getContext(),
                android.R.layout.simple_list_item_1,
                menuItems
        );
        lvMenu.setAdapter(adapter);

        // 菜单点击事件
        lvMenu.setOnItemClickListener((parent, view1, position, id) -> {
            Toast.makeText(getContext(), "选择了" + menuItems[position], Toast.LENGTH_SHORT).show();
            dismiss();
        });

        return view;
    }
}

// 显示底部弹窗
findViewById(R.id.btn_show_bottom_sheet).setOnClickListener(v -> {
    BottomSheetFragment bottomSheetFragment = new BottomSheetFragment();
    bottomSheetFragment.show(getSupportFragmentManager(), "bottom_sheet");
});

六、Fragment 常见坑点与避坑指南(实战经验总结)

6.1 坑点 1:Fragment 重叠问题(屏幕旋转后)

  • 现象:屏幕旋转后,Fragment 重复加载,出现多个 Fragment 重叠显示;
  • 原因:Activity 重建时,FragmentManager 会自动恢复已添加的 Fragment,若在 onCreate () 中再次 addFragment,会导致重复添加;
  • 解决方案:
    1. 在 addFragment 前判断是否已添加(通过 tag 查找);
    if (savedInstanceState == null) {
        // 仅在首次创建时添加Fragment,重建时不添加
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fragment_container, new TestFragment(), "test_tag")
                .commit();
    }
    
    1. 或通过findFragmentByTag()查找已存在的 Fragment,避免重复创建。

6.2 坑点 2:Fragment 获取 Activity 为 null

  • 现象:在 Fragment 的 onCreate () 中调用getActivity()返回 null;
  • 原因:Fragment 的 onCreate () 执行时,可能还未与 Activity 完全关联;
  • 解决方案:
    1. 延迟获取 Activity,在 onAttach () 或 onViewCreated () 中获取;
    2. 优先使用requireActivity()(若 Activity 为 null 会抛出异常,便于排查)而非getActivity()
    3. 保存 Activity 引用时,使用 WeakReference 避免内存泄漏:
    private WeakReference<Activity> mActivityRef;
    
    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        mActivityRef = new WeakReference<>((Activity) context);
    }
    
    // 使用时
    Activity activity = mActivityRef.get();
    if (activity != null && !activity.isFinishing()) {
        // 执行操作
    }
    

6.3 坑点 3:FragmentTransaction.commit () 报错(状态非法)

  • 现象:调用 commit () 时抛出IllegalStateException: Can not perform this action after onSaveInstanceState
  • 原因:Activity 的 onSaveInstanceState () 执行后(如按 Home 键),系统已保存状态,此时不能再提交 Fragment 事务;
  • 解决方案:
    1. commitAllowingStateLoss()替代 commit ()(允许状态丢失,适合非关键事务);
    2. 确保事务提交在 Activity 的 onSaveInstanceState () 之前(如 onCreate ()、onResume ());
    3. 避免在异步回调(如网络请求回调)中提交事务,需先判断 Activity 状态。

6.4 坑点 4:ViewPager2 预加载导致懒加载失效

  • 现象:设置了懒加载,但 ViewPager2 仍会预加载相邻 Fragment;
  • 原因:ViewPager2 默认预加载 1 个 Fragment,且 Fragment 的生命周期会提前执行;
  • 解决方案:
    1. 结合setMaxLifecycle()控制 Fragment 的生命周期状态;
    2. 使用前面讲的懒加载基类,通过setUserVisibleHint()判断可见状态。

6.5 坑点 5:Fragment 内存泄漏

  • 现象:应用退出后,LeakCanary 检测到内存泄漏;
  • 常见原因:
    1. Fragment 持有 Activity 的强引用(如非静态内部类、Handler);
    2. 线程未停止(如在 Fragment 中启动的线程未在 onDestroy () 中中断);
    3. 监听器未注销(如广播接收器、EventBus 未注销);
  • 解决方案:
    1. 避免持有 Activity 强引用,使用 WeakReference;
    2. 线程在 onDestroy () 中中断,监听器在对应的生命周期方法中注销;
    3. 非静态内部类改为静态内部类(如 Handler、Thread)。

七、总结:Fragment 核心知识点图谱

到这里,Fragment 的核心内容已经全部讲完,我们用一张图谱梳理重点:

Fragment核心知识点
├── 基础认知:模块化UI组件、与Activity的区别、典型场景
├── 生命周期:11个核心回调、与Activity联动、状态保存(onSaveInstanceState/ViewModel)
├── 加载方式:
│   - 静态加载:布局声明、简单场景
│   - 动态加载:FragmentManager/FragmentTransaction、add/replace/remove/hide/show、回退栈
├── 通信方式:
│   - Fragment→Activity:接口回调/ViewModel
│   - Activity→Fragment:Bundle传参/ViewModel
│   - Fragment→Fragment:ViewModel共享/Activity中转
├── 进阶用法:
│   - ViewPager2+Fragment:滑动切换、适配器
│   - 懒加载:按需加载数据、基类封装
│   - 特殊Fragment:DialogFragment(弹窗)、BottomSheetFragment(底部弹窗)
├── 避坑指南:重叠问题、Activity为null、事务提交错误、预加载失效、内存泄漏

其实 Fragment 的学习关键是 “理解模块化思想 + 掌握生命周期联动”—— 它的核心价值是拆分复杂页面、实现 UI 复用,只要理清生命周期的联动逻辑,掌握动态操作和通信方式,就能避开大部分坑。

把文中的示例逐个敲一遍,结合日志观察执行流程,再在实际项目中应用,很快就能熟练掌握。如果遇到具体问题,可以在评论区留言,我会第一时间回复~

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值