Android日积月累系列之六-Activity与Fragment和Fragment与Fragment通信

573 阅读6分钟

Android日积月累系列之六-Activity与Fragment和Fragment与Fragment通信

为了重复使用 Fragment,请将每个 Fragment 构建为一个完全独立的组件,使其定义自己的布局和行为。定义这些可重复使用的 Fragment 后,您可以将其与 Activity 关联,并将其与应用逻辑联系起来,以实现整体复合界面。

为了正确响应用户事件,或为了共享状态信息,通常需要在 activity 与其 fragment 之间或者两个或更多 fragment 之间具有通信渠道。为使 Fragment 保持独立,您不应让 Fragment 直接与其他 Fragment 或与其宿主 Activity 进行通信。

Fragment 库提供了两个通信选项:共享的 ViewModel 和 Fragment Result API。建议的选项取决于用例。如需与任何自定义 API 共享持久性数据,您应使用 ViewModel。对于包含的数据可放置在 Bundle 中的一次性结果,您应使用 Fragment Result API。

1.ViewModel作用范围

ViewModel可能在一个Activity或Fragment内使用,也可以指定其作用范围,以在多个Activity/Fragment里共享同一对象,实现Activity和Fragment间通信。

以kotlin为例

1.1 Activity和Fragment共享同一个ViewModel实例

关键点

 private val viewModel: ItemViewModel by activityViewModels()
class MainActivity : AppCompatActivity() {
    // Using the viewModels() Kotlin property delegate from the activity-ktx
    // artifact to retrieve the ViewModel in the activity scope
    private val viewModel: ItemViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.selectedItem.observe(this, Observer { item ->
            // Perform an action with the latest item data
        })
    }
}

class ListFragment : Fragment() {
    // Using the activityViewModels() Kotlin property delegate from the
    // fragment-ktx artifact to retrieve the ViewModel in the activity scope
    private val viewModel: ItemViewModel by activityViewModels()

    // Called when the item is clicked
    fun onItemClicked(item: Item) {
        // Set a new item
        viewModel.selectItem(item)
    }
}

1.2 多个Fragment之间共享同一个ViewModel实现通讯

同一 activity 中的两个或更多 fragment 通常需要相互通信。例如,假设有一个 Fragment 显示一个列表,另一个 Fragment 允许用户对该列表应用各种过滤器。如果 Fragment 不直接通信(这意味着,它们不再独立),这种情况可能不容易实现。此外,这两个 fragment 还必须处理另一个 fragment 尚未创建或不可见的情况。

这两个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理这种通信。通过以这种方式共享 ViewModel,Fragment 不需要相互了解,Activity 也不需要执行任何操作来促进通信。

以下示例展示了两个 Fragment 如何使用共享的 ViewModel 进行通信:

请注意,这两个 Fragment 都将其宿主 Activity 用作 ViewModelProvider 的范围。由于这两个 Fragment 使用同一范围,因此它们会收到 ViewModel 的同一实例,这使它们能够来回通信。

注意ViewModel 会一直在内存中,直到其范围限定到的 ViewModelStoreOwner 永久消失。在一个 Activity 架构中,如果 ViewModel 的范围限定为 Activity,那么它本质上是单例。首次实例化 ViewModel 之后,使用 activity 范围检索 ViewModel 的后续调用始终返回相同的现有 ViewModel 以及现有数据,直到 activity 的生命周期永久结束。

class ListViewModel : ViewModel() {
    val filters = MutableLiveData<Set<Filter>>()

    private val originalList: LiveData<List<Item>>() = ...
    val filteredList: LiveData<List<Item>> = ...

    fun addFilter(filter: Filter) { ... }

    fun removeFilter(filter: Filter) { ... }
}

class ListFragment : Fragment() {
    // Using the activityViewModels() Kotlin property delegate from the
    // fragment-ktx artifact to retrieve the ViewModel in the activity scope
    private val viewModel: ListViewModel by activityViewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
            // Update the list UI
        }
    }
}

class FilterFragment : Fragment() {
    private val viewModel: ListViewModel by activityViewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filters.observe(viewLifecycleOwner, Observer { set ->
            // Update the selected filters UI
        }
    }

    fun onFilterSelected(filter: Filter) = viewModel.addFilter(filter)

    fun onFilterDeselected(filter: Filter) = viewModel.removeFilter(filter)
}

1.3 在父 Fragment 与子 Fragment 之间共享数据

使用子 Fragment 时,父 Fragment 及其子 Fragment 可能需要相互共享数据。如需在这些 Fragment 之间共享数据,请将父 Fragment 用作 ViewModel 范围。

class ListFragment: Fragment() {
    // Using the viewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel
    private val viewModel: ListViewModel by viewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
            // Update the list UI
        }
    }
}

class ChildFragment: Fragment() {
    // Using the viewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel using the parent fragment's scope
    private val viewModel: ListViewModel by viewModels({requireParentFragment()})
    ...
}

1.4 将 ViewModel 的范围限定为导航图 实现共享通信

如果您使用的是 Navigation 库,还可以将 ViewModel 的范围限定为目的地的 NavBackStackEntry 的生命周期。例如,可以将 ViewModel 的范围限定为 ListFragmentNavBackStackEntry

class ListFragment: Fragment() {
    // Using the navGraphViewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel using the NavBackStackEntry scope
    // R.id.list_fragment == the destination id of the ListFragment destination
    private val viewModel: ListViewModel by navGraphViewModels(R.id.list_fragment)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { item ->
            // Update the list UI
        }
    }
}

2 使用 Fragment Result API 获取结果

在某些情况下,您可能要在 Fragment 之间或 Fragment 与其宿主 Activity 之间传递一次性值。例如,您可能有一个 fragment,它读取二维码,并将数据传回之前的 fragment。在 fragment 版本 1.3.0 及更高版本中,每个 FragmentManager 都实现了 FragmentResultOwner。这意味着,FragmentManager 可以充当 fragment 结果的集中存储区。此更改通过设置 Fragment 结果并监听这些结果而不要求组件直接相互引用,让这些组件能够相互通信。

2.1使用 Fragment Result API 在 Fragment 之间传递结果

如需将数据从 Fragment B 传回 Fragment A,请先在 Fragment A(即接收结果的 Fragment)上设置结果监听器。对 Fragment A 的 FragmentManager 调用 setFragmentResultListener(),如以下示例所示:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact
    setFragmentResultListener("requestKey") { requestKey, bundle ->
        // We use a String here, but any type that can be put in a Bundle is supported
        val result = bundle.getString("bundleKey")
        // Do something with the result
    }
}

在 Fragment B(即生成结果的 Fragment)中,您必须使用相同的 requestKey 在同一 FragmentManager 上设置结果。您可以使用 setFragmentResult() API 来完成此操作:

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

然后,一旦 Fragment A 处于 STARTED 状态,它就会收到结果并执行监听器回调。

对于给定的键,只能有一个监听器和结果。如果您对同一个键多次调用 setFragmentResult(),并且监听器未处于 STARTED 状态,则系统会将所有待处理的结果替换为更新后的结果。如果您设置的结果没有相应的监听器来接收,则结果会存储在 FragmentManager 中,直到您设置一个具有相同键的监听器。监听器收到结果并触发 onFragmentResult() 回调后,结果会被清除。这种行为有两个主要影响:

  • 返回堆栈上的 Fragment 只有在被弹出且处于 STARTED 状态之后才会收到结果。
  • 如果在设置结果时监听结果的 Fragment 处于 STARTED 状态,则会立即触发监听器的回调。

fragment-ktx封装的FragmentResult的几个方法,都是用到了parentFragmentManager

public fun Fragment.setFragmentResult(requestKey: String, result: Bundle) {
    parentFragmentManager.setFragmentResult(requestKey, result)
}

public fun Fragment.clearFragmentResult(requestKey: String) {
    parentFragmentManager.clearFragmentResult(requestKey)
}


public fun Fragment.setFragmentResultListener(requestKey: String,listener: ((requestKey: String, bundle: Bundle) -> Unit)) {
    parentFragmentManager.setFragmentResultListener(requestKey, this, listener)
}


public fun Fragment.clearFragmentResultListener(requestKey: String) {
    parentFragmentManager.clearFragmentResultListener(requestKey)
}

2.2 在父 Fragment 与子 Fragment 之间传递结果

如需将结果从子 Fragment 传递到父 Fragment,父 Fragment 在调用 setFragmentResultListener() 时应使用 getChildFragmentManager() 而不是 getParentFragmentManager()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // We set the listener on the child fragmentManager
    childFragmentManager.setFragmentResultListener("requestKey") { key, bundle ->
        val result = bundle.getString("bundleKey")
        // Do something with the result
    }
}

子 Fragment 在其 FragmentManager 上设置结果。然后,一旦父 Fragment 处于 STARTED 状态,它就会收到结果:

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

2.3 在宿主 Activity 中接收结果Fragment里的结果

如需在宿主 activity 中接收 fragment 结果,请使用 getSupportFragmentManager() 在 fragment 管理器上设置结果监听器。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportFragmentManager
                .setFragmentResultListener("requestKey", this) { requestKey, bundle ->
            // We use a String here, but any type that can be put in a Bundle is supported
            val result = bundle.getString("bundleKey")
            // Do something with the result
        }
    }
}

3.参考资料

链接