上篇文章 分析了 NotesList 这个 Activity ,并着重剖析了其中的 intent 机制,本文将继续上篇未完的工作,以 NotesList 为实例介绍 Android 的菜单机制(尤其是动态菜单机制)。
简介
android 提供了三种菜单类型,分别为 options menu , context menu , sub menu 。
options menu 就是通过按 home 键来显示, context menu 需要在 view 上按上 2s 后显示。这两种 menu 都有可以加入子菜单,子菜单不能种不能嵌套子菜单。 options menu 最多只能在屏幕最下面显示 6 个菜单选项,称为 icon menu , icon menu 不能有 checkable 选项。多于 6 的菜单项会以 more icon menu 来调出,称为 expanded menu 。 options menu 通过 activity 的 onCreateOptionsMenu 来生成,这个函数只会在 menu 第一次生成时调用。任何想改变 options menu 的想法只能在 onPrepareOptionsMenu 来实现,这个函数会在 menu 显示前调用。 onOptionsItemSelected 用来处理选中的菜单项。
context menu 是跟某个具体的 view 绑定在一起,在 activity 种用 registerForContextMenu 来为某个 view 注册 context menu 。 context menu 在显示前都会调用 onCreateContextMenu 来生成 menu 。 onContextItemSelected 用来处理选中的菜单项。
android 还提供了对菜单项进行分组的功能,可以把相似功能的菜单项分成同一个组,这样就可以通过调用 setGroupCheckable , setGroupEnabled,setGroupVisible 来设置菜单属性,而无须单独设置。
Options Menu
Notepad 中使用了 options menu 和 context menu 两种菜单。首先来看生成 options menu 的 onCreateOptionsMenu 函数。
这是一个标准的插入一个菜单项的方法,菜单项的 id 为 MENU_ITEM_INSERT 。
有意思的是下面这几句代码:
这到底有何用处呢?其实这是一种动态菜单技术 ( 也有点像插件机制 ) ,若某一个 activity ,其类型是 ”android.intent.category.ALTERNATIVE” ,数据是 ”vnd.android.cursor.dir/vnd.google.note” 的话,系统就会为这个 activity 增加一个菜单项。在 androidmanfest.xml 中查看后发现,没有一个 activity 符合条件,所以这段代码并没有动态添加出任何一个菜单项。
为了验证上述分析,我们可以来做一个实验,在 androidmanfest.xml 中进行修改,看是否会动态生成出菜单项。
实验一
首先我们来创建一个新的 activity 作为目标 activity, 名为 HelloAndroid, 没有什么功能,就是显示一个界面。
它所对应的布局界面 XML 文件如下:
然后修改 androidmanfest.xml ,加入下面这段配置,让 HelloAndroid 满足上述两个条件:
好了,运行下试试,哎,还是没有动态菜单项加入呀!
怎么回事呢?查看代码后发现,原来是 onPrepareOptionsMenu 搞的鬼!这个函数在 onCreateOptionsMenu 之后运行,下面这段代码中,由于 Menu.CATEGORY_ALTERNATIVE 是指向同一个组,所以把 onCreateOptionsMenu 中设置的菜单项给覆盖掉了, 而由于 onPrepareOptionsMenu 没有给 Menu.CATEGORY_ALTERNATIVE 附新值,故 Menu.CATEGORY_ALTERNATIVE 还是为空。
好的,那我们暂时把上面这几句给注释掉,当然,也可以不注释这几句, 在 onCreateOptionsMenu 中改 groupid 号,即将 Menu.CATEGORY_ALTERNATIVE 改为 Menu.first, 其他的也行,但注意不要改为 menu.none, 这样会覆盖掉
添加的菜单。因为 menu.none 也为 0
运行后就可以看到动态菜单出来了!
上面这个 options menu 是在 NotesList 界面上没有日志列表选中的情况下生成的,若先选中一个日志,然后再点 ”menu” ,则生成的 options menu 是下面这样的:
哎,又动态增加了两个菜单项 ”Edit note” 和 ”Edit title”, 这又是如何动态加入的呢?这就是 onPrepareOptionsMenu 的功劳了。
首先获取选中的日志(若没有选择,则 uri 为空)
然后为选中的日志创建一个 intent, 操作类型为 Intent. ACTION_EDIT ,数据为选中日志的 URI. 于是会为选中的日志创建一个 ”Edit note” 菜单项。
这几句和上面 onCreateOptionsMenu 函数中类似,用于动态增加菜单项,若某一个 activity ,其类型是 ”android.intent.category.ALTERNATIVE” ,数据是 ”vnd.android.cursor.item/vnd.google.note” 的话,系统就会为这个 activity 增加一个菜单项。在 androidmanfest.xml 中查看后发现, TitleEditor 这个 activity 符合条件,于是系统就为 TitleEditor 这个 activity 动态添加一个菜单项 ”Edit title” 。
若日志列表为空,则从菜单中删除组号为 Menu. CATEGORY_ALTERNATIVE 的菜单项,只剩下 ”Add note” 菜单项。
处理 “ 选中菜单项 ” 事件
菜单项选中事件的处理非常简单,通过 onOptionsItemSelected 来完成 , 这里只是简单地调用 startActivity( new Intent(Intent. ACTION_INSERT , getIntent().getData())); 这个 intent 的操作类型为 Intent. ACTION_INSERT ,数据为日志列表的 URI ,即 ”content:// com.google.provider.NotePad/notes”
Context Menu
下面介绍另一种菜单 --- 上下文菜单,这通过重载 onCreateContextMenu 函数实现。
首先确认已经选中了日志列表中的一个日志,若没选择,则直接返回。 Cursor 指向选中的日志项。
然后,设置上下文菜单的标题为日志标题
最后为上下文菜单增加一个菜单项
对于上下文菜单项选中的事件处理,是通过重载 onContextItemSelected 实现的。
对于日志的删除,首先调用 ContentUris.withAppendedId(getIntent().getData(), info.id); 来拼接出待删除日志的 URI. 然后 getContentResolver().delete(noteUri, null, null); 调用下层的 Content Provider 去删除此日志。
实验二
来做个简单实验,在上述代码基础上增加一个上下文菜单项。首先在 onCreateContextMenu 函数中增加一个上下文菜单项:
然后为其在 onContextItemSelected 函数中增加一个处理过程:
感谢 Evan JIANG 对前一篇文章的错误之处进行指正,
“<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
只是指明会在 Launcher 中显示图标,同一个 apk 可以在桌面上加很多的图标,分别启动内部不同的多个界面。 “ ,实验后发现确实如此,学习了。