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

自定义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
其实,我个人认为这两个参数的功能重复了。所以,在我看到的很多代码里面,都是将前者specifics设定为null,但是奇怪的是Notepad中却把这两个参数都用上了。到底API中,为何要设计两个如此相像的参数,素我才疏学浅,暂时还没参透其中的玄妙。但是,理解这个两个参数之间的关系的能力还是有的。下面,就结合API的解释和测试例子,谈谈我自己的理解。
首先,关于这个两个参数的说明,官网上是这样子说的:
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)方法里面是如何使用,如下所示:
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作区分。
下面说说,onPrepareOptionsMenu(Menu menu)方法。这个中,addIntentOptions()方法使用得就有点复杂了。如下所示:
 
 // 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
            );

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

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片段:
<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属性。
下面是具体的运行情况截图:
 
因为,系统中存在两个能够能够响应这个intent-filter的Activity,所以,需要进一步选择之。如下所示:
 
 
 
接下来,我们将这个activity中的intent-filter作修改,如下所示:
           <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>
 

 运行截图如下所示:
 
细心的读者可能发现两次在menu中显示的这个activity的名称是不一样的。为何?因为,大家不知道有没留意到这两次的intent-filter的label是不一样的。而menu中显示的名称正是根据这个属性值来决定的。从而,也可以验证了这两次是分别响应不同的intent-filter的。

总结:

第一、onPrepareOptionsMenu(Menu menu)在每次菜单渲染之前都会被调用,而onCreateOptionsMenu(Menu menu)只有在菜单第一次渲染出的时候被调用。而且,在菜单第一次渲染出的时候,在调用onCreateOptionsMenu(Menu menu)之后,onPrepareOptionsMenu(Menu menu)也会被调用。所以,在处理同样的组件的时候,注意前后的冲突。

第二、使用动态菜单组件需要使用到addIntentOptions()方法。其中,specfics参数指明第一批用于请求的intent,而intent参数是在specifics参数之外,指明请求的intent的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值