- Android开发权威指南(第二版)
- 李宁编著
- 211字
- 2024-12-21 14:46:03
9.1 菜单的基本用法
源代码目录:src/ch09/Menu
菜单是Android系统中重要的用户接口之一。在Android系统中提供了丰富多彩的菜单,例如,系统的主菜单,也可称为选项菜单;带图像、复选框、选项按钮的菜单;上下文菜单。本节将对这些菜单的实现方法进行详细讲解。
9.1.1 创建选项菜单(Options Menu)
Activity.onCreateOptionsMenu方法用来创建选项菜单,该方法的原型如下:
public boolean onCreateOptionsMenu(Menu menu);
通常需要将创建选项菜单的代码放在onCreateOptionsMenu方法中。调用Menu.add方法可以添加一个选项菜单项。该方法有4种重载形式,它们的原型如下:
public MenuItem add(int titleRes);
public MenuItem add(CharSequence title);
public MenuItem add(int groupId, int itemId, int order, int titleRes);
public MenuItem add(int groupId, int itemId, int order, CharSequence title);
add方法最多有4个参数,这些参数的含义如下。
groupId:菜单项的分组ID,该参数一般用于带选项按钮的菜单(将在后面详细介绍)。参数值可以是负整数、0和正整数。
itemId:当前添加的菜单项的ID。该参数值可以是负整数、0和正整数。
order:菜单显示顺序。Android系统在显示菜单项时,根据order参数的值按升序从左到右、从上到下显示菜单项。参数值必须是0和正整数,不能为负整数。
titleRes或title:菜单项标题的字符串资源ID或字符串。
如果使用add方法的前两种重载形式,groupId、itemId和order三个参数的值都为0。这时菜单项的显示顺序就是菜单项的添加顺序。下面的代码添加了3个选项菜单项:
public boolean onCreateOptionsMenu(Menu menu)
{
menu.add(1, 1, 1, "菜单项1");
menu.add(1, 2, 2, "菜单项2");
menu.add(1, 3, 3, "菜单项3");
return true;
}
如果想为菜单项设置图像,可以使用MenuItem.setIcon方法,代码如下:
// 通过图像资源ID装载图像
public MenuItem setIcon(int iconRes);
// 通过Drawable对象装载图像
public MenuItem setIcon(Drawable icon);
下面的代码设置了“删除”菜单项的图像:
MenuItem deleteMenuItem = menu.add(1, 1, "删除");
deleteMenuItem.setIcon(R.drawable.delete); // 设置“删除”菜单项的图像
选项菜单的显示效果根据Android版本不同分为如下两个阶段。
Android 1.x、2.x
选项菜单最多显示6个菜单项,如果不足6个菜单项,可根据实际情况来排列,例如,在有5个菜单项的情况下,第1行会显示两个菜单项,第2行会显示3个菜单项,如图9-1所示。如果菜单项超过6个,系统会显示前5个菜单项,而最后一个菜单项的文本是“更多”或“More”,如图9-2所示。单击该菜单项后,会显示其余的菜单项。如果菜单项的文本过长,系统会显示三行两列的选项菜单,而不是如图9-2所示的两行三列的选项菜单,而且过长的标题会从左到右移动显示。
▲图9-1 有5 个菜单项的选项菜单
▲图9-2 超过6 个菜单项的Activity 菜单
Android 3.x、Android 4.x
由于从Android 3.x开始,Android开始支持ActionBar,也就是将菜单、按钮放到窗口顶端的一种界面风格。ActionBar的出现取代了标题栏的位置,并改变了选项菜单的风格的同时与选项菜单紧密结合。在本节并不需要了解ActionBar的细节,只要知道可以很容易地将选项菜单项移到ActionBar上作为一个按钮显示即可,并且该选项菜单项会从选项菜单中消失。当然,即使菜单项显示在选项菜单中,也不会再显示图像,而只会显示文本,并且也不会是图9-1的菜单风格,实际的菜单风格是从窗口底端弹出一个如图9-3所示的单列菜单,如果菜单项太多一屏显示不下,该菜单可以上下滚动。
▲图9-3 新版Android选项菜单的效果
不同Android版本的选项菜单只是风格不同,但使用方法是完全一样的,因此从代码角度是完全兼容的。
注意
如果想在Android 3.x或Android 4.x中使用旧版选项菜单的效果,可以将AndroidManifest.xml文件中<application>标签的android:theme属性去掉即可,因为该主题设置了与选项菜单和ActionBar相关的属性。如果去掉了该主题,虽然选项菜单恢复了旧貌,但ActionBar则消失了。
9.1.2 关联Activity
虽然可以通过代码显示一个Activity,但我们还有更简单的方法,就是直接将Activity与菜单项关联。做法非常简单,只需要使用MenuItem.setIntent方法指定一个Intent对象即可。setIntent方法的原型如下:
public MenuItem setIntent(Intent intent);
将一个Activity与菜单项关联后,单击该菜单项,系统会调用startActivity方法显示与菜单项关联的Activity。下面的代码将名为AddActivity的窗口类与“添加”菜单项关联,单击“添加”菜单项,系统就会显示AddActivity窗口。
MenuItem addMenuItem = menu.add(1, 1, 1, "添加");
// 将AddActivity与“添加”菜单项进行关联
addMenuItem.setIntent(new Intent(this, AddActivity.class));
注意
如果设置了菜单项的单击事件,并且单击事件返回true,则与菜单项关联的Activity将失效。也就是说,系统调用单击事件方法后,就不会再调用startActivity方法显示与菜单项关联的Activity了。
9.1.3 响应菜单的单击动作
调用MenuItem.etOnMenuItemClickListener方法可以设置菜单项的单击监听对象,该方法有一个OnMenuItemClickListener类型的参数,处理菜单项的单击事件类必须实现OnMenuItemClickListener接口。下面的代码为“删除”菜单项设置了单击事件。
源代码文件:ch09/Menu/src/mobile/android/menu/Main.java
public class Main extends Activity implements OnMenuItemClickListener
{
// 菜单项单击事件方法
@Override
public boolean onMenuItemClick(MenuItem item)
{
// 在这里编写菜单项单击事件的代码,可根据item参数的getItemId方法来判断单击的是哪个菜单项
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuItem deleteMenuItem = menu.add(1, 2, 2, "删除");
deleteMenuItem.setIcon(R.drawable.delete);
// 设置“删除”菜单项单击事件的监听器,因为当前窗口类已经实现了
// OnMenuItemClickListener接口
deleteMenuItem.setOnMenuItemClickListener(this);
}
}
除了设置菜单项的单击事件外,还可以使用Activity.onOptionsItemSelected和Activity.onMenu ItemSelected方法响应菜单项的单击事件。这两个方法的原型如下:
public boolean onOptionsItemSelected(MenuItem item);
public boolean onMenuItemSelected(int featureId, MenuItem item);
这两个方法都有一个item参数,用于传递被单击的菜单项的MenuItem对象。可以根据MenuItem接口的相应方法(例如,getTitle和getItemId方法)判断单击的是哪个菜单项。
既然有3种响应菜单项单击事件的方法,就会产生一个问题:如果同时使用这3种方法,它们都会起作用吗?如果都起作用,那么调用顺序如何呢?实际上,当onMenuItemClick方法返回true时,另两种单击事件的响应方式都会失效,也就是说,单击菜单项时,系统不会再调用onOptionsItemSelected和onMenuItemSelected方法了。如果未设置菜单项的单击事件(onMenuItemClick方法),而同时使用了另外两种响应单击事件的方式,系统会根据在onMenuItemSelected方法中调用父类(Activity类)的onMenuItemSelected方法的位置来决定先调用onOptionsItemSelected方法还是先调用onMenuItemSelected方法。
// 如果将super.onMenuItemSelected(...)放在Log.d(...)后面调用,
// 系统会在执行完onMenuItemSelected方法中的代码后再调用onOptionsItemSelected方法
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item)
{
super.onMenuItemSelected(featureId, item);// 这条语句调用了onOptionsItemSelected方法
Log.d("onMenuItemSelected:itemId=", String.valueOf(item.getItemId()));
return true;
}
9.1.4 动态添加、修改和删除选项菜单
在很多Android应用中,需要在程序的运行过程中根据具体情况动态地对选项菜单进行处理,例如,增加菜单项、修改菜单项的标题和图像。实现这个功能的关键是获得描述选项菜单的Menu对象。
Activity类中的很多方法都可以获得Menu对象。例如, onCreateOptionsMenu方法的menu参数就是Menu类型,我们要做的就是在onCreateOptionsMenu方法中将Menu对象保存在类成员变量中。下面的代码动态地向选项菜单中添加了5个菜单项:
源代码文件:ch09/Menu/src/mobile/android/menu/Main.java
public class Main extends Activity implements OnMenuItemClickListener,
OnClickListener
{
private Menu menu;
private int menuItemId = Menu.FIRST; // Menu.FIRST的值是1
@Override
public void onClick(View view)
{
// 只有单击手机上的“Menu”按钮,onCreateOptionsMenu方法才会被调用,
// 因此,如果不按“Menu”按钮,Main类的menu变量值是null
if (menu == null) return;
// 向Activity菜单添加5个菜单项,菜单项的id从10开始
for (int i = 10; i <15; i++)
{
int id = menuItemId++;
menu.add(1, id, id, "菜单" + i);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
this.menu = menu; // 保存Menu变量
return super.onCreateOptionsMenu(menu);
}
... ...
}
运行程序后,单击模拟器上的“Menu”按钮(为了调用onCreateOptionsMenu方法以获得Menu对象),然后单击“动态添加5个菜单项”按钮,再次单击模拟器上的“Menu”按钮,将显示如图9-4所示的效果。
▲图9-4 动态添加的5 个菜单项
既然有了Menu对象,修改和删除指定的菜单项就变得非常容易了,读者可以使用Menu对象的相应方法来完成这些工作。
9.1.5 带复选框和选项按钮的子菜单
传统的子菜单是以层次结构显示的,而Android中的子菜单采用了弹出式的显示方式。也就是当单击带有子菜单的菜单项后,父菜单会关闭,而在屏幕上会单独显示子菜单。
Menu.addSubMenu方法用来添加子菜单。该方法有4种重载形式,它们的原型如下:
SubMenu addSubMenu(final CharSequence title);
SubMenu addSubMenu(final int titleRes);
SubMenu addSubMenu(final int groupId, final int itemId, int order, final CharSequence title);
SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes);
addSubMenu方法和add方法的参数个数、数据类型和含义完全相同,所不同的是它们的返回值类型。addSubMenu方法返回了一个SubMenu对象(SubMenu是Menu的子接口),可以通过SubMenu.add方法添加子菜单项。SubMenu.add方法与Menu.add方法在功能和使用方法上完全相同,这两个add方法都会返回一个MenuItem对象。
在子菜单项上不能显示图像,但可以在子菜单头上显示图像,不过子菜单项可以带复选框和选项按钮。例如,下面的代码向“文件”菜单项添加了3个子菜单项,并将第1个子菜单项设置成复选框类型,将后两个子菜单项设置成选项按钮类型,同时为子菜单头设置了图像。
源代码文件:ch09/Menu/src/mobile/android/menu/Main.java
public boolean onCreateOptionsMenu(Menu menu)
{
// 添加子菜单
SubMenu fileSubMenu = menu.addSubMenu(1, 1, 2, "文件");
fileSubMenu.setIcon(R.drawable.file); // 设置在选项菜单中显示的图像
fileSubMenu.setHeaderIcon(R.drawable.headerfile); // 设置子菜单头的图像
MenuItem newMenuItem = fileSubMenu.add(1, 2, 2, "新建");
newMenuItem.setCheckable(true); // 将第1个子菜单项设置成复选框类型
newMenuItem.setChecked(true); // 选中第1个子菜单项中的复选框
MenuItem openMenuItem = fileSubMenu.add(2, 3, 3, "打开");
MenuItem exitMenuItem = fileSubMenu.add(2, 4, 4, "退出");
exitMenuItem.setChecked(true); // 将第3个子菜单项的选项按钮设为选中状态
fileSubMenu.setGroupCheckable(2, true, true); // 将后两个子菜单项设置成选项按钮类型
}
在编写上面代码时应注意如下几点。
添加子菜单并不是直接在MenuItem下添加菜单项,而需要使用addSubMenu方法创建一个SubMenu对象,并在SubMenu下添加子菜单项。SubMenu和MenuItem是平级的,这一点在添加子菜单时要注意。
将子菜单项设置成复选框类型,需要使用MenuItem.setCheckable方法。但设置成选项按钮类型,不需要使用setCheckable方法,而要将同一组的选项按钮(子菜单项)的groupId设置成相同的值,同时使用setGroupCheckable方法来设置这个groupId。该方法的第1个参数指定子菜单项的groupId,第2个参数必须为true。如果第3个参数为true,相同groupId的子菜单项会被设置成选项按钮效果;如果为false,相同groupId的子菜单项会被设置成复选框效果。根据相同groupId设置的选项按钮和复选框除了显示效果,并没有什么其他的不同。在菜单项上加选项按钮或复选框主要是为了标志菜单项当前的状态,至于选中或未选中状态代表什么,完全由开发人员自己决定。
使用setChecked方法可以将复选框或选项按钮设置成选中状态。
选项菜单不支持嵌套子菜单,也就是说,不能在子菜单项下再建立子菜单,否则系统将抛出异常。
运行程序后,单击选项菜单中的“文件”菜单项,根据setGroupCheckable方法的第3个参数值是true或false,显示的选项效果或复选框效果如图9-5和图9-6所示。
▲图9-5 选项按钮效果
▲图9-6 复选框按钮效果
9.1.6 上下文菜单
上下文菜单可以和任意View对象进行关联,例如,TextView、EditText、Button等控件都可以关联上下文菜单。上下文菜单的显示效果和子菜单有些类似,也分为菜单头和菜单项。
要想创建上下文菜单,需要实现Activity.onCreateContextMenu方法,该方法的原型如下:
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo);
可以使用ContextMenu.setHeaderTitle和ContextMenu.setHeaderIcon方法设置上下文菜单头的标题和图像。上下文菜单项不能带图像,但可以带复选框或选项按钮(这一点和子菜单相同)。上下文菜单与选项菜单一样,也不支持嵌套子菜单。下面的代码创建一个包含4个菜单项的上下文菜单,其中最后一个菜单项包含两个子菜单项。
源代码文件:ch09/Menu/src/mobile/android/menu/Main.java
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, view, menuInfo);
menu.setHeaderTitle("上下文菜单");
menu.setHeaderIcon(R.drawable.face);
// 添加3个上下文菜单项,Menu.NONE的值是0
menu.add(0, menuItemId++, Menu.NONE, "菜单项1").setCheckable(true).setChecked(true);
menu.add(20, menuItemId++, Menu.NONE, "菜单项2");
// 选中第2个选项按钮
menu.add(20, menuItemId++, Menu.NONE, "菜单项3").setChecked(true);
menu.setGroupCheckable(20, true, true);
// 添加带子菜单的上下文菜单项
SubMenu sub = menu.addSubMenu(0, menuItemId++, Menu.NONE, "子菜单");
sub.add("子菜单项1");
sub.add("子菜单项2");
}
上下文菜单与其他菜单不同的是必须注册到指定的View上才能显示。注册上下文菜单可以使用Activity.registerForContextMenu方法。下面的代码将当前窗口的上下文菜单注册到Button、EditText和TextView上。
Button button = (Button) findViewById(R.id.btnAddMenu);
EditText editText = (EditText) findViewById(R.id.edittext);
TextView textView = (TextView)findViewById(R.id.textview);
// 注册上下文菜单
registerForContextMenu(button);
registerForContextMenu(editText);
registerForContextMenu(textView);
当一个控件关联上下文菜单后,长按该控件,等一会就会显示上下文菜单,如运行程序后,长按TextView控件,会显示如图9-7所示的上下文菜单。
▲图9-7 TextView 控件的上下文菜单
上下文菜单项的单击事件也可以使用OnMenuItemClickListener.onMenuItemSelected方法来响应,这和选项菜单、子菜单的响应方法相同。但对于上下文菜单来说,第3种响应单击事件的方式需要实现Activity.onContextItemSelected方法,该方法的原型如下:
public boolean onContextItemSelected(MenuItem item);
9.1.7 菜单事件
Activity类还有一些与菜单相关的事件方法,这些方法的原型如下:
public boolean onPrepareOptionsMenu(Menu menu);
public void onOptionsMenuClosed(Menu menu);
public void onContextMenuClosed(Menu menu);
public boolean onMenuOpened(int featureId, Menu menu);
这些方法的含义如下:
onPrepareOptionsMenu:在显示选项菜单之前被调用。一般可用来修改即将显示的选项菜单。
onOptionsMenuClosed:在关闭选项菜单时被调用。
onContextMenuClosed:在关闭上下文菜单时被调用。
onMenuOpened:在显示选项菜单之前被调用。该方法在onPrepareOptionsMenu方法之后调用。
9.1.8 从菜单资源中装载菜单
前面介绍的各种类型的菜单都是通过代码添加的,Android SDK还允许我们从菜单资源中装载菜单。Android工程中的所有菜单资源都在res/menu目录中,例如,下面就是一个包含3个菜单项的菜单资源文件。
file_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="新建" />
<item android:title="打开" />
<item android:title="退出" />
</menu>
编写完了菜单资源,还必须在onCreateOptionsMenu或onCreateContextMenu方法中使用如下的代码来装载这个菜单资源。本例在onCreateContextMenu方法中将file_menu.xml文件中的内容作为上下文菜单添加。相当于调用3次Menu.add方法添加3个菜单项。
getMenuInflater().inflate(R.menu.file_menu, menu);