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.
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.
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:
int
), which should be NONE
unless you are creating a specific grouped set of menu choices for use with setGroupCheckable()
(described shortly)int
), for use in identifying this choice in the onOptionsItemSelected()
callback when a menu choice is chosenint
), for indicating where this menu choice should be slotted if the menu has Android-supplied choices alongside your own; for now, just use NONE
String
or a resource IDThe 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:
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 itemMenu#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 timeYou 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.
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.
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.
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.
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.
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.
Chapter 13 explained how you can describe View
s 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 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:
menu
root element.menu
element are item
elements and group
elements, the latter representing a collection of menu items that can be operated upon as a group.menu
element as a child of an item
element, using this new menu
element to describe the contents of the submenu.android:id
, just as you do with View
layout XML.Inside the item
and group
elements, you can specify various options, matching up with corresponding methods on Menu
or MenuItem
, as follows:
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
).@drawable/eject
), use the android:icon
attribute on the item
element.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.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
.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
.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.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));
}
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.