自定义android应用程序的菜单项首先要知道切入点。经过学习,知道主要是两个Activity类中的回调函数,分别是 onCreateOptionsMenu(Menu menu)和onPrepareOptionsMenu(Menu menu)。其中,onPrepareOptionsMenu(Menu menu)是每次激活菜单项目之前都会被调用的,而 onCreateOptionsMenu(Menu menu)仅在第一次激活菜单项目的时候才会被调用。并且,在这个第一次激活菜单项目的时候,也是首先调用 onCreateOptionsMenu(Menu menu),再调用onPrepareOptionsMenu(Menu menu)的。
分析官方例子Notepad发现,因为onPrepareOptionsMenu(Menu menu)在每次激活菜单项目的时候都会被调用的,所以可以在这个回调方法里面,根据实时情况改变菜单项目的内容,而我们下面提到的动态菜单项也是在这个onPrepareOptionsMenu(Menu menu)方法中切入的。
而同时,我们可以在 onCreateOptionsMenu(Menu menu)方法中,完成大致的菜单项渲染工作。而至于菜单项的渲染工作,本人推荐使用XML加载菜单的方式完成。
下面先就如何使用XML加载菜单展开说明。
第一步,在项目的res/menu下新建并且编写定义menu的xml文件。下面就Notepad里面的list_options_menu.xml作简要说明。
res/menu/list_options_menu.xml:
- <?xml version="1.0" encoding="utf-8"?>
- <menu xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- This is our one standard application action (creating a new note). -->
- <item android:id="@+id/menu_add"
- android:icon="@drawable/ic_menu_compose"
- android:title="@string/menu_add"
- android:alphabeticShortcut='a'
- android:showAsAction="always" />
- <!-- the appearance logic is defined in the onPrepareOptionsMenu(Menu menu) -->
- <!-- If there is currently data in the clipboard, this adds a PASTE menu item to the menu
- so that the user can paste in the data.. -->
- <item android:id="@+id/menu_paste"
- android:icon="@drawable/ic_menu_compose"
- android:title="@string/menu_paste"
- android:alphabeticShortcut='p' />
- </menu>
上面的代码主要定义了两个菜单项目“menu_add”和“menu_paste”,而至于注释中提及的逻辑需要在这个xml之外,通过Java代码实现的。
第二步,在onCreateOptionsMenu(Menu menu)回调方法中,通过MenuInflater实例来加载list_options_menu.xml并且完成渲染。
src/NotesList.java的onCreateOptionsMenu(Menu menu)方法:
- // Inflate menu from XML resource
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.list_options_menu, menu);
上面的代码比较直观,其中需要说明的android系统,严格来说,应该eclipse的ADT插件会为每个在res文件夹下的文件生成一个ID(raw子目录除外)。之后,我们就可以通过这个ID引用这个文件(也可以说是资源吧!)
关于通过XML文件加载菜单的部分到这里为止,下面说说动态菜单关于动态菜单项目的原理,这里先作简要说明,详细可以参考本人翻译的官网中说明这一原理的博文。
言归正传,动态菜单就犹如在菜单中安装了一个动态插件,并且通过addIntentOptions()方法定义匹配规则。当系统中的某个应用的Activity能够匹配得上,菜单就增加一个能够调用这个Activity的菜单项。
其中,使用动态菜单的难度在于addIntentOptions()方法,而使用这个方法的难度又在于两个参数的理解上。这两个臭名昭著的参数就是
Intent[]specifics, Intent intent
- These are ordered first by all of the intents resolved in specifics and then any additional activities that can handle intent but did not get included by one of the specifics intents.
对于这句话,我的理解是:android系统先优先选择能够匹配specifics的应用程序,再根据intent标准,在不符合specifics的应用程序之内加载activity。其中,intent中必须要alternative的category属性。
- Intent intent = new Intent(null, getIntent().getData());
- intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
- menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
- new ComponentName(this, NotesList.class), null, intent, 0, items);
这里使用addIntentOptions()方法得十分简练,没有使用到specifics参数,而intent也是十分简单的,基本如同官网上讲义所示。需要注意是intent中的data,特别要同onPrepareOptionsMenu(Menu menu)的那个intent的data作区分。
- // This is the selected item.(By append the ID to the current uri)
- Uri uri = ContentUris.withAppendedId(getIntent().getData(), getSelectedItemId());
- // Creates an array of Intents with one element. This will be used to send an Intent
- // based on the selected menu item.
- Intent[] specifics = new Intent[1];
- //Intent[] specifics = new Intent[2];
- // Sets the Intent in the array to be an EDIT action on the URI of the selected note.
- specifics[0] = new Intent(Intent.ACTION_EDIT, uri);
- // Creates an array of menu items with one element. This will contain the EDIT option.
- MenuItem[] items = new MenuItem[1];
- // Creates an Intent with no specific action, using the URI of the selected note.
- Intent intent = new Intent(null, uri);
- /* Adds the category ALTERNATIVE to the Intent, with the note ID URI as its
- * data. This prepares the Intent as a place to group alternative options in the
- * menu.
- */
- intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
- /*
- * Add alternatives to the menu
- */
- menu.addIntentOptions(
- Menu.CATEGORY_ALTERNATIVE, // Add the Intents as options in the alternatives group.
- Menu.NONE, // A unique item ID is not required.
- Menu.NONE, // The alternatives don't need to be in order.
- null, // The caller's name is not excluded from the group.
- specifics, // These specific options must appear first.
- intent, // These Intent objects map to the options in specifics.
- Menu.NONE, // No flags are required.
- items // The menu items generated from the specifics-to-
- // Intents mapping
- );
再次聚焦到两个参数的定义:
- <intent-filter android:label="@string/title_activity_main" >
- <action android:name="android.intent.action.EDIT" />
- <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
以上代码,是新建的activity中的其中一个intent-filter,也就是声明了这个activity中的一个功能。而当这个功能能够匹配得上某个android系统转来的intent请求的时候,这个activity将会被运行。留意到这个intent-filter没有alternative的category属性,但是有能够匹配specifics中的intent的EDIT ACTION属性和ITEM DATE属性。
- <pre class="html" name="code"> <intent-filter android:label="category">
- <action android:name="android.intent.action.VIEW"/>
- <category android:name="android.intent.category.ALTERNATIVE"/>
- <data android:mimeType="vnd.android.cursor.item/vnd.google.note"/>
- </intent-filter></pre>
- <pre> </pre>
- <pre></pre>
总结:
第一、onPrepareOptionsMenu(Menu menu)在每次菜单渲染之前都会被调用,而onCreateOptionsMenu(Menu menu)只有在菜单第一次渲染出的时候被调用。而且,在菜单第一次渲染出的时候,在调用onCreateOptionsMenu(Menu menu)之后,onPrepareOptionsMenu(Menu menu)也会被调用。所以,在处理同样的组件的时候,注意前后的冲突。
第二、使用动态菜单组件需要使用到addIntentOptions()方法。其中,specfics参数指明第一批用于请求的intent,而intent参数是在specifics参数之外,指明请求的intent的。