03-Fragment通信
Fragment 通信
1. 不推荐直接互相调用
旧资料中常见写法:
val activity = getActivity() as MainActivity
activity.methodA()
或者:
val fragmentA = activity.supportFragmentManager.findFragmentByTag("A") as FragmentA
fragmentA.doSomething()
这种写法能用,但缺点明显:
- Fragment 和 Activity 强耦合。
- Fragment 之间互相知道对方,复用性差。
getActivity()可能为 null。- 页面重建、返回栈、异步回调时容易出错。
现代写法更推荐以下两种:
持续共享数据:Shared ViewModel
一次性结果传递:Fragment Result API
2. Fragment 与 Activity 通信
2.1 使用接口回调
这是传统写法,适合简单事件通知。
class MenuFragment : Fragment() {
interface Callback {
fun onMenuSelected(id: Long)
}
private var callback: Callback? = null
override fun onAttach(context: Context) {
super.onAttach(context)
callback = context as? Callback
?: error("Host Activity must implement MenuFragment.Callback")
}
override fun onDetach() {
callback = null
super.onDetach()
}
private fun onItemClick(id: Long) {
callback?.onMenuSelected(id)
}
}
Activity 实现接口:
class MainActivity : AppCompatActivity(), MenuFragment.Callback {
override fun onMenuSelected(id: Long) {
// 处理事件
}
}
优点:简单直接。
缺点:Activity 必须实现接口,耦合仍然存在。
2.2 使用 Activity 级 ViewModel
适合多个 Fragment 与 Activity 共享状态。
class SharedViewModel : ViewModel() {
private val _selectedId = MutableLiveData<Long>()
val selectedId: LiveData<Long> = _selectedId
fun select(id: Long) {
_selectedId.value = id
}
}
Fragment A 设置数据:
class ListFragment : Fragment(R.layout.fragment_list) {
private val vm: SharedViewModel by activityViewModels()
fun onItemClick(id: Long) {
vm.select(id)
}
}
Fragment B 观察数据:
class DetailFragment : Fragment(R.layout.fragment_detail) {
private val vm: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
vm.selectedId.observe(viewLifecycleOwner) { id ->
// 更新详情页
}
}
}
3. Fragment 与 Fragment 通信
3.1 共享 ViewModel
适合:
- Tab 页面之间共享筛选条件。
- 列表页和详情页共享当前选中项。
- 多个 Fragment 共同维护一个页面状态。
如果是同一个 Activity 下的多个 Fragment:
private val vm: SharedViewModel by activityViewModels()
如果是父子 Fragment 之间共享:
private val vm: SharedViewModel by viewModels({ requireParentFragment() })
3.2 Fragment Result API
适合一次性结果,例如:
- 选择城市后返回城市 ID。
- 弹窗选择日期后返回日期。
- 子页面完成输入后返回表单数据。
接收方:
parentFragmentManager.setFragmentResultListener(
"request_city",
viewLifecycleOwner
) { _, bundle ->
val cityId = bundle.getString("city_id")
// 处理结果
}
发送方:
parentFragmentManager.setFragmentResult(
"request_city",
bundleOf("city_id" to "shanghai")
)
4. 父 Fragment 与子 Fragment 通信
父 Fragment 中获取子 FragmentManager:
childFragmentManager.commit {
replace(R.id.child_container, ChildFragment())
}
子 Fragment 获取父 Fragment:
val parent = parentFragment as? ParentFragment
但除非非常简单,否则依旧建议用:
- 父 Fragment 作用域的 ViewModel。
- Fragment Result API。
- 明确的接口回调。
5. 通信方式对比
| 方式 | 适合场景 | 优点 | 缺点 |
|---|---|---|---|
| 直接调用 Activity 方法 | 简单 Demo、老项目 | 快 | 强耦合,易空指针 |
| 接口回调 | Fragment 通知宿主事件 | 清晰 | 宿主必须实现接口 |
| Shared ViewModel | 持续共享状态 | 解耦,适合架构化 | 需要 ViewModel 体系 |
| Fragment Result API | 一次性返回结果 | 轻量,生命周期安全 | 只适合 Bundle 类型数据 |
| 直接 findFragment 调方法 | 特殊场景 | 直接 | 强耦合,不推荐 |
6. 面试回答模板
问题:Fragment 之间怎么通信?
可以这样回答:
老写法可以通过宿主 Activity 中转,或者通过 FragmentManager 找到另一个 Fragment 再调用方法,但这会造成强耦合。现代 Android 更推荐 Shared ViewModel 或 Fragment Result API。持续共享状态用 ViewModel,一次性返回结果用 Fragment Result API。Fragment 与 Activity 通信也可以使用接口回调,但大型项目里更推荐架构层的数据共享。