Android: How to include a menu xml inside another

2019-06-20 15:27发布

Simple question.

I have my menu of child items:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/fp_pitcher"
        android:title="Pitcher">
    </item>
    <item
        android:id="@+id/fp_catcher"
        android:title="Catcher">
    </item>
<!-- SNIP ---> 
</menu>

And later I would want to include it as a submenu of this menu:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >


    <item
        android:id="@+id/teameditor_remove"
        android:title="Remove Player from Team">
    </item>

    <item
        android:id="@+id/teameditor_assignbattingposition"
        android:title="Assign Batting Position">
    </item>

    <item
        android:id="@+id/teameditor_assignfieldingposition"
        android:title="Assign Feilding Position">
        <!-- I want to include the submenu here-->
    </item>

</menu>

The question here kind of answered this - I'm not sure how to inflate the submenu.

I'm thinking that you inflate it in the onContextItemSelected method - but inflate requires a menu object, which isn't passed into onContextItemSelected.

2条回答
Deceive 欺骗
2楼-- · 2019-06-20 15:51

It's not pretty, but if you you need to do it without copying the XML content over (which would work easily). When you inflate the second menu you can also do a menu.findItem(R.id.teameditor_assignfieldingposition).getSubMenu().add(...) for each of the items you want to add. If you have the strings ("Pitcher" and "Catcher") in a String array resource you could iterate over that array to add the same items as in the original. Alternatively, you would probably need to parse the other menu's XML, you can cheat that by just inflating it I guess, and then using it's size() and getItem(int).

In fact, you could just inflate the first menu into a Menu and then use size() and getItem(int) to get the MenuItems out of it. Then, for each item you can do add(menuItem.getGroupId(), menuItem.getItemId(), menuItem.getOrder(), menuItem.getTitle()) on the getSubMenu() of the second menu's findItem(R.id.teameditor_assignfieldingposition). That should add all the items of the first menu as a submenu of that item. This means you are inflating two XML files, but it's kind of unavoidable if you want to use separate XML files, seeing as there isn't an <include> for menu XML files. I would probably inflate the second menu normally (in the onCreateOptionsMenu(...)) and then add the first menu as a submenu in the onPrepareOptionsMenu(...) (it's given the menu you created in onCreateOptionsMenu(...)). I think you could do it all in onCreateOptionsMenu(...), but I believe it's better practice to make modifications to the menu in onPrepareOptionsMenu(...).

I think the second way is the best solution I can find, I'm leaving the first option as an alternative just in case.

查看更多
够拽才男人
3楼-- · 2019-06-20 15:57

It's sadly not possible in plain XML, but there's a nice way without using manual Menu.add* methods: here's how you can obtain a Menu instance to include/inflate the other file into:

inflater.inflate(R.menu.player, menu);
MenuItem fp_menu = menu.findItem(R.id.teameditor_assignfieldingposition);
inflater.inflate(R.menu.positions, fp_menu.getSubMenu()); // needs <menu />

You can put the above code to any of the following using the specified inflater:

  • Activity.onCreateContextMenu(menu, v, menuInfo): getMenuInflater()
  • Fragment.onCreateContextMenu(menu, v, menuInfo): getActivity().getMenuInflater()
  • Activity.onCreateOptionsMenu(menu): getMenuInflater()
  • Fragment.onCreateOptionsMenu(menu, inflater): inflater

menu/player.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/teameditor_remove"
          android:title="Remove Player from Team"
    />
    <item android:id="@+id/teameditor_assignbattingposition"
          android:title="Assign Batting Position"
    />
    <item android:id="@+id/teameditor_assignfieldingposition"
          android:title="Assign Feilding Position">
        <menu><!-- include: positions.xml --></menu>
    </item>
</menu>

The empty <menu /> placeholder is very important, without that getSubMenu() will be null!

menu/positions.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/fp_pitcher"
          android:title="Pitcher"
    />
    <item android:id="@+id/fp_catcher"
          android:title="Catcher"
    />
    <!-- SNIP ---> 
</menu>

Note on your onContextItemSelected idea

I'm thinking that you inflate it in the onContextItemSelected method [...]

I think it's too late if you're in onContextItemSelected, since you're already handling the event which would lead to showing you're submenu... which is not inflated yet. You could try the same inflate into getSubMenu(), but I'm not sure that it'll show up. It's best to create the menu where it's supposed to be created.

Note on including the same submenu multiple times in the same menu

Untested If you need to inflate the same positions.xml into teameditor_assignbattingposition as well you'll have some problems in onOptionsItemSelected/onContextItemSelected. One way to work around it is to convert the findItem variable to a field and save the reference to both

this.fp_menu = menu.findItem(R.id.teameditor_assignfieldingposition);
inflater.inflate(R.menu.positions, fp_menu.getSubMenu());
this.bp_menu = menu.findItem(R.id.teameditor_assignbattingposition);
inflater.inflate(R.menu.positions, bp_menu.getSubMenu());

and then in on*ItemSelected:

switch (item.getItemId()) {
    case R.id.fp_pitcher:
        if (item == fp_menu.findItem(R.id.fp_pitcher)) {
            // selected inside teameditor_assignfieldingposition
        } else if (item == bp_menu.findItem(R.id.fp_picther)) {
            // selected inside teameditor_assignbattingposition
        } else {
            throw new ImLostInMenusException();
        }
        return true;
}
return super.on*ItemSelected();
查看更多
登录 后发表回答