"setHasOptionsMenu(Boolean): Unit"已被弃用。在Java中已不建议使用。

90

我如何在Android片段(fragment)中声明菜单? 我之前使用的方法现在已过时。

原始内容:

    override fun onCreateView(...): View {
        setHasOptionsMenu(true)
    }
 override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        this.menu = menu
        inflater.inflate(R.menu.menu, this.menu)
    }

9
你是否阅读了链接到代码片段的发布说明?链接如下:https://developer.android.com/jetpack/androidx/releases/fragment#1.5.0-alpha04,其中提供了更多细节。 - ianhanniballake
谢谢您分享这个资源!@ianhanniballake - SUR4IDE
1
为什么它被弃用了?我没有看到新代码有什么特别之处... - android developer
2
如上述发布说明所述,“Fragment API 为活动的 ActionBar 提供菜单的功能已被弃用,因为它们将片段与活动紧密耦合在一起,并且无法单独进行测试。” - Fakrudeen
10个回答

129

从开发者文档中,可以通过以下方式实现:

/**
  * Using the addMenuProvider() API directly in your Activity
  **/
class ExampleActivity : ComponentActivity(R.layout.activity_example) {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Add menu items without overriding methods in the Activity
   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
      }
    })
  }
}

/**
  * Using the addMenuProvider() API in a Fragment
  **/
class 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 when (menuItem.itemId) {
                R.id.menu_clear -> {
                    // clearCompletedTasks()
                    true
                }
                R.id.menu_refresh -> {
                    // loadTasks(true)
                    true
                }
                else -> false
            }
      }
    }, viewLifecycleOwner, Lifecycle.State.RESUMED)
  }

Fragments中的setHasOptionsMenu已被弃用,请使用setHasOptionsMenu


1
其他功能如失效和获取菜单项的引用呢?它们还存在吗,还是也发生了改变? - android developer
1
@riccardogabellone 我遇到了类似的问题,但我注意到你可以将LifecycleOwner参数传递给addMenuProvider函数,这意味着在片段中我使用以下代码:activity!!.addMenuProvider(menuProviderThatYouCreated, this.viewLifecycleOwner) - android developer
以下是什么意思?://使用接口可以注入您自己的实现 - David
4
在片段中,我看到需要的是MenuHost,但实际上找到的是FragmentActivity。因此,需要进行以下翻译:val menuHost: MenuHost = requireActivity() -> 在片段中,我看到需要的是MenuHost类型的变量,但实际上找到的是FragmentActivity类型的变量。 - Dr Mido
这看起来很奇怪。你必须在活动和片段中都要填充XML吗?还是只能在片段中填充? - chitgoks
显示剩余2条评论

33

扩展@joseph-wambura和@hammad-zafar-bawara所说的,您还可以在片段中实现接口...

class MyFragment : Fragment(), MenuProvider {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        // Do stuff...
        val menuHost: MenuHost = requireActivity()
        menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
    }

    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        menuInflater.inflate(R.menu.options, menu)
        // Do stuff...
    }

    override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        // Do stuff...
        return false
    }
}

2
在Fragment中,我看到需要MenuHost,但找到的是FragmentActivity。应该如何使用它?代码示例:val menuHost: MenuHost = requireActivity() - Dr Mido
请确保您使用的是 import androidx.fragment.app.Fragment 而不是已弃用的 import android.app.Fragment - rogue
我已经使用了这个。 - Dr Mido
1
解决方案是将其转换,像这样 val menuHost: MenuHost = requireActivity() as MenuHost - Shadow
@Shadow,感谢您提供的优雅解决方案来回答这个问题。 - rogue
显示剩余4条评论

25
在Kotlin中,ActivityFragmentPreferenceFragmentCompat的声明方式。

Activity

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

   addMenuProvider(object : MenuProvider {
      override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        menuInflater.inflate(R.menu.main_menu, menu)
      }

      override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        // Handle the menu selection
        return true
      }
    })
  }
}

碎片

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

    // The usage of an interface lets you inject your own implementation
    val menuHost: MenuHost = requireActivity()

    menuHost.addMenuProvider(object : MenuProvider {
      override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        // Add menu items here
        menuInflater.inflate(R.menu.main_menu, menu)
      }

      override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        // Handle the menu selection
            return when (menuItem.itemId) {
                R.id.action_menu1 -> {
                    // todo menu1
                    true
                }
                R.id.action_menu2 -> {
                    // todo menu2
                    true
                }
                else -> false
            }
      }
    }, viewLifecycleOwner, Lifecycle.State.RESUMED)
 }

PreferenceFragmentCompat

val menuHost: MenuHost = requireHost() as MenuHost
//Same declaration with Fragment

使用MenuProvider接口

class FirstFragment : Fragment(), MenuProvider {
 
      override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
              val menuHost: MenuHost = requireActivity()
              menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
      }
 
      override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
        // Add menu items here
        menuInflater.inflate(R.menu.second_menu, menu)
      }
 
      override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
        // Handle the menu selection
            return when (menuItem.itemId) {
                R.id.menu_clear -> {
                    // Do stuff...
                    true
                }
                R.id.menu_refresh -> {
                    // Do stuff...
                    true
                }
                else -> false
            }
      }, viewLifecycleOwner, Lifecycle.State.RESUMED)
}

我尝试了,但 val menuHost: MenuHost = requireActivity() 出现错误: 类型不匹配:推断类型为 FragmentActivity,但期望的是 MenuHost - Joshua
1
添加了 implementation 'androidx.activity:activity-compose:1.5.1' 后,错误消失了,谢谢。 - Joshua
请问这两个东西的作用是什么?viewLifecycleOwnerLifecycle.State.RESUMED - Ahmed Elsayed
通过这个参数,你可以告诉菜单在片段的生命周期中什么时候应该可见(在这种情况下是恢复状态)。 - Codelaby

13

JAVA代码

Activity中的选项菜单

addMenuProvider(new MenuProvider() {
            @Override
            public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
                menuInflater.inflate(R.menu.bottom_nav_menu, menu);

                // Add menu options here

            }

            @Override
            public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {

                // Handle Menu Options Selection Here

                return false;
            }
        });


在片段中的选项菜单
requireActivity().addMenuProvider(new MenuProvider() {
            @Override
            public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
                menuInflater.inflate(R.menu.bottom_nav_menu, menu);
                
                // Add option Menu Here
                
            }

            @Override
            public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
                return false;
                
                // Handle option Menu Here
                
            }
        }, viewLifecycleOwner, Lifecycle.State.RESUMED);

在 Fragment 中的哪个方法中应该使用 requireActivity().addMenuProvider - Dr Mido
我正在使用它在方法“onCreateView”中。 - Hammad Zafar Bawara
在调用 requireActivity() 后,我遇到了“未解决的引用:addMenuProvider”错误,似乎没有这样的方法。 - Dr Mido
1
@Dr Mido 请尝试。MenuHost menuHost = requireActivity();。menuHost.addMenuProvider(); - chisom emmanuel
谢谢,我尝试了,但是 val menuHost: MenuHost = requireActivity() 报错:类型不匹配:推断类型为 FragmentActivity,但期望的是 MenuHost - Joshua
1
在添加了 implementation 'androidx.activity:activity-compose:1.5.1' 后,错误消失了,谢谢。 - Joshua

8
如果您正在使用Jetpack NavigationUI,则需要setSupportActionBar(toolbar),否则菜单将不会存在。

1
正确。如果使用导航图,则这是必须的。否则,MenuProvider将无法工作。 - pravingaikwad07

4

这对我在onCreateView方法上有所帮助:

requireActivity().addMenuProvider(new MenuProvider() {
        @Override
        public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
            menuInflater.inflate(R.menu.bottom_nav_menu, menu);
            
            // Add option Menu Here
            
        }

        @Override
        public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
                           
            // Handle option Menu Here
             return false;
        }
    }, getViewLifecycleOwner, Lifecycle.State.RESUMED);

1
如果menuprovider在主活动中,我只想在片段处于视图/聚焦状态时单击菜单项时添加一些代码,该怎么办?我是否还需要在onCreateMenu中膨胀? - chitgoks

3

我不确定为什么所有的答案都建议在addMenuProvider调用中传递Lifecycle.State.RESUME作为生命周期状态。这会使菜单在片段暂停时消失,当片段暂停但仍可见时,这看起来并不好。

例如,通过点击菜单项显示对话框将使菜单消失。当对话框被解除时,它将重新出现。

对于大多数情况,更好的生命周期状态是传递Lifecycle.State.CREATE,这只会在视图被销毁时删除菜单。这也是默认行为,因此您可以简单地省略生命周期状态。


2

对于那些对Java感到有所回忆的人,这里有一个更好的方式,使用这个扩展函数:

fun Fragment.addMenuProvider(@MenuRes menuRes: Int, callback: (id: Int) -> Boolean) {
    val menuProvider = object : MenuProvider {
        override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
            menuInflater.inflate(menuRes, menu)
        }

        override fun onMenuItemSelected(menuItem: MenuItem) = callback(menuItem.itemId)

    }
    (requireActivity() as MenuHost).addMenuProvider(
        menuProvider,
        viewLifecycleOwner,
        Lifecycle.State.RESUMED
    )
}

以及使用方法:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    addMenuProvider(R.menu.feed_fragment) {
        when (it) {
            R.id.menu_write_post -> viewModel.goToPostCreation()
            R.id.menu_filter -> viewModel.goToFilter()
            else -> false
        }
    }
}

如果需要功能标志,则可以多次重复调用addMenuProvider


1
这实际上非常酷,因为回调在达到onPause时会自动删除。 - Andre Thiele

0

在Java的Fragment中,我尝试了这个方法。对我来说运行得很好。

 @Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.layout_example, container, false);

    toolbar = (Toolbar)view.findViewById(R.id.toolbar_1);
    ((AppCompatActivity)getActivity()).setSupportActionBar(toolbar);
    MenuHost menuHost = requireActivity();
    menuHost.addMenuProvider(new MenuProvider() {
        @Override
        public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
            menuInflater.inflate(R.menu.menu_search,menu);
        }

        @Override
        public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {

            if (menuItem.getItemId() == R.id.search_friend){
                Toast.makeText(getActivity(), "friends", Toast.LENGTH_SHORT).show();
                return true;
            }
            else return false;
        }
    },getViewLifecycleOwner(), Lifecycle.State.RESUMED);


    return view;
}

你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Community

0

除了以上的答案,我更喜欢创建一个扩展函数来减少样板代码:

扩展函数实现:

fun Fragment.setupMenu(@MenuRes menuId: Int, onMenuSelected: (MenuItem) -> Boolean) =
(requireActivity() as MenuHost).addMenuProvider(object : MenuProvider {
    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) =
        menuInflater.inflate(menuId, menu)

    override fun onMenuItemSelected(menuItem: MenuItem) = onMenuSelected(menuItem)
}, viewLifecycleOwner, Lifecycle.State.RESUMED)

这样,你就能确保始终添加了 lifeCycleOwner 并传递了正确的 生命周期状态,即 Lifecycle.State.RESUMED

然后,在片段中调用时,你只需简单地使用它:

setupMenu(R.menu.menu_save) { menuItem ->
    when (menuItem.itemId) {
        R.id.save -> {
            viewModel.onSaveClicked()
            true
        }
        else -> false
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接