Chapter 16

Applying Menus

Like applications for the desktop and some mobile operating systems, Android supports activities with application menus. Most Android phones have a dedicated menu key for popping up the menu; other devices offer alternate means for triggering the menu to appear, such as the onscreen button used by the Archos 5 Android tablet.

Also, as with many GUI toolkits, you can create context menus for your Android applications. On a traditional GUI, a context menu might be triggered by the user clicking with the right-mouse button. On mobile devices, context menus typically appear when the user taps and holds over a particular widget. For example, if a TextView has a context menu, and the device is designed for finger-based touch input, you could push the TextView with your finger, hold it for a second or two, and a pop-up menu would appear.

Flavors of Menu

Android refers to the two types of menu described in the preceding section as options menus and context menus. The options menu is triggered by pressing the hardware Menu button on the device, while the context menu is raised by a tap-and-hold on the widget sporting the menu.

In addition, the options menu operates in one of two modes: icon or expanded. When the user first presses the Menu button, the icon mode will appear, showing up to the first six menu choices as large, finger-friendly buttons in a grid at the bottom of the screen. If the menu has more than six choices, the sixth button will be labeled More. Tapping the More option will bring up the expanded mode, showing the remaining choices not visible in the regular menu. The menu is scrollable, so the user can scroll to any of the menu choices.

Menus of Options

Instead of building your activity’s options menu during onCreate(), the way you wire up the rest of your UI, you need to implement onCreateOptionsMenu(). This callback receives an instance of Menu.

The first thing you should do is chain upward to the superclass (super.onCreateOptionsMenu(menu)), so the Android framework can add in any menu choices it feels are necessary. Then you can go about adding your own options, as described in this section.

If you will need to adjust the menu during your activity’s use (e.g., disable a now-invalid menu choice), just hold onto the Menu instance you receive in onCreateOptionsMenu(). Alternatively, you can implement onPrepareOptionsMenu(), which is called just before displaying the menu each time it is requested.

Given that you have received a Menu object via onCreateOptionsMenu(), you add menu choices by calling add(). There are many flavors of this method, which require some combination of the following parameters:

  • A group identifier (int), which should be NONE unless you are creating a specific grouped set of menu choices for use with setGroupCheckable() (described shortly)
  • A choice identifier (also an int), for use in identifying this choice in the onOptionsItemSelected() callback when a menu choice is chosen
  • An order identifier (yet another int), for indicating where this menu choice should be slotted if the menu has Android-supplied choices alongside your own; for now, just use NONE
  • The text of the menu choice, as a String or a resource ID

The add() family of methods all return an instance of MenuItem, where you can adjust any of the menu item settings you have already set (e.g., the text of the menu choice).

You can also set the shortcuts for the menu choice, which are single-character mnemonics that choose that menu item when the menu is visible. Android supports both an alphabetic (or QWERTY) set of shortcuts and a numeric set of shortcuts. These are set individually by calling setAlphabeticShortcut() and setNumericShortcut(), respectively. The menu is placed into alphabetic shortcut mode by calling setQwertyMode() on the menu with a true parameter.

The choice and group identifiers are keys used to unlock additional menu features, such as the following:

  • Calling MenuItem#setCheckable() with a choice identifier, to control if the menu choice has a two-state check box alongside the title, where the check box value is toggled when the user chooses that menu item
  • Calling Menu#setGroupCheckable() with a group identifier, to turn a set of menu choices into ones with a mutual-exclusion radio button between them, so that only one item in the group can be in the checked state at any time

You can create fly-out submenus by calling addSubMenu(), supplying the same parameters as addMenu(). Android will eventually call onCreatePanelMenu(), passing it the choice identifier of your submenu, along with another Menu instance representing the submenu itself. As with onCreateOptionsMenu(), you should chain upward to the superclass, and then add menu choices to the submenu. One limitation is that you cannot indefinitely nest submenus—a menu can have a submenu, but a submenu cannot have a sub-submenu.

Finally, you can even push your menu items up into the action bar, which makes your options more discoverable by your users and, more importantly, better utilizes all the available screen space on tablets and larger devices. We’ll explore this capability in more depth in Chapter 27 when we focus on the action bar itself.

If the user makes a menu choice, your activity will be notified via the onOptionsItemSelected() callback that a menu choice was selected. You are given the MenuItem object corresponding to the selected menu choice. A typical pattern is to switch() on the menu ID (item.getItemId()) and take appropriate behavior. Note that onOptionsItemSelected() is used regardless of whether the chosen menu item was in the base menu or a submenu.

Menus in Context

By and large, context menus use the same guts as options menus. The two main differences are how you populate the menu and how you are informed of menu choices.

First, you need to indicate which widget or widgets on your activity have context menus. To do this, call registerForContextMenu() from your activity, supplying the View that is the widget needing a context menu.

Next, you need to implement onCreateContextMenu(), which, among other things, is passed the View you supplied in registerForContextMenu(). You can use that to determine which menu to build, assuming your activity has more than one.

The onCreateContextMenu() method gets the ContextMenu itself, the View the context menu is associated with, and a ContextMenu.ContextMenuInfo, which tells you which item in the list the user did the tap-and-hold over, in case you want to customize the context menu based on that information. For example, you could toggle a checkable menu choice based on the current state of the item.

It is also important to note that onCreateContextMenu() gets called each time the context menu is requested. Unlike the options menu (which is built only once per activity), context menus are discarded after they are used or dismissed. Hence, you do not want to hold onto the supplied ContextMenu object; just rely on getting the chance to rebuild the menu to suit your activity’s needs on an on-demand basis based on user actions.

To find out when a context menu choice was chosen, implement onContextItemSelected() on the activity. Note that you get only the MenuItem instance that was chosen in this callback. As a result, if your activity has two or more context menus, you may want to ensure they have unique menu item identifiers for all their choices, so you can distinguish between them in this callback. Also, you can call getMenuInfo() on the MenuItem to get the ContextMenu.ContextMenuInfo you received in onCreateContextMenu(). Otherwise, this callback behaves the same as onOptionsItemSelected(), as described in the previous section.

Taking a Peek

In the sample project Menus/Menus, you will find an amended version of the ListView sample (List) with associated menus. Since the menus do not affect the layout, the XML layout file does not need to be changed and thus is not reprinted here. However, the Java code has a few new behaviors:

packagecom.commonsware.android.menus;

importandroid.app.AlertDialog;
importandroid.app.ListActivity;
importandroid.content.DialogInterface;
importandroid.os.Bundle;
importandroid.view.ContextMenu;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.widget.AdapterView;
importandroid.widget.ArrayAdapter;
importandroid.widget.EditText;
importandroid.widget.ListView;
importandroid.widget.TextView;
importjava.util.ArrayList;

public class MenuDemo extends ListActivity {
  private static final String[] items={"lorem", "ipsum", "dolor",
          "sit", "amet", "consectetuer", "adipiscing", "elit",
          "morbi", "vel", "ligula", "vitae", "arcu", "aliquet",
          "mollis", "etiam", "vel", "erat", "placerat", "ante",
          "porttitor", "sodales", "pellentesque", "augue", "purus"};
  public static final int MENU_ADD = Menu.FIRST+1;
  public static final int MENU_RESET = Menu.FIRST+2;
  public static final int MENU_CAP = Menu.FIRST+3;
  public static final int MENU_REMOVE = Menu.FIRST+4 ;
  private ArrayList<String> words=null;

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

    initAdapter();
    registerForContextMenu(getListView());
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    menu
    .add(Menu.NONE, MENU_ADD, Menu.NONE, "Add")
    .setIcon(R.drawable.ic_menu_add);
  menu
    .add(Menu.NONE, MENU_RESET, Menu.NONE, "Reset")
    .setIcon(R.drawable.ic_menu_refresh);

  return(super.onCreateOptionsMenu(menu));
  }

  @Override
  public void onCreateContextMenu(ContextMenu menu, View v,
                                   ContextMenu.ContextMenuInfo menuInfo) {
    menu.add(Menu.NONE, MENU_CAP, Menu.NONE, "Capitalize");
    menu.add(Menu.NONE, MENU_REMOVE, Menu.NONE, "Remove");
  }

  @Override
  public booleanon OptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case MENU_ADD:
        add();
        return(true);

      case MENU_RESET:
        initAdapter();
        return(true);
    }

    return(super.onOptionsItemSelected(item));
  }

  @Override
  public boolean onContextItemSelected(MenuItem item) {
    AdapterView.AdapterContextMenuInfo info=
      (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
    ArrayAdapter<String> adapter=(ArrayAdapter<String>)getListAdapter();

    switch (item.getItemId()) {
      case MENU_CAP:
        String word=words.get(info.position);

        word=word.toUpperCase();

        adapter.remove(words.get(info.position));
        adapter.insert(word, info.position);

        return(true);

      case MENU_REMOVE:
        adapter.remove(words.get(info.position));

        return(true);
    }

    return(super.onContextItemSelected(item));
  }

  private void initAdapter() {
    words=new ArrayList<String>();

    for (String s : items) {
      words.add(s);
    }

    setListAdapter(new ArrayAdapter<String>(this,
                    android.R.layout.simple_list_item_1, words));
  }

  private void add() {
    final View addView=getLayoutInflater().inflate(R.layout.add, null);

    newAlertDialog.Builder(this)
      .setTitle("Add a Word")
      .setView(addView)
      .setPositiveButton("OK",
                         new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog,
                             int whichButton) {
          ArrayAdapter<String> adapter=(ArrayAdapter<String>)getListAdapter();
          EditText title=(EditText)addView.findViewById(R.id.title);

          adapter.add(title.getText().toString());
        }
      })
      .setNegativeButton("Cancel", null)
      .show();
  }
}

In onCreate(), we register our ListView widget as having a context menu. We also delegate loading the adapter to an initAdapter() private method, one that copies the data out of our static String array and pours it into an ArrayList, using the ArrayList for the ArrayAdapter. The reason we do this is that we want to be able to change the contents of the list on-the-fly, and that is much easier if we use an ArrayList rather than an ordinary String array.

For the options menu, we override onCreateOptionsMenu() and add two menu items, one to add a new word to the list and one to reset the words to their initial state. These menu items have IDs defined locally as static data members (MENU_ADD and MENU_RESET), and they also sport icons copied from the Android open source project. If the user displays the menu, it looks as shown in Figure 16–1.

images

Figure 16–1. The MenuDemo sample application and its options menu

We also override onOptionsItemSelected(), which will be called if the user makes a choice from the menu. The supplied MenuItem has a getItemId() method that should map to either MENU_ADD or MENU_RESET. In the case of MENU_ADD, we call a private add() method that displays an AlertDialog with a custom View as its contents, inflated from res/layout/add.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    >
  <TextView
      android:text="Word:"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      />
  <EditText
      android:id="@+id/title"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_marginLeft="4dip"
      />
</LinearLayout>

That produces a dialog box like the one shown in Figure 16–2.

images

Figure 16–2. The same application, showing the Add a Word dialog box

If the user taps the OK button, we get our ArrayAdapter and call add() on it, adding the entered word to the end of the list.

If the user chooses MENU_RESET, we call initAdapter() again, setting up a new ArrayAdapter and attaching it to our ListActivity.

For the context menu, we override onCreateContextMenu(). Once again, we define a pair of menu items with local IDs, MENU_CAP (to capitalize the long-tapped-upon word) and MENU_REMOVE (to remove the word). Since context menus have no icons, we can skip that part. That gives the user the context menu shown in Figure 16–3 if they long-tap on a word.

images

Figure 16–3. The same application, showing the context menu

We also override onContextMenuSelected(). Since this is a context menu for a ListView, our MenuItem has some extra information for us—specifically, which item was long-tapped upon in the list. To do that, we call getMenuInfo() on the MenuItem and cast the result to be an AdapterView.AdapterContextMenuInfo. That object, in turn, has a position data member, which is the index into our array of the word the user chose. From there, we work with our ArrayAdapter to capitalize or remove the word, as requested.

Yet More Inflation

Chapter 13 explained how you can describe Views via XML files and “inflate” them into actual View objects at runtime. Android also allows you to describe menus via XML files and inflate them when a menu is needed. This helps you keep your menu structure separate from the implementation of menu-handling logic, and it provides easier ways to develop menu-authoring tools.

Menu XML Structure

Menu XML goes in res/menu/ in your project tree, alongside the other types of resources that your project might employ. As with layouts, you can have several menu XML files in your project, each with its own filename and the .xml extension.

For example, from the Menus/Inflation sample project, here is a menu called option.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@+id/add"
    android:title="Add"
    android:icon="@drawable/ic_menu_add" />
  <item android:id="@+id/reset"
    android:title="Reset"
    android:icon="@drawable/ic_menu_refresh" />
</menu>

Note the following:

  • You must start with a menu root element.
  • Inside a menu element are item elements and group elements, the latter representing a collection of menu items that can be operated upon as a group.
  • Submenus are specified by adding a menu element as a child of an item element, using this new menu element to describe the contents of the submenu.
  • If you want to detect when an item is chosen, or to reference an item or group from your Java code, be sure to apply an android:id, just as you do with View layout XML.

Menu Options and XML

Inside the item and group elements, you can specify various options, matching up with corresponding methods on Menu or MenuItem, as follows:

  • Title: The title of a menu item is provided via the android:title attribute on an item element. This can be either a literal string or a reference to a string resource (e.g., @string/foo).
  • Icon: Menu items optionally have icons. To provide an icon, in the form of a reference to a drawable resource (e.g., @drawable/eject), use the android:icon attribute on the item element.
  • Order: By default, the order of the items in the menu is determined by the order in which they appear in the menu XML. You can change that order by specifying the android:orderInCategory attribute on the item element. This is a 0-based index of the order for the items associated with the current category. There is an implicit default category; groups can provide an android:menuCategory attribute to specify a different category to use for items in that group. Generally, though, it is simplest just to put the items in the XML in the order in which you want them to appear.
  • Enabled: Items and groups can be enabled or disabled, controlled in the XML via the android:enabled attribute on the item or group element. By default, items and groups are enabled. Disabled items and groups appear in the menu but cannot be selected. You can change an item’s status at runtime via the setEnabled() method on MenuItem, or change a group’s status via setGroupEnabled() on Menu.
  • Visible: Items and groups can be visible or invisible, controlled in the XML via the android:visible attribute on the item or group element. By default, items and groups are visible. Invisible items and groups do not appear in the menu. You can change an item’s status at runtime via the setVisible() method on MenuItem, or change a group’s status via setGroupVisible() on Menu.
  • Shortcut: Items can have shortcuts—single letters (android:alphabeticShortcut) or numbers (android:numericShortcut) that can be pressed to choose the item without having to use the touchscreen, D-pad, or trackball to navigate the full menu.

Inflating the Menu

Actually using the menu, once it’s defined in XML, is easy. Just create a MenuInflater and tell it to inflate your menu.

The Menus/Inflation project is a clone of the Menus/Menus project, with the menu creation converted to use menu XML resources and MenuInflater. The options menu was converted to the XML shown previously in this section; here is the context menu:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@+id/cap"
    android:title="Capitalize" />
   item android:id="@+id/remove"
    android:title="Remove" />
</menu>

The Java code is nearly identical, changing mostly in the implementation of onCreateOptionsMenu() and onCreateContextMenu():

@Override
public boolean onCreateOptionsMenu(Menu menu) {
  new MenuInflater(this).inflate(R.menu.option, menu);

  return(super.onCreateOptionsMenu(menu));
}

@Override
public void onCreateContextMenu(ContextMenu menu, View v,
                                 ContextMenu.ContextMenuInfomenuInfo) {
  new MenuInflater(this).inflate(R.menu.context, menu);
}

Here, we see how MenuInflater “pours” the menu items specified in the menu resource (e.g., R.menu.option) into the supplied Menu or ContextMenu object.

We also need to change onOptionsItemSelected() and onContextItemSelected() to use the android:id values specified in the XML:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
  switch (item.getItemId()) {
    case R.id.add:
      add();
      return(true);

    case R.id.reset:
      initAdapter();
      return(true);
  }

  return(super.onOptionsItemSelected(item));
}

@Override
public boolean onContextItemSelected(MenuItem item) {
  AdapterView.AdapterContextMenuInfo info=
    (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
  ArrayAdapter<String> adapter=(ArrayAdapter<String>)getListAdapter();

  switch (item.getItemId()) {
    caseR.id.cap:
      String word=words.get(info.position);

      word=word.toUpperCase();

      adapter.remove(words.get(info.position));
      adapter.insert(word, info.position);

      return(true);

    case R.id.remove:
      adapter.remove(words.get(info.position));

      return(true);
  }

  return(super.onContextItemSelected(item));
}

When Giant Menus Walk the Earth

With Android 3.x and 4.0, new ways of dealing with tablets and large displays have been introduced and folded into the core of the platform. Options menus in particular change from being something triggered by a Menu button to a drop-down menu from the action bar. Fortunately, this is backward-compatible, so your existing menus will not need to change to adopt this new look. We’ll cover the overall implications of using larger devices in Chapter 26, and the action bar itself is covered in Chapter 27.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset