在Android中如何为Fragment添加选项菜单

458

我正在尝试从一组片段中向选项菜单添加一个项目。

我创建了一个新的MenuFragment类,并将其扩展到我希望在其中包含菜单项的片段。以下是代码:

Java:

public class MenuFragment extends Fragment {

    MenuItem fav;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        fav = menu.add("add");
        fav.setIcon(R.drawable.btn_star_big_off);
    }
}

Kotlin:

class MenuFragment : Fragment {

    lateinit var fav: MenuItem

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        setHasOptionsMenu(true)
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        fav = menu.add("add");
        fav.setIcon(R.drawable.btn_star_big_off);
    }
}

由于某些原因,onCreateOptionsMenu 方法似乎未运行。


也许这是一个愚蠢的问题...你按菜单按钮对吧? - Ovidiu Latcu
2
是的,我已经按了菜单按钮,我也尝试过使用和不使用 fav.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);。 - misterbassman
嗨,也许这个线程可以帮助你,或者查看api演示以获取一个可行的例子。 - kameny
https://droidmentor.com/how-to-use-fragment-specific-menu-in-android/ - the_prole
自 androidx.activity:activity:1.4.0 以来,有一种新的方法可以实现这个。请查看我的新答案:https://dev59.com/MGsy5IYBdhLWcg3wzBJ4#73080607 - Gesit
25个回答

682

调用父类方法:

Java:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // TODO Add your menu entries here
        super.onCreateOptionsMenu(menu, inflater);
    }

Kotlin:

    override fun void onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        setHasOptionsMenu(true)
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        // TODO Add your menu entries here
        super.onCreateOptionsMenu(menu, inflater)
    }

在代码中加入日志语句,以查看方法是否被调用或者菜单是否被您的代码修改。

同时确保在onCreate(Bundle)中调用setHasOptionsMenu(boolean),以通知片段应参与选项菜单处理。


2
感谢帮助,我添加了super方法,并意识到我之前移除了@Override,所以重新加上了这个注解。Eclipse报错后,我将MenuInflater的导入语句替换为import android.view.MenuInflater;而不是import android.support.v4.view.MenuInflater;现在一切正常工作。 - misterbassman
197
两次都遗漏了在Fragment的onCreate中调用setHasOptionsMenu(true),导致问题出现。 - joshkendrick
7
我正在将我的Activity转移到Fragment上,然后遇到了这个问题。该方法的签名已经从public boolean变成了public void,并且参数也发生了改变。请确保注意到这一点! - you786
15
请注意,Fragment.onCreateOptionsMenu(Menu, MenuInflater)方法是一个空方法,因此完全不需要调用super。这里唯一的错误是错误的方法签名,可能还有一个在onCreate()中缺少setHasOptionsMenu()的问题。 - cmende
6
请用inflater.inflate(R.menu.menu_custom, menu)替换super.onCreateOptionsMenu方法。 - Code_Worm
显示剩余9条评论

218

我遇到了同样的问题,但我认为更好的方法是总结并介绍最后一步使其正常工作:

  1. 在你的Fragment的onCreate(Bundle savedInstanceState)方法中添加setHasOptionsMenu(true)方法。

  2. 覆盖你的Fragment的onCreateOptionsMenu(Menu menu, MenuInflater inflater)方法(如果你想在Fragment的菜单中进行不同的操作),并在你的Fragment中覆盖onOptionsItemSelected(MenuItem item)方法。

  3. 在你的onOptionsItemSelected(MenuItem item)活动方法中,确保当菜单项操作将在onOptionsItemSelected(MenuItem item) Fragment方法中被实现时返回false。

一个例子:

活动

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getSupportMenuInflater();
    inflater.inflate(R.menu.main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {

        case R.id.activity_menu_item:

            // Do Activity menu item stuff here
            return true;

        case R.id.fragment_menu_item:

            // Not implemented here
            return false;
        default:
            break;
    }

    return false;
}

片段

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);
    ....
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    // Do something that differs the Activity's menu here
    super.onCreateOptionsMenu(menu, inflater);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {

        case R.id.activity_menu_item:

            // Not implemented here
            return false;
        case R.id.fragment_menu_item:

            // Do Fragment menu item stuff here
            return true;

        default:
            break;
    }

    return false;
}

3
只是缺少了 setHasOptionsMenu(true);。谢谢。 - Chad Bingham
2
在某些设备上的 viewPager 上,菜单只有在第二次访问该片段时才会显示。 - Roel
3
@Marco HC,你的代码很好用。但是如果我想要在Activity或Fragment中隐藏一些菜单怎么办?你已经提到了在哪里实现选项菜单(在Activity和Fragment中)。但是如果我想要隐藏一些菜单怎么办? - Shreyash Mahajan
@iDroidExplorer 只需保留对该 MenuItem 的引用并将其可见性设置为 gone 或 invisible。这就是你的问题吗? - Marco Hernaiz
如何在片段中使用片段按钮单击事件显示选项菜单? - Keyur Android
显示剩余2条评论

162

如果您发现onCreateOptionsMenu(Menu menu, MenuInflater inflater)方法没有被调用,请确保从Fragment的onCreate(Bundle savedInstanceState)方法中调用以下方法:

setHasOptionsMenu(true)

它给我一个像空指针异常的错误。 - Pratik Butani
2
好的,你说得对。我是从静态的newInstance方法中调用了setHasOptionMenu方法。由于只有在savedInstanceState为null时才会附加我的片段,因此当屏幕配置更改时,菜单将不会被创建。片段的onCreate方法是设置setHasOptionMenu为true的正确位置。 - argenkiwi
1
我正在使用一个 Toolbar,为了使它生效,我不得不在 onCreateView() 中调用 setHasOptionsMenu(true),而不是在 onCreate() 中。 - CLOUGH
"setHasOptionsMenu(Boolean): Unit"已被弃用。在Java中已经过时。当使用MenuProvider为您的活动提供菜单时,不再需要此方法,而是推荐使用onCreateOptionsMenu作为提供一种一致、可选地具有生命周期感知和模块化的方式来处理菜单创建和项目选择的方法。 - philoopher97

82
如果您需要在特定的Fragment中刷新webview,您可以使用以下代码:menu
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {

    // TODO Add your menu entries here
    inflater.inflate(R.menu.menu, menu);
    super.onCreateOptionsMenu(menu, inflater);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    case R.id.exit:
        System.exit(1);
        break;

    case R.id.refresh:
        webView.reload();
        break;
    }
    return true;

}

菜单.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/exit" android:title="Exit" android:icon="@drawable/ic_action_cancel" />
    <item android:id="@+id/refresh" android:title="Refresh" android:icon="@drawable/ic_action_refresh" />
</menu>

1
这是一个很好的答案,我正在寻找一种将活动中的应用栏和来自片段的其他选项合并的方法,谢谢! - Teoman shipahi

49

TL;DR

使用 android.support.v7.widget.Toolbar 并执行以下操作:

toolbar.inflateMenu(R.menu.my_menu)
toolbar.setOnMenuItemClickListener {
    onOptionsItemSelected(it)
}

独立工具栏

大多数建议的解决方案(如setHasOptionsMenu(true))仅在父Activity在其布局中拥有工具栏并通过setSupportActionBar()声明时才起作用。然后,片段可以参与此特定操作栏的菜单填充:

Fragment.onCreateOptionsMenu():初始化片段主机的标准选项菜单的内容。

如果您想要一个独立的工具栏和菜单以供一个特定的片段使用,则可以执行以下操作:

menu_custom_fragment.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/menu_save"
        android:title="SAVE" />
</menu>
`

自定义片段.xml

`
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    ...

CustomFragment.kt

->

自定义Fragment.kt

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val view = inflater.inflate(layout.custom_fragment, container, false)
    val toolbar = view.findViewById<Toolbar>(R.id.toolbar)
    toolbar.inflateMenu(R.menu.menu_custom_fragment)
    toolbar.setOnMenuItemClickListener {
        onOptionsItemSelected(it)
    }
    return view
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    return when (item.itemId) {
        R.id.menu_save -> {
            // TODO: User clicked the save button
            true
        }
        else -> super.onOptionsItemSelected(item)
    }
}

是的,就是这么简单。您甚至不需要覆盖onCreate()onCreateOptionsMenu()

附注:此方法仅适用于android.support.v4.app.Fragmentandroid.support.v7.widget.Toolbar(还要确保在您的styles.xml中使用AppCompatActivityAppCompat主题)。


1
真的帮了我很多。 - brahmy adigopula
在这种情况下是否有 onPrepareOptionsMenu 的替代方法? - user12194200
这就是我的问题!谢谢! - Kobato

27

menu.xml文件中,您应该添加所有菜单项。然后,您可以隐藏不想在初始加载时看到的项目。

menu.xml

<item
    android:id="@+id/action_newItem"
    android:icon="@drawable/action_newItem"
    android:showAsAction="never"
    android:visible="false"
    android:title="@string/action_newItem"/>
在onCreate()方法中添加setHasOptionsMenu(true)以调用您的Fragment类中的菜单项。 FragmentClass.java
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);
}

您不需要在Fragment类中再次覆盖onCreateOptionsMenu方法。可以通过覆盖Fragment中可用的onPrepareOptionsMenu方法来更改菜单项(添加/删除)。

@Override
public void onPrepareOptionsMenu(Menu menu) {
    menu.findItem(R.id.action_newItem).setVisible(true);
    super.onPrepareOptionsMenu(menu);

}

21

针对我的情况,以下是步骤。

步骤一

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Here notify the fragment that it should participate in options menu handling.
    setHasOptionsMenu(true);
}

第二步

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    // First clear current all the menu items
    menu.clear();

    // Add the new menu items
    inflater.inflate(R.menu.post_stuff, menu);

    super.onCreateOptionsMenu(menu, inflater);
}

第三步

 @Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.post_stuff:
            Log.d(TAG, "Will post the photo to server");
            return true;
        case R.id.cancel_post:
            Log.d(TAG, "Will cancel post the photo");
            return true;
        default:
            break;
    }
    return super.onOptionsItemSelected(item);
}

1
我已经在主活动上有了菜单,使用 menu.clear() 清除了先前的菜单。对我很有效 :) - Anbuselvan Rocky
我有一个带有片段的ViewPager,menu.clear()是缺失的部分,谢谢! - finalpets

20

自从 androidx.activity:activity:1.4.0 版本以后,有了一种新的做法。

你应该使用MenuProvider API。

使用方法如下:

不要再调用super.setHasOptionMenu和实现onCreateOptionsMenu,而是请在onViewCreated中调用addMenuProvider

示例:

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 true
      }
    }, viewLifecycleOwner)
  }

来源: Activity 更新日志


使用片段添加菜单的最新和现代方式。 - Dhananjay pathak
@Gesit 请问您能详细说明一下这段代码的含义吗?menuHost被声明为MenuHost类型,但是您却将其设置为FragmentActivity。 - Gideon Steven
@GideonSteven MenuHost只是一个接口,由FragmentActivity实现。也可以将menuHost作为FragmentActivity,并直接调用这些方法。 - Gesit

17
您需要在填充菜单之前使用menu.clear()。
@Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        menu.clear();
        inflater.inflate(R.menu.menu, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

1
当您想在不同的片段中使用不同的菜单,并且添加片段而不是替换它们时,这是正确的。 - Akshay Mahajan
谢谢!menu.clear() 帮我移除 Activity 的菜单选项。 - kimcy

8
如果您想添加自定义菜单
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    inflater.inflate(R.menu.menu_custom, menu);
}

如果有一个选项卡视图,你想为每个片段填充不同的菜单,该怎么办? - Jainam Jhaveri

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