34

In a word game for Android I currently have a hardcoded menu inflated from left_drawer_menu.xml and consisting of 3 groups (my turn, opponent turn and finally other stuff):

app screenshot

mLeftDrawer = (NavigationView) findViewById(R.id.left_drawer);
mLeftDrawer.setNavigationItemSelectedListener(
        new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(final MenuItem menuItem) {
                Menu menu = mLeftDrawer.getMenu();

                if (menuItem.getGroupId() == R.id.my_move) {
                    menu.setGroupCheckable(R.id.my_move, true, true);
                    menu.setGroupCheckable(R.id.his_move, false, false);
                    menu.setGroupCheckable(R.id.extras, false, false);
                } else if (menuItem.getGroupId() == R.id.his_move) {
                    menu.setGroupCheckable(R.id.my_move, false, false);
                    menu.setGroupCheckable(R.id.his_move, true, true);
                    menu.setGroupCheckable(R.id.extras, false, false);
                } else if (menuItem.getGroupId() == R.id.extras) {
                    menu.setGroupCheckable(R.id.my_move, false, false);
                    menu.setGroupCheckable(R.id.his_move, false, false);
                    menu.setGroupCheckable(R.id.extras, true, true);
                }

                menuItem.setChecked(true);
                mLeftItem = menuItem.getItemId();
                mDrawerLayout.closeDrawer(mLeftDrawer);
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (mLeftItem == R.id.start) {
                            startNewGame();
                        } 
                    }
                },DRAWER_CLOSE_DELAY);

                return true;
            }
        });

Now I am trying to change that menu dynamically.

I have SQLite instance containing all game data and use IntentService to read/write the database - that part works fine.

My current difficulty is: with the following code, the new items are added outside the R.id.my_move group:

if (mLeftItem == R.id.start) {
    startNewGame();

    Random r = new Random();
    int i = r.nextInt(100);
    menu.add(R.id.my_move, i, i, "Item " + i);   // why is my_move ignored?
} 

app screenshot

UPDATE:

As a further test I have tried assigning even and not even items to 2 separate groups with this code:

Random r = new Random();
int i = r.nextInt(100);
int group = 1 + (i % 2); // can be 1 or 2
menu.add(group, i, i, "Item " + i);

However the result looks chaotic:

app screenshot

Also I have discovered the (probably already fixed?) Issue 176300 and wonder if maybe sub-menus should be better used instead of menu groups?

Alexander Farber
  • 21,519
  • 75
  • 241
  • 416

3 Answers3

41

On checking MenuItemImpl source code

     ...
     *    @param group Item ordering grouping control. The item will be added after
     *            all other items whose order is <= this number, and before any
     *            that are larger than it. This can also be used to define
     *            groups of items for batch state changes. Normally use 0.
     ...

    MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
        CharSequence title, int showAsAction) {

So you should define ordering in your xml (give same order to items in one group and increment in each following group)

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <group android:id="@+id/my_move" android:checkableBehavior="single">
        <item
            android:orderInCategory="0"
            android:id="@+id/game1"
            android:icon="@drawable/ic_stars_black_24dp"
            android:title="Game #1" />
        <item
            android:orderInCategory="0"
            android:id="@+id/game2"
            android:icon="@drawable/ic_stars_black_24dp"
            android:title="Game #2" />
    </group>
    <group android:id="@+id/his_move" android:checkableBehavior="single">
        <item
            android:orderInCategory="1"
            android:id="@+id/game5"
            android:icon="@drawable/ic_clock_black_24dp"
            android:title="Game #5" />
        <item
            android:orderInCategory="1"
            android:id="@+id/game6"
            android:icon="@drawable/ic_clock_black_24dp"
            android:title="Game #6" />
        <item
            android:orderInCategory="1"
            android:id="@+id/game7"
            android:icon="@drawable/ic_clock_black_24dp"
            android:title="Game #7" />
    </group>
    .....

</menu>

and give an appropriate order value while adding the item in your code. So if you want to add the item at the end of first group, add it as:

menu.add(R.id.my_move, Menu.NONE, 0, "Item1");

and if you want to add to second group, add it as:

menu.add(R.id.his_move, Menu.NONE, 1, "Item2");

The problem with your code could be that all items in the xml have default orderInCategory 0 and so the new item gets added after all these items.

UPDATE

To add icon use setIcon method for MenuItem

menu.add(R.id.my_move, Menu.NONE, 0, "Item1").setIcon(R.drawable.ic_stars_black_24dp);
random
  • 10,238
  • 8
  • 57
  • 101
  • This works well and the colors are same too (when using submenus the highlight colors are different from the original menu). But I wonder, how to set the icon for the dynamically added menu entries? – Alexander Farber Dec 31 '15 at 18:28
  • 1
    Use `setIcon` method to set an icon, answer updated. – random Jan 01 '16 at 04:09
  • 5
    This seems kind of stupid.. you specify a group to add the item in, yet Android just ignores it and dumps it at the end of the list, unless you specify orders for items that are not even in the same group? What the hell Google? Also, it's still adding the item after the groups for me :/ – NaturalBornCamper Dec 24 '17 at 04:39
  • @NaturalBornCamper Because of poor documentation I also did not know what 'groupId' means in "..menu.add ( groupId : 1, itemId: 2,....... Does it mean to add and generate a new group with this id we pass or as you say to add it to the group with this id in the menu we get?! But after testing it a bit now I know that groupId means the id of the group you are about to create and add NOT the one you wanna add it to! For that you can specify the group in a manner of indexing starting from 0 by eg: ...getMenu().getItem(index: 0) and then .add(groupId [the new group ID] – Araz Mar 08 '22 at 16:04
20

enter image description here

I've solved it this way:

  1. Set up the menu:

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:title="my moves"
              android:id="@+id/submenu_1">
            <menu>
                <item
                    android:id="@+id/my_dummy_item_1"
                    android:icon="@drawable/ic_menu_camera"
                    android:title="Import" />
                <item
                    android:id="@+id/my_dummy_item_2"
                    android:icon="@drawable/ic_menu_gallery"
                    android:title="Gallery" />
                <item
                    android:id="@+id/add_item"
                    android:icon="@drawable/ic_menu_manage"
                    android:title="Add Item" />
            </menu>
        </item>
        <item android:title="opponent's moves"
              android:id="@+id/submenu_2">
            <menu>
                <item
                    android:id="@+id/opponent_dummy_item_1"
                    android:icon="@drawable/ic_menu_camera"
                    android:title="Import" />
                <item
                    android:id="@+id/opponent_dummy_item_2"
                    android:icon="@drawable/ic_menu_gallery"
                    android:title="Gallery" />
                <item
                    android:id="@+id/opponent_dummy_item_3"
                    android:icon="@drawable/ic_menu_manage"
                    android:title="Tools" />
            </menu>
        </item>
    </menu>
    
  2. In onNavigationItemSelected(), get MenuItem you want to expand by order id (or via findItem()), then get SubMenu from it and add new item into it:

    @SuppressWarnings("StatementWithEmptyBody")
    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        int id = item.getItemId();
    
        if (id == R.id.add_item) {
            Random r = new Random();
            int i = r.nextInt(100);
            MenuItem myMoveGroupItem = navigationView.getMenu().getItem(0);
            // MenuItem myMoveGroupItem = navigationView.getMenu().findItem(R.id.submenu_1);  -- it also works!
            SubMenu subMenu = myMoveGroupItem.getSubMenu();
            subMenu.add("Item "+i);
        }
    
        return true;
    }
    

I hope, it helps

Konstantin Loginov
  • 15,802
  • 5
  • 58
  • 95
  • Thanks +1 for the great answer. Do you know, how to add icon for sub-menu items? Also the highlight color seems to be different for those items... – Alexander Farber Dec 31 '15 at 18:25
2
menu.add(R.id.my_move, i, i, "Item " + i);

You are also assigning the order (3rd param) as i. I am guessing that this is overriding the groupId. Try setting it as NONE as mentioned here

menu.add(R.id.my_move, i, NONE, "Item " + i);

Edit: Maybe something like this

MenuItem lastItem = menu.findItem(R.id.<lastItemId>);
int lastOrder= lastItem.getOrder();
menu.add(R.id.my_move, i, lastOrder-5, "Item " + i);

Order is a combination of category and order, so it might not be as straight forward as this.

Roshan
  • 3,442
  • 2
  • 14
  • 23
  • Unfortunately `Menu.NONE` does not fix the problem and menu items are still added at the bottom of the `NavigationView` menu - and not in the upmost group. – Alexander Farber Dec 29 '15 at 14:05
  • 1
    You could try calculating the required order based on items already in the menu and set that order. – Roshan Dec 29 '15 at 14:18