Android开发-Fragment
一、什么是 Fragment?
1. 核心概念
- Fragment 是一个可以嵌入在
Activity
中的 UI 片段。 - 它拥有自己的布局、生命周期和事件处理逻辑。
- 一个 Activity 可以包含多个 Fragment,一个 Fragment 也可以在多个 Activity 中复用。
2. 为什么使用 Fragment?
场景 | 优势 |
---|---|
大屏适配 | 在平板上并排显示列表 + 详情页 |
Tab 切换 | ViewPager + Fragment 实现标签页 |
组件化 | 将功能模块(如设置、消息)独立封装 |
状态保留 | 横竖屏切换时自动保存 UI 状态 |
二、Fragment 的生命周期
Fragment 的生命周期比 Activity 更复杂,因为它依赖于宿主 Activity 并可能被动态添加/移除。
1. 生命周期图谱
onAttach() → onCreate() → onCreateView() → onActivityCreated()→ onStart() → onResume()↗ (用户交互) ↘
onPause() ← onStop() ← onDestroyView() ← onDestroy() ← onDetach()
2. 关键回调详解
方法 | 说明 | 注意事项 |
---|---|---|
onAttach() | Fragment 与 Activity 关联 | 可获取 Activity 引用 |
onCreate() | 初始化 Fragment | 避免耗时操作 |
onCreateView() | 创建并返回 UI 布局 | inflate 布局文件 |
onViewCreated() | View 创建完成 | 初始化 View(如 findViewById) |
onActivityCreated() | Activity 的 onCreate() 完成 | 可安全调用 Activity 方法 |
onStart() / onResume() | 可见、可交互 | 类似 Activity |
onPause() / onStop() | 不可见 | 释放资源 |
onDestroyView() | View 被销毁 | 清理 View 相关资源(如 ButterKnife.unbind) |
onDestroy() | Fragment 销毁 | 最终清理 |
onDetach() | 与 Activity 解除关联 | 置空 Activity 引用 |
⚠️ 重点:
onDestroyView()
和onDestroy()
可能不同时触发。例如,replace()
操作会触发onDestroyView()
但不立即触发onDestroy()
。
三、创建与使用 Fragment
1. 定义 Fragment
public class NewsListFragment extends Fragment {private RecyclerView recyclerView;private NewsAdapter adapter;// 必须提供空参构造函数public NewsListFragment() {}@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {// 加载布局return inflater.inflate(R.layout.fragment_news_list, container, false);}@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);// 初始化 ViewrecyclerView = view.findViewById(R.id.recycler_view);adapter = new NewsAdapter();recyclerView.setAdapter(adapter);recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));loadData();}private void loadData() {// 模拟加载数据List<News> newsList = NewsRepository.getNews();adapter.submitList(newsList);}
}
2. 在 Activity 中静态添加(不推荐)
<!-- activity_main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><fragmentandroid:name="com.example.NewsListFragment"android:id="@+id/fragment_news"android:layout_width="match_parent"android:layout_height="match_parent" /></LinearLayout>
❌ 缺点:无法动态管理,不灵活。
四、动态管理 Fragment(推荐)
使用 FragmentManager
和 FragmentTransaction
动态添加、替换、移除 Fragment。
1. 在 Activity 布局中预留容器
<FrameLayoutandroid:id="@+id/fragment_container"android:layout_width="match_parent"android:layout_height="match_parent" />
2. 使用 FragmentTransaction
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (savedInstanceState == null) {// 首次创建,添加 FragmentgetSupportFragmentManager().beginTransaction().add(R.id.fragment_container, new NewsListFragment()).commit();}}
}
3. 常用操作
FragmentManager fm = getSupportFragmentManager();// 添加
fm.beginTransaction().add(R.id.container, fragment).commit();// 替换(常用)
fm.beginTransaction().replace(R.id.container, newFragment).addToBackStack("tag") // 加入返回栈.commit();// 移除
fm.beginTransaction().remove(fragment).commit();// 隐藏/显示(切换)
fm.beginTransaction().hide(fragment1).show(fragment2).commit();
✅ 最佳实践:
- 使用
replace()
实现页面切换。- 调用
addToBackStack()
实现返回键导航。- 调用
commit()
提交事务。
五、Fragment 之间的通信
Fragment 不应直接相互调用,应通过 宿主 Activity 或 ViewModel 进行通信。
方案 1:通过 Activity 通信(传统方式)
(1) 定义接口
public interface OnNewsSelectedListener {void onNewsSelected(News news);
}
(2) Fragment 中触发事件
// NewsListFragment.java
private OnNewsSelectedListener listener;@Override
public void onAttach(@NonNull Context context) {super.onAttach(context);if (context instanceof OnNewsSelectedListener) {listener = (OnNewsSelectedListener) context;} else {throw new RuntimeException(context + " must implement OnNewsSelectedListener");}
}// 在点击时调用
listener.onNewsSelected(selectedNews);
(3) Activity 实现接口并转发
public class MainActivity extends AppCompatActivity implements OnNewsSelectedListener {@Overridepublic void onNewsSelected(News news) {NewsDetailFragment detailFragment = NewsDetailFragment.newInstance(news);getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, detailFragment).addToBackStack(null).commit();}
}
方案 2:使用 ViewModel(推荐 - Jetpack)
(1) 创建共享 ViewModel
public class SharedViewModel extends ViewModel {private final MutableLiveData<News> selectedNews = new MutableLiveData<>();public LiveData<News> getSelectedNews() {return selectedNews;}public void selectNews(News news) {selectedNews.setValue(news);}
}
(2) Fragment 中观察数据
// NewsDetailFragment.java
public class NewsDetailFragment extends Fragment {private SharedViewModel viewModel;@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);viewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);viewModel.getSelectedNews().observe(getViewLifecycleOwner(), news -> {if (news != null) {displayNews(news);}});}
}
(3) 另一个 Fragment 发送数据
// NewsListFragment.java
viewModel.selectNews(selectedNews); // 发送选中新闻
✅ 优势:解耦、生命周期安全、支持配置变更。
六、与 Jetpack Navigation 集成
现代 Android 开发推荐使用 Navigation 组件统一管理 Fragment 导航。
1. 添加依赖
implementation "androidx.navigation:navigation-fragment:2.7.7"
implementation "androidx.navigation:navigation-ui:2.7.7"
2. 定义导航图(nav_graph.xml)
<navigation xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/nav_graph"app:startDestination="@id/newsListFragment"><fragmentandroid:id="@+id/newsListFragment"android:name="com.example.NewsListFragment"android:label="新闻列表"><actionandroid:id="@+id/action_to_detail"app:destination="@id/newsDetailFragment" /></fragment><fragmentandroid:id="@+id/newsDetailFragment"android:name="com.example.NewsDetailFragment"android:label="新闻详情" />
</navigation>
3. 使用 Navigation Controller
// 在 Fragment 中跳转
findNavController().navigate(R.id.action_to_detail);// 带参数跳转
Bundle bundle = new Bundle();
bundle.putParcelable("news", selectedNews);
findNavController().navigate(R.id.action_to_detail, bundle);
七、最佳实践与避坑指南
永远不要在 Fragment 构造函数中传递参数
✅ 使用newInstance()
静态工厂方法:public static NewsDetailFragment newInstance(News news) {Bundle args = new Bundle();args.putParcelable("news", news);NewsDetailFragment fragment = new NewsDetailFragment();fragment.setArguments(args);return fragment; }
使用
requireContext()
/requireActivity()
替代getContext()
/getActivity()
,避免空指针。在
onDestroyView()
中清理 View 引用
防止内存泄漏。使用
ViewLifecycleOwner
观察 LiveData 时使用getViewLifecycleOwner()
而非getLifecycleOwner()
。避免在
onCreate()
中执行耗时操作
八、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!