现在的位置: 首页 > 综合 > 正文

实战android菜单项之XML加载菜单与动态菜单项

2013年11月05日 ⁄ 综合 ⁄ 共 6814字 ⁄ 字号 评论关闭

自定义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:

[html] view
plain
copy

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <menu xmlns:android="http://schemas.android.com/apk/res/android">  
  3.     <!--  This is our one standard application action (creating a new note). -->  
  4.     <item android:id="@+id/menu_add"  
  5.           android:icon="@drawable/ic_menu_compose"  
  6.           android:title="@string/menu_add"  
  7.           android:alphabeticShortcut='a'  
  8.           android:showAsAction="always" />  
  9.    <!-- the appearance logic is defined in the onPrepareOptionsMenu(Menu menu)  -->  
  10.     <!--  If there is currently data in the clipboard, this adds a PASTE menu item to the menu  
  11.           so that the user can paste in the data.. -->  
  12.     <item android:id="@+id/menu_paste"  
  13.           android:icon="@drawable/ic_menu_compose"  
  14.           android:title="@string/menu_paste"  
  15.           android:alphabeticShortcut='p' />  
  16. </menu>  


上面的代码主要定义了两个菜单项目“menu_add”和“menu_paste”,而至于注释中提及的逻辑需要在这个xml之外,通过Java代码实现的。

第二步,在onCreateOptionsMenu(Menu menu)回调方法中,通过MenuInflater实例来加载list_options_menu.xml并且完成渲染。

src/NotesList.java的onCreateOptionsMenu(Menu menu)方法:

[java] view
plain
copy

  1. // Inflate menu from XML resource  
  2.         MenuInflater inflater = getMenuInflater();  
  3.         inflater.inflate(R.menu.list_options_menu, menu);  


上面的代码比较直观,其中需要说明的android系统,严格来说,应该eclipse的ADT插件会为每个在res文件夹下的文件生成一个ID(raw子目录除外)。之后,我们就可以通过这个ID引用这个文件(也可以说是资源吧!)

关于通过XML文件加载菜单的部分到这里为止,下面说说动态菜单关于动态菜单项目的原理,这里先作简要说明,详细可以参考本人翻译的官网中说明这一原理的博文

言归正传,动态菜单就犹如在菜单中安装了一个动态插件,并且通过addIntentOptions()方法定义匹配规则。当系统中的某个应用的Activity能够匹配得上,菜单就增加一个能够调用这个Activity的菜单项。

其中,使用动态菜单的难度在于addIntentOptions()方法,而使用这个方法的难度又在于两个参数的理解上。这两个臭名昭著的参数就是

 Intent[]specifics, Intent intent

其实,我个人认为这两个参数的功能重复了。所以,在我看到的很多代码里面,都是将前者specifics设定为null,但是奇怪的是Notepad中却把这两个参数都用上了。到底API中,为何要设计两个如此相像的参数,素我才疏学浅,暂时还没参透其中的玄妙。但是,理解这个两个参数之间的关系的能力还是有的。下面,就结合API的解释和测试例子,谈谈我自己的理解。
首先,关于这个两个参数的说明,官网上是这样子说的:

[plain] view
plain
copy

  1. 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属性。

为了验证我的想法,再次通过Notepad研究一下。
关于Menu的渲染,Notepad中主要有两处关于动态菜单项目的代码。一个是在onPrepareOptionsMenu(Menu menu),而另外一个在onCreateOptionsMenu(Menu menu)方法中。如前所述,onCreateOptionsMenu(Menu menu)方法会在onPrepareOptionsMenu(Menu menu)之后调用。在之前转载的一篇博文中,作者的观点是,这两部分因为有重叠所以有冲突。对于这个看法本人是不认同的。
本人认为,两部分虽然很相似,但是实际是在做不同的事情的。
在onCreateOptionsMenu(Menu menu)方法中动态菜单项目主要是针对集合的。因为,此时作为addIntentOptions()方法参数的intent,其中的data类型是content://com.google.provider.NotePad/notes。而在onPrepareOptionsMenu(Menu menu)中的intent却是某个具体的项目。
推断编写Notepad的作者意图,menu在渲染过程中,默认使用针对data为集合的intent添加动态菜单项目,而这个默认的行为在onCreateOptionsMenu(Menu menu)方法中定义。而因为onPrepareOptionsMenu(Menu menu)会在每次渲染出menu之前都会被调用,所以,在这个回调方法体内定义了更加灵活的逻辑,即根据当前选择条目的情况,判断是否为其生成动态条目。自然请求建立这个动态条目的intent的data是具体条目的ID。
言归正传,说说addIntentOptions()方法本身的使用,特别是上文提到的两个参数的使用。
首先,我们看看onCreateOptionsMenu(Menu menu)方法里面是如何使用,如下所示:

[java] view
plain
copy

  1. Intent intent = new Intent(null, getIntent().getData());        
  2.         intent.addCategory(Intent.CATEGORY_ALTERNATIVE);  
  3.         menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 00,  
  4.                 new ComponentName(this, NotesList.class), null, intent, 0, items);  

这里使用addIntentOptions()方法得十分简练,没有使用到specifics参数,而intent也是十分简单的,基本如同官网上讲义所示。需要注意是intent中的data,特别要同onPrepareOptionsMenu(Menu menu)的那个intent的data作区分。
下面说说,onPrepareOptionsMenu(Menu menu)方法。这个中,addIntentOptions()方法使用得就有点复杂了。如下所示:
 

[java] view
plain
copy

  1. // This is the selected item.(By append the ID to the current uri)  
  2.            Uri uri = ContentUris.withAppendedId(getIntent().getData(), getSelectedItemId());  
  3.   
  4.            // Creates an array of Intents with one element. This will be used to send an Intent  
  5.            // based on the selected menu item.  
  6.            Intent[] specifics = new Intent[1];  
  7.            //Intent[] specifics = new Intent[2];  
  8.   
  9.            // Sets the Intent in the array to be an EDIT action on the URI of the selected note.  
  10.            specifics[0] = new Intent(Intent.ACTION_EDIT, uri);  
  11.            
  12.            // Creates an array of menu items with one element. This will contain the EDIT option.  
  13.            MenuItem[] items = new MenuItem[1];  
  14.   
  15.            // Creates an Intent with no specific action, using the URI of the selected note.  
  16.            Intent intent = new Intent(null, uri);  
  17.   
  18.            /* Adds the category ALTERNATIVE to the Intent, with the note ID URI as its 
  19.             * data. This prepares the Intent as a place to group alternative options in the 
  20.             * menu. 
  21.             */  
  22.            intent.addCategory(Intent.CATEGORY_ALTERNATIVE);  
  23.   
  24.            /* 
  25.             * Add alternatives to the menu 
  26.             */  
  27.            menu.addIntentOptions(  
  28.                Menu.CATEGORY_ALTERNATIVE,  // Add the Intents as options in the alternatives group.  
  29.                Menu.NONE,                  // A unique item ID is not required.  
  30.                Menu.NONE,                  // The alternatives don't need to be in order.  
  31.                null,                       // The caller's name is not excluded from the group.  
  32.                specifics,                  // These specific options must appear first.  
  33.                intent,                     // These Intent objects map to the options in specifics.  
  34.                Menu.NONE,                  // No flags are required.  
  35.                items                       // The menu items generated from the specifics-to-  
  36.                                            // Intents mapping  
  37.            );  

再次聚焦到两个参数的定义:

specifics参数,里面定义一个intent,并且这个intent的action为Intent.ACTION_EDIT以及data的URI为notes表中的某一行,并没有Alternative的category属性。而intent参数定义了Alternative的category属性。
据此,我的理解是,android系统发出specifics中定义的intent请求,然后再发出intent参数定义的intent请求。并且,能够匹配intent参数定义的intent请求的Activity必须要不能匹配specifics中定义的intent请求。
据此,我认为能够成为动态菜单项的Activity不一定需要有category属性为alternative的intent-filter。只要能够满足specifics即可。为了验证这一点,下面我做了一个实验。
新建一个应用,这个应用中的activity采用ADT默认生成的方式,所有的玄妙之处在这个应用程序的AndroidManifest.xml。
留意下面的AndroidManifest.xml片段:

[html] view
plain
copy

  1. <intent-filter android:label="@string/title_activity_main" >

抱歉!评论已关闭.