使用新的菜单提供程序 API 弃用“setHasOptionsMenu”后,隐藏 Fragment 中的菜单项并在导航返回上再次显示它们

the*_*ear 11 android android-menu android-fragments

大约一个月前,Android 团队弃用了onCreateOptionsMenuonOptionsItemSelected,以及setHasOptionsItemMenu. 不幸的是,这破坏了我的所有代码。

我的应用程序有很多片段,当用户导航到它们时,我总是确保菜单项会消失并在返回时重新出现,代码如下:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setHasOptionsMenu(true)
}
Run Code Online (Sandbox Code Playgroud)
override fun onPrepareOptionsMenu(menu: Menu) {
    super.onPrepareOptionsMenu(menu)
    menu.clear()
}
Run Code Online (Sandbox Code Playgroud)

这段代码运行良好并且非常简单。现在 Android 团队已弃用(为什么?)setHasOptionsMenu,我无法重新创建此代码。

我了解用于扩充菜单项和处理菜单项单击事件的新语法,尽管我一生都无法弄清楚如何将菜单隐藏在片段中,然后使用新菜单在导航返回时再次显示它提供商 API。

这是我尝试过的:

导航到片段:

if (supportFragmentManager.backStackEntryCount == 0) {
            supportFragmentManager.commit {
                replace(R.id.activityMain_primaryFragmentHost, NewProjectFragment.newInstance(mainSpotlight != null))
                addToBackStack(null)
            }
        }
Run Code Online (Sandbox Code Playgroud)

getRootMenuProvider接口中的函数ActivityFragment

interface ActivityFragment {
    val title: String

    companion object {
        fun getRootMenuProvider() = object : MenuProvider {
            override fun onPrepareMenu(menu: Menu) {
                for (_menuItem in menu.children) {
                    _menuItem.isVisible = false
                }
            }

            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
            }

            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
                return false
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用该getRootMenuProvider功能:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val menuHost: MenuHost = requireActivity()
        menuHost.addMenuProvider(ActivityFragment.getRootMenuProvider())
    }
Run Code Online (Sandbox Code Playgroud)

MainActivity(尝试将菜单项恢复到之前的状态):

    override fun onPrepareOptionsMenu(menu: Menu): Boolean {
        for (_menu in menu.children) {
            _menu.isVisible = true
        }

        return super.onPrepareOptionsMenu(menu)
    }

    override fun onBackPressed() {
        super.onBackPressed()
        findViewById<BottomNavigationView>(R.id.activityMain_bottomNavigationView)?.visibility = View.VISIBLE
        invalidateOptionsMenu()
    }
Run Code Online (Sandbox Code Playgroud)

这会隐藏片段中的项目,但导航回来后这些项目仍然保持隐藏状态,直到用户通过旋转屏幕或执行类似操作重新加载活动。

如何使用新的菜单提供程序 API隐藏片段中的菜单项并在导航返回时重新显示它们?

ian*_*ake 20

短期

一切都“崩溃”的原因是因为您假设menu.clear()片段菜单调用的调度是在您的活动添加了自己的菜单项之后发生的。现在,当您的活动调用时,片段会经历菜单调用的调度,super.onCreateOptionsMenu()或者super.onPrepareOptionsMenu()您经常可以通过将其作为您的覆盖调用的最后一个而不是第一个来“解决”您的问题。

长期

事实上,您做错了很多:由 Activity 控制的全局菜单是共享资源,任何单独的片段都不应该手动清除整个菜单。这会破坏活动菜单项、子片段菜单项以及其他片段的菜单项。只有膨胀某些菜单项的组件才应该接触这些特定的菜单项。

因此,要解决您的问题,您应该遵循Activity 1.4.0-alpha01 发行说明(该版本添加了MenuHostMenuProvider集成到 Activity 层中:

AndroidX ComponentActivity[及其子类FragmentActivityAppCompatActivity] 现在实现了该MenuHost接口。这允许任何组件通过向活动ActionBar添加实例来添加菜单项。每个菜单项都可以选择添加一个生命周期,该生命周期将根据状态自动控制这些菜单项的可见性,并在销毁处理删除。MenuProviderMenuProviderLifecycleMenuProviderLifecycle

他们继续展示其在片段中的用法示例:

/**
  * Using the addMenuProvider() API in a Fragment
  **/
ExampleFragment : Fragment(R.layout.fragment_example) {

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    // The usage of an interface lets you inject your own implementation
    val menuHost: MenuHost = requireActivity()
  
    // Add menu items without using the Fragment Menu APIs
    // Note how we can tie the MenuProvider to the viewLifecycleOwner
    // and an optional Lifecycle.State (here, RESUMED) to indicate when
    // the menu should be visible
    menuHost.addMenuProvider(object : MenuProvider {
      override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        // Add menu items here
        menuInflater.inflate(R.menu.example_menu, menu)
      }

      override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        // Handle the menu selection
        return true
      }
    }, viewLifecycleOwner, Lifecycle.State.RESUMED)
  }
Run Code Online (Sandbox Code Playgroud)

这特别展示了三件事:

  1. 单个MenuProvider应该只触摸其菜单项。您永远不应该“清除所有菜单项”或任何影响其他组件菜单项的内容。
  2. 通过addMenuProvider使用生命周期进行调用(在本例中,是片段视图的生命周期 - 即仅当片段视图在屏幕上时才存在),那么当片段视图被销毁时(当您的调用发生时),您将自动replace隐藏菜单项并在片段视图重新出现时(即弹出后堆栈时)自动重新显示。
  3. 控制菜单项和可见性的片段本身Lifecycle应该是创建和处理其自己的菜单项的片段。您的活动(可以添加自己的活动MenuProvider,如其他示例中所示)应该只添加活动整个生命周期中存在的菜单项(在所有片段上可见的项目)。