创 建 菜 单
Blog:http://xw59418.blog.163.com/ QQ:935207108
菜单是一个应用的重要部分,它提供给用户一个使用应用功能和设置的熟悉的界面。Android系统为你提供了一个简单的程序接口可以让你在你的应用中提供应用菜单。
Andriod提供了下面的三种类型的应用菜单:
选择菜单
一个activity最基本的菜单项的容器,当用户触摸设备的MENU按钮时会弹出来。当你的应用是运行在Android3.0或者更后面的版本时,你可以通过将它们直接放在 Action Bar里面来对选择的菜单项提供快速的访问,就像“动作菜单项”
上下文菜单
当用户长按在一个被注册用来提供一个上下文菜单的view上时会出现的一个漂浮的菜单项的列。
子菜单
当用户按下一个包含了内嵌菜单的菜单项时出现的一个漂浮的菜单项列。
这篇文档会向你展示如何创建每种类型的菜单,使用XML来定义菜单的内容,还有使用在你的activity里的回调函数来响应用户选择一个菜单项的动作。
创建一个菜单资源
作为在你的应用的代码中实例Menu对象的代替,你可以在一个menu resourceXML里定义一个菜单和其所有的菜单项列,然后在你的应用代码中装载它(当作一个程序化的对象)。在XML里定义你的菜单是一个好的做法,因为它把你的界面设计和你的应用代码分开了。它也可以显而易见的看出在XML里的菜单的结构和内容。
要定义个菜单,要在你的项目的res/menu/目录里新建一个XML文件并用下面的元素来构建你的菜单。
<menu>
创建一个Menu,是一个菜单项的容器。<menu>元素必须作为根节点并控制着一个或多个<item>和<group>元素。
<item>
创建一个 MenuItem,代表了菜单里的一个菜单项。这个元素也可以内嵌一个<menu>元素来创建一个子菜单。
<group>
一个可选项,<item>元素的不可见的容器归类菜单项,这可以让他们共享一些属性,比如活动状态以及可见性。参见 Menu groups。
例如,下面是res/menu/文件里的game_menu.xml
l:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/new_game"
android:icon="@drawable/ic_new_game"
android:title="@string/new_game" />
<item android:id="@+id/help"
android:icon="@drawable/ic_help"
android:title="@string/help" />
</menu>
这个例子定义了一个带有两个菜单项的菜单。每一项都包括下面的属性:
android:id
一个对于这一项唯一的资源ID,这样应用就可以在用户选择它时识别它。
android:icon
一个对用户可见的图标的图像资源
android:title
一个对用户可见的标题的字符串资源
你可以将还有很多的属性包含在<item>元素里,可以包括一些指定菜单项如何在Action Bar里出现的属性。要了解更多的关于菜单资源的XML语法和属性的信息,请参见 Menu Resource资源。
装载菜单资源
在你的应用代码里,你可以使用 MenuInflater.inflate()来装载你的菜单资源(把XML资源转换成一个程序式的对象)。例如,下面的代码在onCreateOptionsMenu()回调函数里装载了上面定义的game_menu.xml文件,这是用于选择菜单的:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.game_menu, menu);
return true;
}
getMenuInflater()方法返回了Activity的一个MenuInflater。使用这个对象你可以调用 inflate()来装载一个菜单资源到 Menu对象里去。在这个例子里,被定义在game_menu.xml里的菜单资源被装载到了传入到 onCreateOptionsMenu()里的 Menu里。(作为创建一个选择菜单的这个回调方法会在下面的部分详细说明)。
创建一个选择菜单
选择菜单是你需要包含基本的应用功能和必要的导航项的地方(例如,一个打开应用设置的按钮)。在选择菜单里的菜单项有两种不同的方法来接近:MENU按钮或者在 Action Bar里。(运行在了Android3.0或更高的设备)用户可以使用MENU键来打开选择菜单。图形1展现了一个选择菜单的截图。
当运行在Android2.3或者更低的版本上的设备上时,选择菜单会在屏幕的地步出现,正如图形1所展示的。当菜单打开时,第一个选择菜单可视的部分是图标菜单。它控制了前面的6个菜单项。如果你添加了不止6个菜单项到选择菜单里的话,Android会将第六项和其后面的菜单项放在溢出菜单上面,用户可以触摸“More”菜单项来打开它们。
在Android3.0和更高的版本里,选择菜单里的菜单项被放置在Action Bar里面,它会出现在activity的顶部的传统的标题栏上面。默认的所有选择菜单里的菜单项都被放在溢出菜单里面,用户可以触摸在Action Bar右边的菜单图标来打开它们。但是,你可以将选择菜单项作为“action items”直接放置在Action Bar里面来立刻进入菜单项,正如图形2所示:
图形2:在Email应用中的Action Bar的截图,其带有两个来自选择菜单了action iems再加上一个溢出菜单
图形1:浏览器应用中的选择菜单截图
当Android系统第一次创建选择菜单时,Android会调用你的activity的 onCreateOptionsMenu()方法。在你的activity里重写这个方法并填充传入到这个方法里的 Menu。填充 Menu是通过装载一个在上面Inflating a Menu Resource里描述的菜单资源。例如:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.game_menu, menu);
return true;
}
你也可以使用代码来填充,使用 add()方法来添加菜单项
Note: 在Android2.3以及更低的版本里,当用户第一次打开时系统会调用方法来创建选择菜单,但是在Android3.0和更高版本,系统会在activity创建时就立即创建它,这是为了填充Action Bar。
响应你的动作
当用户从选择菜单里选择了一个菜单项时,系统会调用你的activity的onOptionsItemSelected()方法。这个方法会传递用户所选的MenuItem。你可以通过调用getItemId()来识别这个菜单项,它会返回那个菜单项的唯一的ID(在菜单资源里的属性定义的android:id或者在add()方法里传入的一个数字)。你可以通过所知道的菜单项来匹配这个ID并执行相应的动作。
例如:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.new_game:
newGame();
return true;
case R.id.help:
showHelp();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
在这个例子里, getItemId()查询了被选中的菜单项的ID并且switch语句会比较在XML资源中指定的菜单项的资源ID。当switch的一个case成功的匹配了这个菜单项时,它会返回“true”来表示被控制的所选项。否则,default语句会把这个菜单项传递给其父类,假如父类能够处理所选项的话。(如果你直接继承了 Activity类,随后父类返回了false,但是这个是一个好方法来传递没有被控制的菜单选项到其父类中而不是直接的返回false。)
此外,Android3.0添加了你可以在 menu resourceXML里为一个菜单项定义on-click动作,使用android:onClick属性。所以你就不需要实现onOptionsItemSelected()方法了。使用android:onClick属性,你可以定义一个当用户选中这个菜单项时要调用的方法。你的activity必须然后实现定义在android:onClick属性里的方法来使它接收一个单独的 MenuItem参数——当系统调用这个方法时,它会传入被选中的菜单项。
Tip:如果你的应用包含了多个activity并且其中的一些要提供相同的选择菜单,那你应该考虑创建一个仅实现了 onCreateOptionsMenu()和 onOptionsItemSelected()方法的Activity。然后为每一个Activity继承这个类就可以共享一个相同的选择菜单。这种方法你必须为控制菜单活动处理一系列的代码并且每个子类都要继承这个菜单的动作。
如果你想要在你的子Activity里添加菜单选项,你在那个Activity里可以重写onCreateOptionsMenu()。并调用super.onCreateOptionsMenu(menu)来让你的原始菜单项被创建,然后用 menu.add()来添加新的菜单项。你也可以为个别的菜单项重写父类的菜单工作
在菜单运行时更改菜单
一旦activity被创建了,onCreateOptionsMenu()方法只会被调用一次,如上面所描述的。系统会保持并重复使用你在方法里定义的 Menu 直到你的activity被销毁。如果你想要在它每次被打开时更改选择菜单,你必须重写 onPrepareOptionsMenu()方法。这个方法会传入当前存在的 Menu。这在你想要根据当前应用的状态来删除,添加,弃用或者启用一个菜单项时是很有用的。
在Android2.3和更低的版本,系统会在每次打开选择菜单时调用 onPrepareOptionsMenu()。
在Android3.0和更高的版本里,你必须在你想更新菜单时调用invalidateOptionsMenu() ,因为菜单是一直打开的。系统会随后调用 onPrepareOptionsMenu()使你可以更新菜单项。
注意:你不能够根据View在选项菜单中获得当前焦点的改变它。当在触摸模式时,(当用户没有使用轨迹或者d-pad的时候)View就不能得到焦点,所以你不能使用焦点来作为在选择菜单里更改菜单的依据。如果你想要提供一个对View上下文敏感的菜单项,请使用 Context Menu。
如果你正在Android3.0或者更高的版本上开发,请确保已经阅读了 Using the Action Bar。
创建上下文菜单
上下文菜单是概念上相似在PC上当用户执行“右键”后显示的菜单。你可以在用户界面上使用上下文菜单来提供给用户有关指定项的访问动作。在Android里,当用户在某一项上执行“长按”会显示处一个上下文菜单。
你可以为任何的View创建一个上下文菜单,不过大多数的上下文菜单项是。当用户在一个ListView里的某一项上长按并且这个列已经注册了上下文菜单时,列表项会通过动画它的背景色来给用户一个上下文菜单是可用的信号——它会在开启这个上下文菜单时从橙色转变成白色。(通讯录应用就演示了这个功能)
为了使一个View提供一个上下文菜单,你必须为上下文菜单“注册”这个view。调用registerForContextMenu()并且传进去你想要设置上下文菜单的 View 。当这个View接收 到一个长按时,它会显示一个上下文菜单。
要定义一个上下文菜单的实现和动作,请重写你的Activity的上下文菜单的回调方法,onCreateContextMenu()和onContextItemSelected()。
例如,下面就是使用了 context_menu.xml菜单资源的onCreateContextMenu():
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.context_menu, menu);
}
MenuInflater是用来从一个 menu resource里装载上下文菜单的。(你可以使用add()方法来添加菜单项)。这个回调函数的参数包含了用户所选择的View和提供了关于被选中项的一些附加信息的一个 ContextMenu.ContextMenuInfo对象。你可能会用到这些参数来决定哪个上下文菜单被创建见了,但是在这个例子里,所有这个Activity的上下文菜单都是一样的。
然后当用户从上下文菜单中选择了一项时,系统会调用 onContextItemSelected()。下面是一个你如何处理选中项的例子:
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
switch (item.getItemId()) {
case R.id.edit:
editNote(info.id);
return true;
case R.id.delete:
deleteNote(info.id);
return true;
default:
return super.onContextItemSelected(item);
}
}
这个代码的结构和Creating an Options Menu例子里的相似, getItemId()查询了所选菜单项的ID并且一个switch语句匹配了在菜单资源定义了这个ID的那一项,并且也和选项菜单例子相似,default语句调用了父类的方法,加入父类能够处理这项的话,如果必要的话。
注册一个 ListView:如果你的activity使用一个ListView并且你想要所有的列选项都提供一个上下文菜单,那么可以为所有的列项注册一个上下文菜单,传入ListView到方法registerForContextMenu()里。例如如果你正在使用 ListActivity,那么可以这样来注册所有的列项:
registerForContextMenu(getListView());
在这个例子里,选中项是来自一个 ListView里的。要在选中项上执行一个动作的话,这个应用需要知道选中项在这个列的ID(在ListView中的位置)。要得到这个ID,应用会调用getMenuInfo(),这会返回一个在id字段包含了选中项的列ID的 AdapterView.AdapterContextMenuInfo对象。局部方法editNote()和deleteNote()会接收这个ID来执行指定的列ID上的数据上的动作。
注意:一个上下文菜单中Item不支持图标或者快捷键。
创建子菜单
子菜单是用户可以通过选中一项来在另外一个菜单中打开的菜单。你可以在任何菜单里添加子菜单(除了子菜单)。子菜单在当你的应用拥有很多可以被组织成主题的功能时是很有用的,就像在PC应用菜单栏上的项一样(文件,编辑,视图等等)。
当创建你的 menu resource时,你可以通过添加一个当作 <item>子类的<menu>元素来创建一个子菜单。例如:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/file"
android:icon="@drawable/file"
android:title="@string/file" >
<!-- "file" submenu -->
<menu>
<item android:id="@+id/create_new"
android:title="@string/create_new" />
<item android:id="@+id/open"
android:title="@string/open" />
</menu>
</item>
</menu>
当用户选中了一个子菜单中的选项时,父菜单的各自的on-item-selected回调函数会接收这个事件。例如,如果上面的菜单被应用成一个选择菜单,然后当子菜单项被选中时onOptionsItemSelected() 方法会被调用。
你也可以使用 addSubMenu()来动态的向一个已存在的 Menu里添加一个SubMenu 。它会返回一个新的SubMenu 对象,你可以用来添加子菜单选项,使用add()。
其他的菜单功能
下面是一些你可以适用在大多菜单项上的其他的功能
菜单组
菜单组是要共享某些特性的菜单项的集合。在组里面,你可以:
●使用 setGroupVisible()显示或隐藏所有的菜单项
●使用setGroupEnabled()来决定菜单项是否有效
●使用setGroupCheckable()来指定是否所有的菜单项是多选的
你可以在你的菜单资源里通过在<item>元素里边内嵌一个<group>元素来创建一个组或者通过在add()方法里指定一个组ID。
下面是一个包含了组的菜单资源:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/item1"
android:icon="@drawable/item1"
android:title="@string/item1" />
<!-- menu group -->
<group android:id="@+id/group1">
<item android:id="@+id/groupItem1"
android:title="@string/groupItem1" />
<item android:id="@+id/groupItem2"
android:title="@string/groupItem2" />
</group>
</menu>
在组里面的菜单项和不在组里的第一个菜单项一起出现——在菜单里的所有三项都是兄妹。但是,你可以通过引用组的ID和使用上面列出的方法来更改在同一个组的两项的特征。
可多选的菜单项
菜单可以用作一个选项开关的界面,为独立的选项使用一个多选框,或者为一组互相独立的选项使用一个单选按钮。图形3展示了一个可检测的单选按钮的菜单项。
注意:(选择菜单)里的带图标的菜单的菜单项不能显示成一个多选框或单选按钮的样子。如果你选择使图标菜单的菜单项可选的话,你必须在每次状态改变时通过交换图标或文本来手动的表示可选的状态。
图形3:一个带有可选项的子菜单的截图
你可以使用在<item>元素里android:checkable属性来定义每个单独的菜单项定义它的可选动作,或者在<group>元素里使用android:checkableBehavior属性来为一个整体的组定义可选动作。例如,在这个菜单组里的所有的选项都被作为单选按钮来可选:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item android:id="@+id/red"
android:title="@string/red" />
<item android:id="@+id/blue"
android:title="@string/blue" />
</group>
</menu>
android:checkableBehavior属性接收下面中的任意一个:
single
只有其中的一个选项可以可选(单选按钮)
all
所有的选项都能可选(多选框)
none
没有选项可选
你可以使用<item>元素里的android:checked属性对一个菜单项应用一个默认的可选状态并且可以在代码里使用 setChecked()方法来改变它。
当一个被选的菜单项被选中时,系统会调用你的各自的item-selected回调方法(比如onOptionsItemSelected())。在这里面你必须设置你的多选框的状态,因为一个多选或单选按钮不会自动的转换它的状态。你可以使用isChecked()方法查到菜单项的当前的状态(在用户选中之前调用)并且然后使用 setChecked()来设置其状态。例如:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.vibrate:
case R.id.dont_vibrate:
if (item.isChecked()) item.setChecked(false);
else item.setChecked(true);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
如果你不用这种方法来设置可选状态,那么当用户选择它时带有可视状态的菜单项(多选框或单选按钮)是不会改变它的状态的。
注意:被选的菜单项旨在用于一个会话基础并且在应用被销毁时不被保存。如果想要拥有可以为保存其状态的应用设置的话,你应该使用Shared Preferences来存储数据。
快捷键
当用户有一个物理键盘时可以帮助用户在选择菜单里快速方法菜单项,你可以使用字符或者数字来作为快速访问的快捷键,在<item>元素里使用android:alphabeticShortcut和android:numericShortcut 属性。你也可以使用setAlphabeticShortcut(char)和 setNumericShortcut(char)方法来设置。快捷键是不区分大小写的。
例如,如果你使用“s”字母来作为一个“save”菜单项的字母快捷键的话,那么当菜单打开时(或者用户按了MENU按钮时)并且用户按下了“s”键时,“save”菜单项会被选中。
快捷键可以在菜单项里作为一个小提示被显示,在菜单项名称下面(除了在选择菜单里的图标菜单,它只在用户按下MENU按钮的时候显示)。
注意:菜单项的快捷键只在拥有物理键盘的设备上有效。快捷键也可以在上下文菜单中被加到菜单项里去。
动态的添加菜单intents
有时你想要一个菜单项可以使用一个Intent来启动一个activity(不管是在你的应用或其他应用中的activity)。当你知道你要使用的intent并知道指定一个会初始化这个intent的菜单项的时候,你可以在合适的on-item-selected 回调方法里使用startActivity()来执行这个intent。(比如 onOptionsItemSelected()回调方法)
但是如果你确定用户的设备里包含一个可处理这个intent的应用,然后添加一个能调用它的菜单项结果是一个没有功能的菜单项,因为这个intent可能不会启动一个activity。为了解决这个问题,当Android在设备上找到可以处理你的intent的activity的时候,Android可以让你在你的菜单里动态的添加菜单项。
要根据可以接收一个intent的有效的activity来添加菜单项:
1. 定义一个都带有 CATEGORY_ALTERNATIVE或CATEGORY_SELECTED_ALTERNATIVE的category ,再加上另外的要求。
2. 调用 Menu.addIntentOptions()方法。Android会随后寻找可以处理这个intent的应用并且将它添加到你的菜单里去。
如果没有应用能够满足这个intent,那么就没有菜单项被添加。
注意:CATEGORY_SELECTED_ALTERNATIVE是用来处理在屏幕上当前被选中的元素。所以,它只应该在onCreateContextMenu()里创建一个菜单时被使用。
例如:
@Override
public boolean onCreateOptionsMenu(Menu menu){
super.onCreateOptionsMenu(menu);
// Create an Intent that describes the requirements to fulfill, to be included
// in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE.
Intent intent = new Intent(null, dataUri);
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
// Search and populate the menu with acceptable offering applications.
menu.addIntentOptions(
R.id.intent_group, // Menu group to which new items will be added
0, // Unique item ID (none)
0, // Order for the items (none)
this.getComponentName(), // The current activity name
null, // Specific items to place first (none)
intent, // Intent created above that describes our requirements
0, // Additional flags to control items (none)
null); // Array of MenuItems that correlate to specific items (none)
return true;
}
对于每一个提供了一个intent filter来匹配定义的intent的activity,一个菜单项会被添加,使用activity里的intent filter的属性作为菜单项的标题还有应用的图标作为菜单项的图标。addIntentOptions()方法会返回添加的菜单项的数量。
注意:当你调用的时候,它重写了所有第一个参数被被菜单组指定的的菜单项。
允许你的activity被添加到其他的菜单里
你可以向其他的应用提供你的activity的服务,所以你的应用可以包含在其他引用的菜单里(和上面描述的规则相反)。
要想被包含在其他的应用菜单里,你需要定一个和往常一样的intent filter,但是必须保证在intent filter的category里包括一个CATEGORY_ALTERNATIVE或CATEGORY_SELECTED_ALTERNATIVE 值。例如:
<intent-filter label="Resize Image">
...
<category android:name="android.intent.category.ALTERNATIVE" />
<category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
...
</intent-filter>
可以在 Intents and Intent Filters文献里阅读更多的关于要写的intent filter
如果需要一个使用了技术的样例应用,请参见 Note Pad样例代码。