首页 > 代码库 > Android UI开发第二十四篇——Action Bar

Android UI开发第二十四篇——Action Bar

   Action bar是一个标识应用程序和用户位置的窗口功能,并且给用户提供操作和导航模式。在大多数的情况下,当你需要突出展现用户行为或全局导航的activity中使用action bar,因为action bar能够使应用程序给用户提供一致的界面,并且系统能够很好根据不同的屏幕配置来适应操作栏的外观。你能够用ActionBar的对象的API来控制操作栏的行为和可见性,这些API被添加在Android3.0(API 级别 11)中。


      Action bar的主要目的是:


        1.  提供一个用于识别应用程序的标示和用户的位置的专用空间。

         这个空间的左边是应用的图标或logo,以及Activity的标题。但是,如果是像当前选择的标签这样的标识当前View对象的导航标签,你可以选择删除Activity的标题。

        2.  在不同的应用程序之间提供一致的导航和视觉体验。

        Action bar提供了用于Fragment间切换的内置导航标签。它还提供了一个用于替换导航模式或优化当前视觉效果(如按照不同条件排序的列表)的下拉列表。

        3.  突出Activity的关键操作(如“搜索”、“创建”、“共享”等),并且在可预见的方法内给用户提供快捷的访问。

       对于关键的用户操作,你能够通过把选项菜单项作为操作项直接放到操作栏中,从而提供快捷的访问。操作项目还能提供一个操作窗口,这个窗口给更直接的操作行为提供一个嵌入的窗口部件。没有改进成操作项的菜单项在溢出菜单中还是有效的,用户既可以使用设备上的菜单按钮(设备上有按钮的时候),也可以使用操作栏中的溢出菜单按钮(当设备上不包含菜单按钮时)来显示这些操作项目。

       上面的总结一下:Action bar就是替换3.0以前的tittle bar和menu。

             

        图1. Honeycomb Gallery应用中的操作栏,从左边开始,依次是logo、导航选项标签和操作项(在右边插入的一个悬浮菜单按钮)。

         Note: If you‘re looking for information about the contextual action bar for displaying contextual action items, see the Menu guide.

        Action Bar Design   For design guidelines, read Android Design‘s Action Bar guide.


添加Action Bar

       从Android3.0(API级别 11)开始,Action bar被包含在所有的使用Theme.Hole主题的Activity(或者是这些Activity的子类)中,当targetSdkVersion或minSdkVersion属性被设置为“11”或更大的数值是,这个主题是默认的主题一。如:

  1. <manifest ... >  
        <uses-sdk android:minSdkVersion="4"  
                  android:targetSdkVersion="11" />  
        ...  
    </manifest>

   在这个例子中,应用程序要求最小的API版本级别是4(Android 1.6),但是它还要求了目标API版本级别是11(Android 3.0)。这样,当应用程序运行在Android3.0或更高的版本上时,系统就会给每个Activity应用holographic  主题,这样,每个Activity就会包含Action bar。

        如果你想使用ActionBar API来进行添加导航模式和修改操作栏样式的操作,你应该把minSdkVersion属性设置为“11”或更大的值。有一些方法可以使你的应用支持更旧的Android版本,同时在API等级为11或更高的API等级的机器的使你的应用支持一些Action bar apis。为了保持后向兼容,请参考边框内的内容(边框内容如下)。


Remaining backward-compatible


If you want to provide an action bar in your application and remain compatible with versions of Android older than 3.0, you need to create the action bar in your activity‘s layout (because theActionBar class is not available on older versions).

To help you, the Action Bar Compatibility sample app provides an API layer and action bar layout that allows your app to use some of theActionBar APIs and also support older versions of Android by replacing the traditional title bar with a custom action bar layout.

删除Action bar

      如果你不想要Action bar,把Activity的主题设置为Theme.Holo.NoActionBar就可以了,如:

<
activity android:theme = "@android:style/Theme.Holo.NoActionBar"
>

或者使用Action bar的 hide()方法,如下:

  1. ActionBar actionBar = getActionBar();  
    actionBar.hide();

  当Action bar隐藏时,系统会调整你的Activity来填充当前有效的屏幕空间。你能够使用show()方法来再次显示操作栏。

      在隐藏和删除Action bar时,要当心为了适应被Action bar占用的空间而导致的Activity的重新布局。如果你的Activity有规律的隐藏和显示Action bar,你可能想要使用覆盖模式。覆盖模式在Activity的顶部描画操作栏,而不是在它们所拥有的屏幕的区域。这样,在Action bar隐藏和重新显示时,你的布局保持不变。要使用覆盖模式,就要给Activity创建一个主题,并且把android:windowActionBarOverlay属性设置为true。

       提示:如果你有一个删除了Action bar的定制化的Activity主题,它把android:windowActionBar样式属性设置为false。但是,如果你使用了删除Action bar的一个主题,那么,创建窗口将不允许Action bar再显示,因此,你不能在以后给这个Activity添加Action bar---因为getActionBar()方法将返回null。


添加操作项

       有些时候,你可能想要让用户直接访问选项菜单中的一个项目,因此你要把应该在Action bar中显示的菜单项作为一个操作项来声明。操作项能够能够包含一个图标或文本标题。如果一个菜单项不作为一个操作项显示,那么系统就会把它放到悬浮菜单中。悬浮菜单既可以通过设备的Menu按钮来显示,也可以在Action bar中一个额外的按钮来显示。

       当Activity首次启动时,系统会调用onCreateOptionsMenu()方法给你的Activity组装Action bar和悬浮菜单。在这个回调方法中应该加载在XML文件中定义的菜单项资源,如:

  1. @Override  
    public boolean onCreateOptionsMenu(Menu menu) {  
        MenuInflater inflater = getMenuInflater();  
        inflater.inflate(R.menu.main_activity, menu);  
        return true;  
    }

             

         图2. 带有图标和文本标题的两个操作项,和悬浮菜单按钮。

       在XML文件中,你能够通过给<item>元素声明android:showAsAction=”ifRoom”属性,请求把一个菜单项作为一个操作项来显示。用这种方式,只在有有效的空间时,菜单项才能显示在Action bar中。如果没有足够的空间,这个菜单项会显示在悬浮菜单中。

        如果你的菜单项支持标题和图标---带有android:title和android:icon属性---那么默认情况下,操作项仅显示图标。如果你要显示文本标题,就要给android:showAsAction属性添加withText设置,如:

  1. <?xml version="1.0" encoding="utf-8"?>  
    <menu xmlns:android="http://schemas.android.com/apk/res/android">  
        <item android:id="@+id/menu_save"  
              android:icon="@drawable/ic_menu_save"  
              android:title="@string/menu_save"  
              android:showAsAction="ifRoom|withText" />  
    </menu>

提示:withText值示意Action bar要显示文本标题。Action bar会尽可能的显示这个标题,但是,如果图标有效并且受到Action bar空间的限制,文本标题有可能显示不全。

当用户选择了一个操作项时,Activity会接收一个onOptionsItemSelected()的回调,要把android:id属性支持的ID传递给这个方法。

给每个菜单项定义android:title属性是至关重要的,即使你没有给操作项声明标题。原因如下:

1.  如果Action bar中没有足够的空间来显示操作项,那么菜单项就会显示在悬浮菜单中,并仅显示标题;

2.  屏幕阅读器要给视障用户朗读菜单项标题;

3.  如果仅用图标来显示操作项,那么,用户能够长按这个项目,用操作项的标题来显示提示信息。

注意:如果你添加源于Fragment对象的菜单项,那么通过Fragment类的onCreateOptionsMenu onCreateOptionsMenu回调方法,当用户选择其中一个Fragment菜单项时,系统会对用那个Fragment对象对应的onOptionsItemSelected()方法。但是,Activity有机会首先处理这个事件,因为系统在调用对应的Fragment对象的onOptionsItemSelected()方法之前会调用Activity的相同的回调方法。

       你也能够声明一个菜单项,让它始终作为操作项来显示,而不是在空间不足时就放到悬浮菜单中。大多数情况下,你不应该使用always属性值来强制一个菜单项始终显示在操作栏中,但是,当提供了一个不给悬浮菜单提供默认操作的操作窗口时,你就需要始终显示一个菜单项。但是要警惕,太多的操作项会创建一个混乱的UI,并且会导致窄屏设备上的布局问题。最好的方法是使用ifRoom属性值,它允许系统在操作栏空间不足的时候,把菜单项移到悬浮菜单中。

选择操作项:

       通过评估一些关键的特性,你应该仔细的选择选项菜单中的那些菜单项应该作为操作项来显示,通常,每个操作项应该至少满足下列特性之一:

        1.  经常使用:用户百分之七十以上的访问都需要使用的操作,或者是要连续的多次使用的操作。

        2.  重要:它是一个用户能够很容易找到的操作,即使它不是经常性的操作,也需要用户在需要的时候能够轻易的找到它,并执行。

             如,Wi-Fi设置中的添加网络等。

        3.  典型:它是一些类似应用程序的操作栏中提供的典型操作,因此,用户都期望在操作栏中能够找到它。

            如,电子邮件或社交应用程序中的“刷新”操作。

        如果你想要把四个以上的菜单项调整为操作项,那么你就应该认真考虑一下他们相对的重要性级别,并且尝试不要超过四个以上的操作项设置(并且还有使用“ifRoom”属性值的设置,允许系统在遇到空间受限的比较小的屏幕的时候,能够把靠后的操作项放到悬浮菜单中)。即使在一些宽屏设备上,空间充足,你也不应该创建很多操作项,这样会扰乱UI的布局,而且更像一个桌面工具栏,因此要保持最小数量的操作项。

       另外,以下操作应该永远不要作为操作项来显示:设置、帮助、意见反馈、或类似的操作。要把它们始终保留在悬浮菜单中。

       注意:不是所有的设备都给检索提供了专有的硬件按钮,因此,如果是你应用程序中的一个重要功能,它应该始终作为一个操作项来显示(而且通常要把放到第一项的位置,尤其是操作窗口中提供这个操作的时候)。

使用分离式操作栏

        当你的应用程序正在Android4.0(API 级别 14)或以上的版本上运行,那么还有一种叫做“分隔操作栏”的额外模式对action bar有效。当你启用分隔操作栏模式时,在屏幕的底部会显示一个独立的横条,用于显示Activity在窄屏设备(如竖屏手机)上运行时的所有操作项。

       把action bar分隔成独立的操作项,确保在窄屏设备上有合适的空间来显示所有的操作项,同时把导航条和标题元素留在顶部。

       要启用分离式操作栏,只需简单的在<application>或<activity>元素中添加uiOptions=”splitActionBarWhenNarrow”属性设置就可以了。

       要注意,Android会基于当前屏幕的尺寸用各种方式来调整操作栏的外观。使用分离式操作栏只是你能够启用的允许操作栏针对不同屏幕尺寸来进一步优化用户体验的选项之一。你还可以允许操作栏把导航选项标签折叠到主操作栏中,如果你在操作栏中使用导航选项标签,那么一旦操作项在窄屏设备上被分离,这些导航选项标签就可能填充到主操作栏中,而不是被分离到堆叠起来的操作栏。尤其是如果你禁用了操作栏的图标和标题(用setDisplayShowHomeEnabled(false)和setDisplayShowTitleEnabled(false)方法),那么导航选项标签就会折叠到主操作栏中,如下图3中第二个设备的显示:

                       

图3. 左侧是带有导航选项标签的分离式操作栏,右侧是禁用了应用图标和标题的分离式操作栏。

注意:尽管android:uiOptions属性在Android4.0(API 级别 14)中才被添加,但是为了保持跟Android的低版本的兼容性,即使你的minSdkVersion属性值小于14,那么你的应用程序也可以安全的包含android:uiOptions属性。在旧版本上运行时,因为系统不能理解这个属性,所以只是简单的忽略了这个XML属性。在你的清单文件中包含这个属性的唯一条件是,你必须在支持API级别14或更高以上版本的平台上编译你的应用程序。为了保持兼容性,你不能在你的应用程序代码中使用由minSdkVersion属性声明的版本所不支持的API,只有XML属性才能被旧的平台版本安全的忽略。

导航栏使用应用图标

       默认情况下,应用程序图标显示在操作栏的左边。你能够把这个图标当做操作项来使用。应用程序应该在这个图标上响应以下两个操作之一:

       1.  返回应用程序的“主”Activity;

       2.  向应用程序上级页面导航。

       当用户触摸这个图标时,系统会调用Activity带有android.R.id.home ID的onOptionsItemSelected()方法。在这个响应中,你既可以启动主Activity,也可以返回你的应用程序结构化层次中用户上一步操作的界面。

       如果你要通过应用程序图标的响应来返回主Activity,那么就应该在Itent对象中包括FLAG_ACTIVITY_CLEAR_TOP标识。用这个标记,如果你要启动的Activity在当前任务中已经存在,那么,堆栈中这个Activity之上的所有的Activity都有被销毁,并且把这个Activity显示给用户。添加这个标识往往是重要的,因为返回主Activity相当与一个回退的动作,因此通常不应该再创建一个新的主Activity的实例,否则,最终可能会在当前任务中产生一个很长的拥有多个主Activity的堆栈。

例如,下例的onOptionsItemSelected()方法实现了返回应用程序的主Activity的操作:

  1. @Override  
    public boolean onOptionsItemSelected(MenuItem item) {  
        switch (item.getItemId()) {  
            case android.R.id.home:  
                // app icon in action bar clicked; go home  
                Intent intent = new Intent(this, HomeActivity.class);  
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  
                startActivity(intent);  
                return true;  
            default:  
                return super.onOptionsItemSelected(item);  
        }  
    }

在用户从另一个应用程序进入当前Activity的情况下,你可能还想要添加FLAG_ACTIVITY_NEW_TASK标识。这个标识确保在用户返回主页或上级页面时,新的Activity不会被添加到当前的任务中,而是在属于你自己的应用程序的任务中启动。例如,如果用户通过被另一个应用程序调用的Intent对象启动了你的应用程序中的一个Activity,那么选择操作栏图标来返回主页或上级页面时,FLAG_ACTIVITY_CLEAR_TOP标识会在属于你的应用程序的任务中启动这个Activity(不是当前任务)。系统既可以用这个新的Activity做根Activity来启动一个新的任务,也可以把存在后台的拥有这个Activity实例的一个既存任务带到前台来,并且目标Activity会接受onNewIntent()回调。因此,如果你的Activity要接收另一个应用程序的Intent对象,那么通常应该给这个Intent对象添加FLAG_ACTIVITY_NEW_TASK标识,如:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);  


注意:如果你要使用应用图标来返回主页,要注意从Android4.0(API 级别 14)开始,必须通过调用setHomeButtonEnabled(true)方法确保这个图标能够作为一个操作项(在以前的版本,默认情况下,这个图标就能够作为一个操作项)。

向应用程序上级页面导航

       作为传统的回退导航(把用户带回任务历史中的前一个窗口)的补充,你能够让action bar图标提供向上级页面导航的功能,它应用把用户带回到你的应用程序的上级页面。例如,当前页面时你的应用程序层次比较深的一个页面,触摸应用程序图标应该返回返回上一级页面(当前页面的父页面)。

          

         图4. Email应用程序的标准图标(左)和向上导航图标(右)。系统会自动添加向上指示。

       例如,图5演示了当用户从一个应用程序导航到一个属于不同应用程序的Activity时,“回退”按钮的行为。

            

       但是,如果在编辑完邮件之后,想要停留在Email应用程序中,那么向上导航就允许你把用户导航到Email应用程序中编辑邮件页面的上级页面,而不是返回到前一个Activity。图6演示了这种场景,在这个场景中,用户进入到Email应用程序后,不是按回退按钮,而是按操作栏图标来向上导航。

       

         图6. 从People应用进入Email应用后,向上导航的行为。

         要是应用程序图标能够向上导航,就要在你的ActionBar中调用SetDisplayHomeAsUpEnabledtrue(true)方法。

  1. protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
      
        setContentView(R.layout.main);  
        ActionBar actionBar = getActionBar();  
        actionBar.setDisplayHomeAsUpEnabled(true);  
        ...  
    }

   当用户触摸这个图标时,系统会调用带有android.R.id.home ID的onOptionsItemSelected()方法。

       请记住要在Intent对象中使用FLAG_ACTIVITY_CLEAR_TOP标识,以便你不会这个父Activity存在的情况下,再创建一个新的实例。例如,如果你不使用FLAG_ACTIVITY_CLEAR_TOP标识,那么向上导航后,再按回退按钮,实际上会把用户带到应用程序的下级页面,这是很奇怪的。

注意:如果有很多用户能够到达应用程序中当前Activity的路径,那么,向上图标应该沿着当前Activity的实际启动路径逐步的向会导航。

添加操作视窗

       操作视窗是作为操作项目按钮的替代品显示在操作栏中的一个可视构件。例如,如果你有一个用于搜索的可选菜单项,你可以用SearchView类来替代操作栏上的搜索按钮,如图7所示:

             


       图7. 折叠(上)和展开(下)的搜索视窗的操作栏

       要个菜单资源中的一个项目声明一个操作视窗,你既可以使用android:actionLayout属性也android:actionViewClass属性来分别指定一个布局资源或要使用的可视构件类。例如:

  1. <?xml version="1.0" encoding="utf-8"?>  
    <menu xmlns:android="http://schemas.android.com/apk/res/android">  
        <item android:id="@+id/menu_search"  
              android:title="@string/menu_search"  
              android:icon="@drawable/ic_menu_search"  
              android:showAsAction="ifRoom|collapseActionView"  
              android:actionViewClass="android.widget.SearchView" />  
    </menu>

android:showAsAction属性也可包含“collapseActionView”属性值,这个值是可选的,并且声明了这个操作视窗应该被折叠到一个按钮中,当用户选择这个按钮时,这个操作视窗展开。否则,这个操作视窗在默认的情况下是可见的,并且即便在用于不适用的时候,也要占据操作栏的有效空间。

       如果需要给操作视窗添加一些事件,那么就需要在onCreateOptionsMenu()回调执行期间做这件事。你能够通过调用带有菜单项ID的findItem()方法来获取菜单项,然后再调用getActionView()方操作视窗中的元素。例如,使用以下方法获取上例中的搜索视窗构件。

  1. @Override  
    public boolean onCreateOptionsMenu(Menu menu) {  
        getMenuInflater().inflate(R.menu.options, menu);  
        SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();  
        // Configure the search info and add any event listeners  
        ...  
        return super.onCreateOptionsMenu(menu);  
    }

处理可折叠的操作视窗


        操作视窗让你在不改变Activity或Fragment的情况下,就可以给用户提供快捷的访问和丰富的操作。但是,默认情况下让操作视窗可见可能不太合适。要保证操作栏的空间(尤其是在小屏幕设备上运行时),你能够把操作视窗折叠进一个操作项按钮中。当用户选择这个按钮时,操作视窗就在操作栏中显示。被折叠的时候,如果你定义了android:showAsAction=”ifRoom”属性,那么系统可能会把这个项目放到溢出菜单中,但是当用户选项了这个菜单项,它依然会显示在操作栏中。通过给android:showAsAction属性添加“collapseActionView”属性值,你能够让操作视窗可以折叠起来。

        因为在用户选择这个项目时,系统会展开这个操作视窗,所以你不必要在onOptionsItemSelected()回调方法中响应这个菜单项。在用户选择这个菜单项时,系统会依然调用onOptionsItemSelected()方法,但是除非你在方法中返回了true(指示你已经替代系统处理了这个事件),否则系统会始终展开这个操作视窗。

       当用户选择了操作栏中的“向上”图标或按下了回退按钮时,系统也会把操作视窗折叠起来。

如果需要,你能够在代码中通过在expandActionView()和collapseActionView()方法来展开或折叠操作视窗。

       注意:尽管把操作视窗折叠起来是可选的,但是,如果包含了SearchView对象,我们推荐你始终把这个视窗折叠起来,只有在需要的时候,由用户选择后才把它给展开。在提供了专用的“搜索”按钮的设备上也要小心了,如果用户按下了“搜索”按钮,那么也应该把这个搜索视窗给展开,简单的重写Activity的onKeyUp()回调方法,监听KEYCODE_SEARCH类型的按键事件,然后调用expandActionView()方法,就可以把操作视窗给展开。

如果你需要根据操作视窗的可见性来更新你的Activity,那么你可以定义一个OnActionExpandListener事件,并且用setOnActionExpandListener()方法来注册这个事件,然后就能够在操作视窗展开和折叠时接受这个回调方法了,如:

  1. @Override  
    public boolean onCreateOptionsMenu(Menu menu) {  
        getMenuInflater().inflate(R.menu.options, menu);  
        MenuItem menuItem = menu.findItem(R.id.actionItem);  
        ...  
      
        menuItem.setOnActionExpandListener(new OnActionExpandListener() {  
            @Override  
            public boolean onMenuItemActionCollapse(MenuItem item) {  
                // Do something when collapsed  
                return true;  // Return true to collapse action view  
            }  
      
            @Override  
            public boolean onMenuItemActionExpand(MenuItem item) {  
                // Do something when expanded  
                return true;  // Return true to expand action view  
            }  
        });  
    }

添加一个操作提供器

       与操作视窗类似,操作提供器(由ActionProvider类定义的)用一个定制的布局代替一个操作项目,它还需要对所有这些项目行为的控制。当你在操作栏中给一个菜单项声明一个操作项目时,它不仅要一个定制的布局来控制这个菜单项的外观,而且当它在显示在溢出菜单中时,还要处理它的默认事件。无论是在操作栏中还是在溢出菜单中,它都能够提供一个子菜单。

       例如,ActionProvider的扩展类ShareActionProvider,它通过在操作栏中显示一个有效的共享目标列表来方便共享操作。与使用传统的调用ACTION_SEND类型Intent对象的操作项不同,你能够声明一个ShareActionProvider对象来处理一个操作项。这种操作提供器会保留一个带有处理ACTION_SEND类型Intent对象的应用程序的下拉列表,即使这个菜单项显示在溢出菜单中。因此,当你使用像这样的操作提供器时,你不必处理有关这个菜单项的用户事件。

要给一个操作项声明一个操作提供器,就要在菜单资源中对应的<item>元素中定义android:actionProviderClass属性,提供器要使用完整的类名。例如:

  1. <?xml version="1.0" encoding="utf-8"?>  
    <menu xmlns:android="http://schemas.android.com/apk/res/android">  
        <item android:id="@+id/menu_share"  
              android:title="@string/share"  
              android:showAsAction="ifRoom"  
              android:actionProviderClass="android.widget.ShareActionProvider" />  
        ...  
    </menu>

在这个例子中,用ShareActionProvider类作为操作提供器,在这里,操作提供器需要菜单项的控制,并处理它们在操作栏中的外观和行为以及在溢出菜单中的行为。你必须依然给这个菜单项提供一个用于溢出菜单的文本标题。

尽管操作提供器提供了它在溢出菜单中显示时所能执行的默认操作,但是Activity(或Fragment)也能够通过处理来自onOptionsItemSelected()回调方法的点击事件来重写这个默认操作。如果你不在这个回调方法中处理点击事件,那么操作提供器会接收onPerformDefaultAction()回调来处理事件。但是,如果操作提供器提供了一个子菜单,那么Activity将不会接收onOptionsItemSelected()回调,因为子菜单的显示替代了选择时调用的默认菜单行为。

使用ShareActionProvider类

       如果你想要在操作栏中提供一个“共享”操作,以充分利用安装在设备上的其他应用程序(如,把一张图片共享给消息或社交应用程序使用),那么使用ShareActionProvider类是一个有效的方法,而不是添加一个调用ACTION_SEND类型Intent对象的操作项。当你给一个操作项使用ShareActionProvider类时,它会呈现一个带有能够处理ACTION_SEND类型Intent对象的应用程序的下拉列表(如图8所示)。

                


        图8. Gallery 应用截屏,用ShareActionProvider对象展开显示共享目标。

        创建子菜单的所有逻辑,包括共享目标的封装、点击事件的处理(包在溢出菜单中的项目显示)等,都在ShareActionProvider类中实现了---你需要编写的唯一的代码是给对应的菜单项声明操作提供器,并指定共享的Intent对象。

        默认情况,ShareActionProvider对象会基于用户的使用频率来保留共享目标的排列顺序。使用频率高的目标应用程序会显示在下来列表的上面,并且最常用的目标会作为默认共享目标直接显示在操作栏。默认情况下,排序信息被保存在由DEFAULT_SHARE_HISTORY_FILE_NAME指定名称的私有文件中。如果你只使用一种操作类型ShareActionProvider类或它的一个子类,那么你应该继续使用这个默认的历史文件,而不需要做任何事情。但是,如果你使用了不同类型的多个操作的ShareActionProvider类或它的一个子类,那么为了保持它们自己的历史,每种ShareActionProvider类都应该指定它们自己的历史文件。给每种ShareActionProvider类指定不同的历史文件,就要调用setShareHistoryFileName()方法,并且提供一个XML文件的名字(如,custom_share_history.xml)

        注意:尽管ShareActionProvider类是基于使用频率来排列共享目标的,但是这种行为是可扩展的,并且ShareActionProvider类的扩展能够基于历史文件执行不同的行为和排序。

        要添加ShareActionProvider对象,只需简单的给android.actionProviderClass属性设定android.widget.ShareActionProvider属性值就可以了。唯一要做的事情是定义你要用于共享的Intent对象,你必须先调用getActionProvider()方法来获取跟菜单项匹配的ShareActionProvider对象,然后调用setShareIntent()方法。

      如果对于共享的Intent对象的格式依赖与被选择的菜单项,或其他的在Activity生存周期内改变的变量,那么你应该把ShareActionProvider对象保存在一个成员属性里,并在需要的时候调用setShareIntent()方法来更新它。如:

  1. private ShareActionProvider mShareActionProvider;  
    ...  
      
    @Override  
    public boolean onCreateOptionsMenu(Menu menu) {  
        mShareActionProvider = (ShareActionProvider) menu.findItem(R.id.menu_share).getActionProvider();  
      
        // If you use more than one ShareActionProvider, each for a different action,  
        // use the following line to specify a unique history file for each one.  
        // mShareActionProvider.setShareHistoryFileName("custom_share_history.xml");  
      
        // Set the default share intent  
        mShareActionProvider.setShareIntent(getDefaultShareIntent());  
      
        return true;  
    }  
    // When you need to update the share intent somewhere else in the app, call  
    // mShareActionProvider.setShareIntent()

 上例中ShareActionProvider对象处理所有的跟这个菜单项有关的用户交互,并且不需要处理来自onOptionsItemSelected()回调方法的点击事件。

创建一个自定义的操作提供器

       当你想要创建一个有动态行为和在悬浮菜单中有默认图标的操作视窗时,继承ActionProvider类来定义这些行为是一个比好的的方案。创建自己的操作提供器,提供一个有组织的可重用的组件,而不是在Fragment或Activity的代码中处理各种操作项的变换和行为。

       要创建自己的操作提供器,只需简单的继承ActionProvider类,并且实现合适的回调方法。你应该实现以下重要的回调方法:

ActionProvider()

        这个构造器把应用程序的Context对象传递个操作提供器,你应该把它保存在一个成员变量中,以便其他的回调方法使用。

OnCreateActionView()

        这是你给菜单项定义操作视窗的地方。使用从构造器中接收的Context对象,获取一个LayoutInflater对象的实例,并且用XML资源来填充操作视窗,然后注册事件监听器。如:

  1. public View onCreateActionView() {  
        // Inflate the action view to be shown on the action bar.  
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);  
        View view = layoutInflater.inflate(R.layout.action_provider, null);  
        ImageButton button = (ImageButton) view.findViewById(R.id.button);  
        button.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                // Do something...  
            }  
        });  
        return view;  
    }

onPerformDefaultAction()
         
在选中悬浮菜单中的菜单时,系统会调用这个方法,并且操作提供器应该这对这个选中的菜单项执行默认的操作。

       但是,如果你的操作提供器提供了一个子菜单,即使是悬浮菜单中一个菜单项的子菜单,那么也要通过onPrepareSubMenu()回调方法来显示子菜单。这样onPerformDefaultAction()在子菜单显示时就不会被调用。

       注意:实现了onOptionsItemSelected()回调方法的Activity或Frament对象能够通过处理item-selected事件(并且返回true)来覆盖操作提供器的默认行为,这种情况下,系统不会调用onPerformDefaultAction()回调方法。

添加导航选项标签

       当你想要在一个Activity中提供导航选择标签时,使用操作栏的选项标签是一个非常好的选择(而不是使用TabWidget类),因为系统会调整操作栏选项标签来适应不同尺寸的屏幕的需要---在屏幕足够宽的时候,导航选项标签会被放到主操作栏中;当屏幕太窄的时候,选项标签会被放到一个分离的横条中,如图9和图10所示。

      

图9. Honeycomb Gallery应用程序中的操作栏选项标签的截图

      

          图10. 在窄屏设备上被堆放在操作栏中的选项标签的截屏 

       要使用选项标签在Fragmengt之间切换,你必须在每次选择一个选项标签时执行一个Fragment事务。如果你不熟悉如何使用FragmentTransaction对象来改变Fragment,请阅读Fragment开发指南。

       首先,你的布局必须包含一个用于放置跟每个Fragment对象关联的选项标签的ViewGroup对象。并且要确保这个ViewGroup对象有一个资源ID,以便你能够在选项标签的切换代码中能够引用它。另外,如果选项标签的内容填充在Activity的布局中(不包括操作栏),那么Activity不需要任何布局(你甚至不需要调用setContentView()方法)。相反,你能够把每个Fragment对象放到默认的根ViewGroup对象中,你能够用android.R.id.content ID来引用这个ViewGroup对象(在Fragment执行事务期间,你能够在下面的示例代码中看到如何使用这个ID的。

        决定了Fragment对象在布局中的显示位置后,添加选项标签的基本过程如下:

          1.  实现ActionBar.TabListener接口。这个接口中回调方法会响应选项标签上的用户事件,以便你能够切换Fragment对象;

           2.  对于每个要添加的选项标签,都要实例化一个ActionBar.Tab对象,并且调用setTabListener()方法设置ActionBar.Tab对象的事件监听器。还可以用setText()或setIcon()方法来设置选项标签的标题或图标。

         3.  通过调用addTab()方法,把每个选项标签添加到操作栏。

        在查看ActionBar.TabListener接口时,注意到回调方法只提供了被选择的ActionBar.Tab对象和执行Fragment对象事务的FragmentTransaction对象---没有说明任何有关Fragment切换的事。因此。你必须定义自己的每个ActionBar.Tab之间的关联,以及ActionBar.Tab所代表的适合的Fragment对象(为了执行合适的Fragment事务)。依赖你的设计,会有几种不同的方法来定义这种关联。在下面的例子中,ActionBar.TabListener接口的实现提供了一个构造器,这样每个新的选项标签都会使用它自己的监听器实例。每个监听器实例都定义了几个在对应Fragment对象上执行事务时必须的几个成员变量。

       例如,以下示例是ActionBar.TabListener接口的一种实现,在这个实现中,每个选项标签都使用了它自己的监听器实例:

public static class TabListener<T extends Fragment> implements ActionBar.TabListener {  
    private Fragment mFragment;  
    private final Activity mActivity;  
    private final String mTag;  
    private final Class<T> mClass;  
  
    /** Constructor used each time a new tab is created. 
      * @param activity  The host Activity, used to instantiate the fragment 
      * @param tag  The identifier tag for the fragment 
      * @param clz  The fragment‘s Class, used to instantiate the fragment 
      */  
    public TabListener(Activity activity, String tag, Class<T> clz) {  
        mActivity = activity;  
        mTag = tag;  
        mClass = clz;  
    }  
  
    /* The following are each of the ActionBar.TabListener callbacks */  
  
    public void onTabSelected(Tab tab, FragmentTransaction ft) {  
        // Check if the fragment is already initialized  
        if (mFragment == null) {  
            // If not, instantiate and add it to the activity  
            mFragment = Fragment.instantiate(mActivity, mClass.getName());  
            ft.add(android.R.id.content, mFragment, mTag);  
        } else {  
            // If it exists, simply attach it in order to show it  
            ft.attach(mFragment);  
        }  
    }  
  
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {  
        if (mFragment != null) {  
            // Detach the fragment, because another one is being attached  
            ft.detach(mFragment);  
        }  
    }  
  
    public void onTabReselected(Tab tab, FragmentTransaction ft) {  
        // User selected the already selected tab. Usually do nothing.  
    }  
}

警告:针对每个回调中的Fragment事务,你都不必调用commit()方法---系统会调用这个方法,并且如果你自己调用了这个方法,有可能会抛出一个异常。你也不能把这些Fragment事务添加到回退堆栈中。

在这个例子中,当对应的选项标签被选择时,监听器只是简单的把一个Fragment对象附加(attach()方法)到Activity布局上---或者,如果没有实例化,就会创建这个Fragment对象,并且把它添加(add()方法)到布局中(android.R.id.content ViewGroup的一个子类),当这个选项标签解除选择时,对应的Fragment对象也会被解除与布局的依附关系。

       ActionBar.TabListener的实现做了大量的工作,剩下的事情就是创建每个ActionBar.Tab对象并把它添加到ActionBar对象中,另外,你必须调用setNavigationMode(NAVIGATION_MODE_TABS)方法来让选项标签可见。如果选项标签的标题实际指示了当前的View对象,你也可以通过调用setDisplayShowTitleEnabled(false)方法来禁用Activity的标题。

       例如,下面的代码使用上面定义的监听器在操作栏中添加了两个选项标签。

    1. @Override  
      protected void onCreate(Bundle savedInstanceState) {  
          super.onCreate(savedInstanceState);  
          // Notice that setContentView() is not used, because we use the root  
          // android.R.id.content as the container for each fragment  
        
          // setup action bar for tabs  
          ActionBar actionBar = getActionBar();  
          actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);  
          actionBar.setDisplayShowTitleEnabled(false);  
        
          Tab tab = actionBar.newTab()  
                  .setText(R.string.artist)  
                  .setTabListener(new TabListener<ArtistFragment>(  
                          this, "artist", ArtistFragment.class));  
          actionBar.addTab(tab);  
        
          tab = actionBar.newTab()  
              .setText(R.string.album)  
              .setTabListener(new TabListener<AlbumFragment>(  
                      this, "album", AlbumFragment.class));  
          actionBar.addTab(tab);  
      }

    注意:以上有关ActionBar.TabListener的实现,只是几种可能的技术之一。在API Demos应用中你能够看到更多的这种样式。

        如果Activity终止了,那么你应该保存当前选择的选项标签的状态,以便当用户再次返回时,你能够打开合适的选项标签。在保存状态的时刻,你能够用getSelectedNavigationIndex()方法查询当前的被选择的选项标签。这个方法返回被选择的选项标签的索引位置。

       警告:保存每个Fragment所必须的状态是至关重要的,因为当用户用选项标签在Fragment对象间切换时,它会查看Fragment在离开时样子。

       注意:在某些情况下,Android系统会把操作栏选项标签作为一个下拉列表来显示,以便确保操作栏的最优化显示。

添加下拉式导航

       作为Activity内部的另一种导航(或过滤)模式,操作栏提供了内置的下拉列表。下拉列表能够提供Activity中内容的不同排序模式。

       启用下拉式导航的基本过程如下:

         1.  创建一个给下拉提供可选项目的列表,以及描画列表项目时所使用的布局;

         2.  实现ActionBar.OnNavigationListener回调,在这个回调中定义当用户选择列表中一个项目时所发生的行为;

         3.  用setNavigationMode()方法该操作栏启用导航模式,如:

  1. ActionBar actionBar = getActionBar();  
    actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);

 4.  用setListNavigationCallbacks()方法给下拉列表设置回调方法,如:

  1. actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationCallback);

这个方法需要SpinnerAdapter和ActionBar.OnNavigationListener对象。下面是 SpinnerAdapter and ActionBar.OnNavigationListener 的例子。

 Example SpinnerAdapter and OnNavigationListener

设置操作栏的样式


       如果你对应用程序中的可视构件进行了定制化的设计,那么你可能也会要对操作栏做一些重新设计,以便跟应用程序的设计匹配。要这样做的话,需要使用Android的样式与主题框架中的一些特殊的样式属性来重新设置操作栏的样式。

       注意:改变外观的背景图片依赖与当前按钮的状态(选择、按下、解除选择),因此你使用的可描画的资源必须是一个可描画的状态列表。

       警告:对于你提供的所有可描画的背景,要确保使用NinePatch类型可描画资源,以便允许图片的拉伸。NinePatch类型的图片应该比40像素高30像素宽的图片要小。


普通的外观

android:windowActionBarOverlay

      这个属性声明了操作栏是否应该覆盖Activity布局,而不是相对Activity的布局位置的偏移。这个属性的默认值是false。

      通常,在屏幕上,操作栏需要它自己的空间,并且把剩下的空间用来填充Activity的布局。当操作栏四覆盖模式时,Activity会使用所有的有效空间,系统会在Activity的上面描画操作栏。如果你想要在操作栏隐藏和显示时,布局中的内容保持固定的尺寸好位置,那么这种覆盖模式是有用的。你也可能只是为了显示效果来使用它,因为你可以给操作栏设置半透明的背景,以便用户依然能够看到操作栏背后的Activity布局。

       注意:默认情况下,Holo主题会用半透明背景来描画操作栏。但是,你能够用自己的样式来修改它,并且默认的情况下,DeviceDefault主题在不同的设备上可能使用不透明的背景。

       覆盖模式被启用时,Activity布局不会感知到操作栏覆盖在它的上面,因此,在操作栏覆盖的区域,最好不要放置一些重要的信息或UI组件。如果适合,你能够引用平台的actionBarSize值来决定操作栏的高度,例如,在XML布局文件中引用这个值。

  1. <SomeView  
        ...  
        android:layout_marginTop="?android:attr/actionBarSize" />

你还能够用getHeight()方法在运行时获取操作栏的高度。如果在Activity生存周期的早期调用这个方法,那么在调用时所反映的操作栏的高度可能不包括被堆放的操作栏(因为导航选项标签)。要看如何在运行时判断操作栏总的高度(包括被堆放的操作栏),请看Honeycomb Gallery示例应用中的TitlesFragment类。

操作项元素

  • android:actionButtonStyle

  • 给操作项按钮定义样式资源。

  • android:actionBarItemBackground

  •  给每个操作项的背景定义可描画资源(被添加在API 级别 14中)。

  • android:itemBackground

  •  给每个悬浮菜单项的背景定义可描画资源。

  • android:actionBarDivider

  • 给操作项之间的分隔线定义可描画资源(被添加在API 级别 14中)

  • android:actionMenuTextColor

  • 给显示在操作项中文本定义颜色。

  • android:actionMenuTextAppearance

  •  给显示在操作项中文本定义样式资源。

  • android:actionBarWidgetTheme

  • 给作为操作视窗被填充到操作栏中的可视构件定义主题资源(被添加在API级别14中)。

导航选项标签


android:actionBarTabStyle 给操作栏中的选项标签定义样式资源。android:actionBarTabBarStyle给显示在导航选项标签下方的细条定义样式资源。android:actionBarTabTextStyle给导航选项标签中的文本定义样式资源。下拉列表


android:actionDropDownStyle 给下拉导航列表定义样式(如背景和文本样式)。如,下例XML文件中给操作栏定义了一些定制的样式:
  1. <?xml version="1.0" encoding="utf-8"?>  
    <resources>  
        <!-- the theme applied to the application or activity -->  
        <style name="CustomActivityTheme" parent="@android:style/Theme.Holo">  
            <item name="android:actionBarTabTextStyle">@style/CustomTabTextStyle</item>  
            <item name="android:actionBarDivider">@drawable/ab_divider</item>  
            <item name="android:actionBarItemBackground">@drawable/ab_item_background</item>  
        </style>  
      
        <!-- style for the action bar tab text -->  
        <style name="CustomTabTextStyle" parent="@android:style/TextAppearance.Holo">  
            <item name="android:textColor">#2456c2</item>  
        </style>  
    </resources>

 注意:一定要在<style>标签中声明一个父主题,这样定制的主题可以继承所有没有明确声明的样式。在修改操作栏样式时,使用父主题是至关重要的,它会让你能够简单的覆写你想要改变的操作栏样式,而不影响你不想修改的样式(如文本的外观或操作项的边缘)。

      你能够在清单文件中把定制的主题应用到整个应用程序或一个单独的Activity对象,如:

  1. <
    application
     
    android:theme
    =
    "@style/CustomActivityTheme"
     ... 
    />

高级样式

      如果需要比上述属性更高级的样式,可以在Activity的主题中包含android:actionBarStyle和android:actionBarSplitStyle属性。这两个属性的每一个都指定了另一种能够给操作栏定义各种属性的样式,包括带有android:background、android:backgroundSplit、android:backgroundStacked属性的不同背景。如果要覆盖这些操作栏样式,就要确保定义一个像Widget.Holo.ActionBar这样的父操作栏样式。

例如,如果要改变操作栏背景,你可以使用下列样式:

  1. <?xml version="1.0" encoding="utf-8"?>  
    <resources>  
        <!-- the theme applied to the application or activity -->  
        <style name="CustomActivityTheme" parent="@android:style/Theme.Holo">  
            <item name="android:actionBarStyle">@style/MyActionBar</item>  
            <!-- other activity and action bar styles here -->  
        </style>  
      
        <!-- style for the action bar backgrounds -->  
        <style name="MyActionBar" parent="@android:style/Widget.Holo.ActionBar">  
            <item name="android:background">@drawable/ab_background</item>  
            <item name="android:backgroundStacked">@drawable/ab_background</item>  
            <item name="android:backgroundSplit">@drawable/ab_split_background</item>  
        </style>  
    </resources>


Android UI开发第二十四篇——Action Bar