Action Bar(操做栏)

操做栏php

操做栏是一种能标识用户位置、提供用户操做和导航模式的窗口特征。使用操做栏能让你的系统优雅的适配不一样的屏幕配置,给你的用户在整个应用中提供熟悉的界面。html


图1.   一个包含了 [1] 应用图标,[2] 两个功能项,[3] 更多操做的操做栏。java

操做栏提供了一些关键功能:android

  • 分配一个专用空间为你的应用提供一致性并标识用户在应用中的位置。api

  • 用可预测的方式突出重要的操做而且容易访问(好比搜索框)。数组

  • 在应用中提供一致的导航和视图切换效果(使用选项卡和下拉列表)。ruby

更多关于操做栏的交互模式和设计指南,请参考  Action Bar  设计指南。app

虽然  ActionBar  API直到Android 3.0(API等级11)才被引入,可是为了兼容Android 2.1(API等级7)和以上的系统,提供了可用的  Support Library 。框架

这篇指南主要讲述如何使用支持库中的操做栏, 可是若是你的应用只支持Android 3.0或更高的版本,你应该使用框架提供的  ActionBar 。他们大部分的API都是同样的,可是属于不一样的包命名空间,还有些例外,好比下面的章节中提到的方法名和签名。ide

注意:  请肯定你从合适的包中引入了正确的  ActionBar  类 (和相关的API):

  • 若是支持的API等级小于11: 
    import android.support.v7.app.ActionBar

  • 若是仅支持API等级11或更高的: 
    import android.app.ActionBar

注解: 若是你想知道关于上下文操做栏显示上下文操做项的信息,请参考 Menu  指南。

添加操做栏


就像上面提到的,这篇指南主要讲述如何使用支持库中  ActionBar  的API。因此在你添加操做栏以前,你必须按照  Support Library Setup  中说明的为你的项目设置  appcompat v7  支持库。

一旦你的项目设置好支持库,下面介绍如何添加操做栏:

  1. 经过继承  ActionBarActivity  来建立你的activity。

  2. 对你的activity使用或继承  Theme.AppCompat  主题中的一种。例如:

    <activity android:theme="@style/Theme.AppCompat.Light" ... >

如今当你的应用运行在Android 2.1(API等级7)或以上的系统时,你的activity就包含了操做栏。

API等级11或更高时

当  targetSdkVersion  或  minSdkVersion 属性被设置为  "11" 或更高时,activity默认状况下使用  Theme.Holo  主题,任何使用这种主题(或他们的子类)的activity都拥有操做栏。

移除操做栏

你能够在运行时调用  hide() 方法来隐藏操做栏。例如:

ActionBar actionBar = getSupportActionBar();     actionBar.hide();

API等级11或更高时

调用  getActionBar()  方法来获取  ActionBar 。

当操做栏隐藏时,系统会调整你的布局去填充可用的屏幕空间。你能够调用 show()  方法从新显示操做栏。

请注意当隐藏或移除操做栏时会致使activity为了填充操做栏占用的空间去从新排版布局。若是你的activity常常会隐藏或显示操做栏,你可能但愿去开启 overlay  模式。Overlay模式模糊化activity布局的顶部位置并在activity布局前面绘制操做栏。这样,你的布局无论操做栏隐藏或重现都是固定的。为你的activity建立一个自定义的主题而且把  windowActionBarOverlay  属性置为  true  就能启用overlay模式了。更多详情,请参考下面关于  Styling the Action Bar  的章节。

使用商标而不是图标

默认状况下,系统在操做栏里使用的是在  <application>  or  <activity> 元素里  icon  属性指定的图标。然而,若是你同时指定了  logo  属性,那么操做栏将会使用商标而不是图标。

商标一般应该比图标宽,除此以外不该该包括没必要要的文字。你一般应该在使用用户能够识别的传统方式展现你的品牌时才使用商标。一个很好的事例是YouTube应用的商标,商标表明了用户指望的品牌,然而应用的图标被修改为方形的以符合启动图标的要求。

添加功能项


 图2.  包含了三个功能项和更多功能按钮的操做栏。

操做栏为用户提供了应用当前上下文相关的最重要功能项的访问方式。那些在操做栏里直接显示的带或不带文字的图标被叫作功能按钮。没法安装在操做栏或不重要的功能被隐藏在更多操做选项中。用户能够按下右边的更多操做按钮(若是设备有菜单按钮的话,也能够按下菜单按钮)显示其余的功能列表。

当你的activity启动时,系统经过调用activity的  onCreateOptionsMenu()  方法填入功能项。使用这个方法去扩充一个定义了全部功能项的  menu resource 。例如,下面是一个定义了一些菜单项的菜单资源:

res/menu/main_activity_actions.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/action_search"      android:icon="@drawable/ic_action_search"      android:title="@string/action_search"/>
    <item android:id="@+id/action_compose"      android:icon="@drawable/ic_action_compose"      android:title="@string/action_compose" /></menu>

而后在activity的  onCreateOptionsMenu()  方法里把菜单资源扩充到给定的 Menu  中以便把每个功能项添加到操做栏中:

@Overridepublic boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu items for use in the action bar
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.main_activity_actions, menu);
    return super.onCreateOptionsMenu(menu);}

要把一个菜单项做为一个功能按钮直接显示在操做栏上,请在  <item>  标签里包含 showAsAction="ifRoom" 。 例如:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:yourapp="http://schemas.android.com/apk/res-auto" >
    <item android:id="@+id/action_search"
          android:icon="@drawable/ic_action_search"
          android:title="@string/action_search"
          yourapp:showAsAction="ifRoom"  />
    ...</menu>

若是在操做栏中没有足够的空间显示项目的话,它将在更多操做中显示。

使用支持库中的XML属性

注意上面的 showAsAction 属性使用的定义在 <menu> 标签中的自定义命名空间。由于这些属性在旧设备的安卓框架中并不存在,那么在使用支持库里定义的任何XML属性时这么作仍是颇有必要的。因此你必须为全部支持库定义的属性使用你本身的命名空间做为前缀。

若是你的菜单项同时拥有标题和图标属性,那么默认状况下功能项只显示图标。若是你想显示文本标题,把 "withText" 添加到 showAsAction 属性里便可。例如:

<item yourapp:showAsAction="ifRoom|withText" ... />

注解:  对于操做栏来讲 "withText" 意味着文本标题应该显示出来,操做栏会尽量的显示出标题,可是当图标可用而且操做栏没有剩余空间时不会显示标题。

尽管你不想功能项带着标题显示出来,你仍是应该老是为每个功能项定义标题,缘由以下:

  • 若是在操做栏里没法为功能项提供足够的空间,那么菜单项将会放在更多操做里而且只会显示标题。

  • 视力障碍读者能够读出菜单项的标题。

  • 若是功能项仅显示图标的话,那么当用户长按功能项时会显示描述这个功能项的短语。

虽然图标是可选的,可是咱们建议使用。关于图标设计建议,请参考 Iconography  设计指南。你也能够从这个  Downloads  页面下载一些标准操做栏图标合集(好比搜索栏和放弃键)。

你也可使用 "always" 来声明一个功能项始终做为功能按钮来展现。然而,经过这种方式你并不能使一个功能项强制出如今操做栏里。 在拥有狭窄屏幕的设备上这么作会产生一些布局问题。最好使用 "ifRoom" 去请求系统把一个功能项显示在操做栏中,这样能够容许系统在空间不足的状况下把功能项移动到更多操做选项里。然而,若是功能项包含一个不能收缩的  action view  而且这个功能项为了提供关键功能的入口必须老是可见的话,使用 "always" 就很必要了。

处理功能项的点击

当用户按下一个功能项时,系统将会调用activity里的 onOptionsItemSelected()  方法。系统会传递  MenuItem  对象给这个方法,这样你能够经过调用它的  getItemId()  方法来区分功能项。它会返回 <item> 标签的 id 属性指定了的惟一的ID,因此你能执行合适的操做。例如:

@Overridepublic boolean onOptionsItemSelected(MenuItem item) {  // 处理操做栏功能项的点击  switch (item.getItemId()) {    case R.id.action_search:      openSearch();      return true;    case R.id.action_compose:      composeMessage();      return true;    default:      return super.onOptionsItemSelected(item);  }}

注解: 若是你在fragment中经过  Fragment  类的  onCreateOptionsMenu()  方法扩展菜单项,系统会在用户选中其中菜单项时调用 onOptionsItemSelected()  回调。然而,activity拥有优先处理事件的机会, 因此系统会在fragment调用  onOptionsItemSelected()  前,优先调用activity里相同名字的回调。为了确保activity里的任何frament都一样拥有处理回调的机会,当你不想处理这个项目时请老是把调用传递给它的父类做为默认的处理方式而不是返回false  。

图3.  实物模型展现了带选项卡的操做栏(左),分离操做栏(中)和不带应用图标和标题的操做栏(右)。

使用分离操做栏

当activity运行在狭窄的屏幕上时(好比垂直方向的手机),分离操做栏能够在屏幕底部提供一个能显示全部功能项的分离栏。

这样分离操做栏能够确保在狭窄的屏幕上用合理的空间去显示全部的功能项,顶部剩余的空间能够显示导航和标题元素。

当使用支持库时想开启分离操做栏,你必须作下面两件事:

  1. 在  <application>  元素或每一个  <activity>  元素里添加uiOptions="splitActionBarWhenNarrow" 。这个属性仅在API等级14或更高的系统上支持(旧版本会忽略这个属性)。

  2. 为了支持旧版本,在每一个  <activity>  元素里添加  <meta-data>  子元素,而且声明 "android.support.UI_OPTIONS" 的值为splitActionBarWhenNarrow 。

例如:

<manifest ...>
    <activity uiOptions="splitActionBarWhenNarrow" ... >
        <meta-data android:name="android.support.UI_OPTIONS"
                   android:value="splitActionBarWhenNarrow" />
    </activity></manifest>

若是你移除了操做栏的图标和标题(如图3右侧所示),使用分离操做栏能够容许  navigation tabs  收缩在主操做栏中。要达到这种效果,请分别调用 setDisplayShowHomeEnabled(false)  和 setDisplayShowTitleEnabled(false)  方法使操做栏的图标和标题不可用。

使用应用图标向上导航


 图4.  Gmail中的向上按钮

使用应用图标做为向上导航按钮可使用户基于界面间的层次关系操做你的应用。举个例子,若是界面A显示一个项目列表而且选择一个项目时会通向界面B,那么界面B应该包含用来返回界面A的向上的按钮。

注解: 向上导航与系统回退键提供的后退导航功能不一样。回退键被用来在用户最近使用过的历史界面里以倒序的方式进行导航,这是基于界面间的临时关系而不是应用的层级结构(向上导航的基础正是应用的层级结构)。

调用  setDisplayHomeAsUpEnabled()  能够将应用的图标做为向上按钮使用。例如:

@Overrideprotected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_details);  ActionBar actionBar = getSupportActionBar();  actionBar.setDisplayHomeAsUpEnabled(true);  ...}

如今操做栏里的图标旁会出现一个向上的符号(如图4所示)。然而,默认它不会有任何效果。要指定当用户按下向上按钮时应该打开哪一个activity的话,你有下面两种选择:

  • 在清单文件中指定它的父级activity。

    当 父级activity老是同样时 这种是最好的选择。经过在清单文件中声明父级activity,操做栏能够在用户按下向上按钮时自动执行正确的操做。

    从Android 4.1(API等级16)开始,你能够在  <activity>  元素中使用 parentActivityName  属性来指明父级activity。

    要使用支持库去支持旧设备,一样要包含一个用来指定指定父级activity的 <meta-data>  元素,而且指定的父级activity用来做为android.support.PARENT_ACTIVITY 的值。例如:

    <application ... >  ...  <!-- 主activity(没有父级activity) -->  <activity    android:name="com.example.myfirstapp.MainActivity" ...>    ...  </activity>  <!-- 主activity的子级activity -->  <activity    android:name="com.example.myfirstapp.DisplayMessageActivity"    android:label="@string/title_activity_display_message"    android:parentActivityName="com.example.myfirstapp.MainActivity" >    <!-- 父级activity元数据用来支持API等级7或以上的版本 -->    <meta-data      android:name="android.support.PARENT_ACTIVITY"      android:value="com.example.myfirstapp.MainActivity" />  </activity></application>

    一旦像这样在清单文件中指定了父级activity而且经过 setDisplayHomeAsUpEnabled()  启用了向上按钮后,你的工做就完成了,操做栏会正确的向上导航。

  • 或者,重写activity中的  getSupportParentActivityIntent()  和 onCreateSupportNavigateUpTaskStack() 。

  • 取决于用户如何到达当前界面的, 父级activity也可能不一样 ,那么这么作就很合适了。也就是说,若是用户有许多能够到达当前界面的路径,那么向上按钮应该按照用户实际到达的路劲回退导航。

    当用户按下向上按钮时系统会调用 getSupportParentActivityIntent()  去导航你的应用(在你应用本身的任务栈内)。取决于用户如何到达当前位置的,向上导航上应该打开的activity也会不一样,因此你应该重写这个方法以返回能够打开合适父级activity的  Intent

    当用户按下向上按钮而且你的activity也运行在不属于你应用的任务栈中时,系统会为你的activity调用  onCreateSupportNavigateUpTaskStack() 。所以,当用户向上导航时你必须使用  TaskStackBuilder  传递到这个方法去创建那个应该合并的合适的回退栈。

    虽然你重写了  getSupportParentActivityIntent()  来具体指定用户在应用中的具体导航,可是你不想实现 onCreateSupportNavigateUpTaskStack() ,那么你能够像上面所示在清单文件中声明“默认”父级activity达到一样的效果。而后 onCreateSupportNavigateUpTaskStack()  的默认实现会基于清单中声明的父级activity去合并回退栈。

注解: 若是你是使用大量的fragment而不是activity去构建你应用层级结构,那么上面的选项都不会生效。要在fragment里向上导航,你应该改成重写 onSupportNavigateUp() ,一般经过调用  popBackStack()  方法把当前fragment从回退栈里出栈去来执行合适的fragment事务。

更多关于实现向上导航的信息,请阅读  Providing Up Navigation 。

添加操做视图


图5.  带可收缩  SearchView  的操做栏。

操做视图是一种显示在操做栏中并能够做为操做按钮替代品的小部件。操做视图能够在不替换操做栏、不改变activity和fragment的的状况下提供快速访问富操做入口。例如,若是你有搜索的操做,你能够如图5所示在操做栏里把一个  SearchView  控件嵌入到操做视图中。

可使用 actionLayout 或 actionViewClass 属性去分别指定使用的布局资源或控件类。例如,下面介绍如何添加  SearchView  控件:

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

注意上面的 showAsAction 属性里也包含了 "collapseActionView" 值。这是用来声明操做视图应该被收缩到一个按钮中的可选操做。(在后面关于 Handling collapsible action views  的章节里会进一步解释这种用法。)

若是你须要配置操做视图(例如添加事件监听器),你能够在 onCreateOptionsMenu()  回调里处理。你能够传递相应的  MenuItem  给静态方法  MenuItemCompat.getActionView()  去获取操做视图的对象。例如,能够这样获取上面例子的搜索控件:

@Overridepublic boolean onCreateOptionsMenu(Menu menu) {  getMenuInflater().inflate(R.menu.main_activity_actions, menu);  MenuItem searchItem = menu.findItem(R.id.action_search);  SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);  // 配置搜索框和添加事件监听器  ...  return super.onCreateOptionsMenu(menu);}

API等级11或更高

调用对应  MenuItem  的  getActionView()  方法获取操做视图:

menu.findItem(R.id.action_search).getActionView()

更多关于使用搜索控件的信息,请参考  Creating a Search Interface 。

处理可收缩的操做视图

为了维护操做栏的空间,你能够把你的操做视图收缩到一个操做按钮中。当收缩时,系统可能会把操做按钮放到更多操做内,可是当用户选择操做时操做视图仍是会在操做栏中显示。如上面XML中所示,你能够经过在 showAsAction 属性里添加"collapseActionView" 来收缩你的操做视图。

由于系统在用户选择操做时会扩展操做视图,因此你不须要在 onOptionsItemSelected()  回调中响应这个操做。系统仍是会调用 onOptionsItemSelected() ,可是若是你返回了true(代表你已经处理了事件)那么操做视图就不会扩展了。

当用户按下向上按钮或回退按钮时系统一样会收缩你的操做视图。

若是你须要基于操做视图的可见性来更新activity,能够经过定义一个 OnActionExpandListener  并把它传递给  setOnActionExpandListener()  方法来在操做视图扩展和收缩时接收到回调。例如:

@Overridepublic boolean onCreateOptionsMenu(Menu menu) {  getMenuInflater().inflate(R.menu.options, menu);  MenuItem menuItem = menu.findItem(R.id.actionItem);  ...  // 当使用支持库时,setOnActionExpandListener()方法是静态的而且接受MenuItem对象做为参数  MenuItemCompat.setOnActionExpandListener(menuItem, new OnActionExpandListener() {    @Override    public boolean onMenuItemActionCollapse(MenuItem item) {      // 当收缩时执行      return true;  // 返回ture去收缩操做视图    }    @Override    public boolean onMenuItemActionExpand(MenuItem item) {      //当扩展时执行      return true;  // 返回true去扩展操做视图    }  });}

添加操做提供者


图6.   操做栏被  ShareActionProvider  扩充来展现分享目标。

与  action view  类似,操做提供者使用自定义的布局去替换操做按钮。然而,不像操做视图,操做提供者控制全部的操做行为而且能够在按下时显示子菜单。

要想声明操做提供者,在菜单 <item> 标签里使用  ActionProvider  类的彻底限定名填充到 actionProviderClass 属性里。

你能够继承  ActionProvider  类来构建你本身的操做提供者,不过Android已经提供了一些预构建的操做提供者,例如  ShareActionProvider ,经过直接在操做栏里(如图6所示)展现能够分享的应用列表来提供分享操做。

由于每一个  ActionProvider  类已经定义过它本身的操做行为了,因此你没必要在  onOptionsItemSelected()  内监听这些操做。若是须要的话,好比你须要同时执行另外的操做,你仍是能够在  onOptionsItemSelected()  中监听这些点击事件。可是为了使操做提供者依旧能接收到  onPerformDefaultAction()  回调来执行它的预约操做请确保返回值的是false。

然而,若是操做提供者提供的是操做子菜单,那么你的activity在用户打开这个列表或选择子菜单中的一项时不会接收到  onOptionsItemSelected()  的调用。

使用ShareActionProvider

使用  ShareActionProvider  类去定义 <item> 里的一个名为actionProviderClass 的标签,这样你就能用  ShareActionProvider  去添加一个“分享”操做了。例如:

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

如今操做提供者控制着操做项而且处理它的外观与行为。除此以外你必须为每个将要在更多操做中用到的操做项提供一个标题。

剩下惟一一件要作的事是定义你想要用来分享的  Intent 。编辑 onCreateOptionsMenu()  方法,传递一个持有操做提供者的  MenuItem  对象给  MenuItemCompat.getActionProvider() 方法,而后传递一个带有合适内容的在返回的  ACTION_SEND  intent给  ShareActionProvider  上的 setShareIntent()  方法。

你应该在  onCreateOptionsMenu()  内调用一次  setShareIntent()  去初始化分享操做。可是由于用户上下文可能会变,因此一旦分享上下文改变时,你必须从新调用  setShareIntent()  去更新intent。

例如:

private ShareActionProvider mShareActionProvider;@Overridepublic boolean onCreateOptionsMenu(Menu menu) {  getMenuInflater().inflate(R.menu.main_activity_actions, menu);  // 设置ShareActionProvider的默认分享intent  MenuItem shareItem = menu.findItem(R.id.action_share);  mShareActionProvider = (ShareActionProvider)      MenuItemCompat.getActionProvider(shareItem);  mShareActionProvider.setShareIntent(getDefaultIntent());  return super.onCreateOptionsMenu(menu);}/** 定义一个默认的(虚拟的)分享intent去初始化操做提供者。
  * 然而,一旦在intent里实际使用的上下文变化了,你必须再次调用mShareActionProvider.setShareIntent()去更新分享intent  */private Intent getDefaultIntent() {  Intent intent = new Intent(Intent.ACTION_SEND);  intent.setType("image/*");  return intent;}

如今  ShareActionProvider  处理全部与操做项的用户交互而且你没必要要在 onOptionsItemSelected()  回调方法里处理点击事件。

默认状况下, ShareActionProvider  基于用户选择的频率对每一个分享目标项进行排名。越经常使用的分享目标项出如今下拉列表越靠近顶端的位置,而且最经常使用的直接做为默认分享目标出如今操做栏上。默认状况下,排名信息被保存在 DEFAULT_SHARE_HISTORY_FILE_NAME  指定文件名的私有文件中。若是你仅仅为一种操做类型使用  ShareActionProvider  或它的扩展,那么你应该继续使用默认的历史文件而不用作任何事。然而,若是你为语义不一样的多样的操做类型使用  ShareActionProvider  或它额扩展,那么为了维持他们各自的历史须要为每一种  ShareActionProvider  指定它们本身的历史文件。调用 setShareHistoryFileName()  并传递一个XML文件名(例如,"custom_share_history.xml" )就能为  ShareActionProvider  指定一个不一样的历史文件。

注解: 尽管  ShareActionProvider  是基于使用分享目标项的频率进行排名,这种处理行为仍是能够扩展的, ShareActionProvider  的扩展能够执行不一样的处理方式并基于历史文件进行排名(在适当状况下)。

建立自定义操做提供者

建立你本身的操做提供者可使你在独立的模块里重用和管理动态的操做项,而不是在activity或fragment代码里处理操做项的转变和行为。就像在前面章节中展现的,安卓已经为分享操做提供了一个  ActionProvider  的扩展:ShareActionProvider 。

为不一样的操做建立你本身的操做提供者,你只须要继承  ActionProvider  类并实现适当的回调方法便可。最重要的是,你应该继承下列方法:

  • ActionProvider()

  • 这个构造方法传递给你应用的  Context ,你应该把它保存在成员变量里以便在其余回调方法里使用。

  • onCreateActionView(MenuItem)

  • 在这里为这个项目定义操做视图。使用从构造方法里得到的  Context  去实例化  LayoutInflater  并使用XML资源去扩充你的操做视图布局,而后挂载事件监听器。例如:

    public View onCreateActionView(MenuItem forItem) {  // 扩充要在操做栏展现的操做视图。  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) {      // 一些处理...    }  });  return view;}
  • onPerformDefaultAction()

  • 当用户选择了更多操做里的菜单项时系统将会调用这个方法,而且操做提供者应该为菜单项执行默认的操做。

    然而,若是你的操做提供者支持子菜单,那么经过   onPrepareSubMenu() 回调,当操做提供者被放置在更多操做内时,子菜单实际上仍是显示的。所以,当有子菜单时  onPerformDefaultAction()  绝对不会被调用。

    注解: 实现  onOptionsItemSelected()  的activity或fragment能够经过处理菜单项被选中事件(而且返回true)来重写操做提供者的默认行为(除非它使用了子菜单),在这种状况下,系统不会调用  onPerformDefaultAction() 。

ActionProvider  扩展的例子,请参考 ActionBarSettingsActionProviderActivity 。

添加导航选项卡


图7 .  宽屏上的操做栏选项卡

图 8.  窄屏上的选项卡

操做栏上的选项卡可使用户很方便的在你应用里不一样的页面间浏览与切换。ActionBar  支持选项卡是很是明智的,由于他们能够适配不一样的屏幕尺寸。例如,当屏幕足够宽时,选项卡出如今操做栏里的操做按钮旁边(如图7所示,好比在平板上),然而当在一个狭窄的屏幕上时,他们出如今一个分离的操做栏上(如图8所示,被称为“堆叠的操做栏”) 。在某些状况下,安卓系统为了保证选项卡在操做栏上的最佳摆放会如下拉列表形式去展现选项卡。

首先,你的布局必须包含一个  ViewGroup ,用来放置选项卡相关的  Fragment。确保  ViewGroup  拥有一个资源ID以便你能在代码里引用它并在里面切换选项卡。另外一种状况,若是选项卡的内容将会填充整个activity布局,那么activity彻底不须要设置布局(你甚至不须要调用  setContentView() ), 也就是说你能够把每一个fragment放置在默认根视图(经过资源ID android.R.id.content 能够获取跟视图的引用)中。

一旦你决定了fragment在布局文件中的位置,那么添加选项卡的基本过程就是下面这些步骤:

  1. 继承  ActionBar.TabListener  接口。这个接口提供选项卡事件的回调,好比用户按下时你能够切换选项卡。

  2. 为每一个你想添加的选项卡,实例化一个  ActionBar.Tab  实例而且调用 setTabListener()  去设置   ActionBar.TabListener ,同时别忘了使用 setText()  设置选项卡的标题(视须要也可使用  setIcon()  设置图标)。

  3. 而后调用  addTab()  把每一个选项卡添加到操做栏中。

注意, ActionBar.TabListener  回调方法没有说明哪一个fragment与这个选项卡关联,仅仅指出了哪一个  ActionBar.Tab  被选中。你必须本身把每一个 ActionBar.Tab  与它表明的合适的  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;  /** 每次调用构造方法会建立一个新的选项卡    * @param activity  宿主Activity,用来实例化fragment    * @param tag  fragment的标识符    * @param clz  fragment的类,被用来实例化frament    */  public TabListener(Activity activity, String tag, Class<T> clz) {    mActivity = activity;    mTag = tag;    mClass = clz;  }  /* 下面是 ActionBar.TabListener 的回调 */  public void onTabSelected(Tab tab, FragmentTransaction ft) {    // 检查fragment是否已经实例化    if (mFragment == null) {      // 若是没有,实例化而且添加到activity中      mFragment = Fragment.instantiate(mActivity, mClass.getName());      ft.add(android.R.id.content, mFragment, mTag);    } else {      // 若是已经存在,为了显示出来只需把fragment依附到activity上      ft.attach(mFragment);    }  }  public void onTabUnselected(Tab tab, FragmentTransaction ft) {    if (mFragment != null) {      // 取消fragment的依附,由于另一个须要依附上      ft.detach(mFragment);    }  }  public void onTabReselected(Tab tab, FragmentTransaction ft) {    // 用户从新选择已经选择过的标签栏,一般不须要作任何事  }}

警告: 这这些回调里你 绝对不能 调用  commit()  去提交fragment事务—由于系统会为你调用它而且若是你本身调用时可能会抛出异常。一样你也 不能 把这些fragment事务添加到回退栈中。

在这个事例里,当各自的选项卡被选中时,监听器仅仅把fragment依附(attach() )到activity布局中,或者若是fragment没有实例化,建立fragment并把它添加( add() )到布局中(做为 android.R.id.content 视图组的一个子视图),当选项卡取消选中时取消fragment的依附( detach() )。

剩下须要作的就是建立每一个  ActionBar.Tab  并添加到  ActionBar 中。另外,你必须调用  setNavigationMode(NAVIGATION_MODE_TABS)  使选项卡可见。

例如,下面的代码使用上面定义的监听器添加两个选项卡:

@Overrideprotected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  // 注意没用使用setContentView(),由于咱们使用根节点android.R.id.content做为fragment的容器  // 设置操做栏上的选项卡  ActionBar actionBar = getSupportActionBar();  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);}

若是activity中止了,你应该在  saved instance state  中保存当前选中的选项卡信息以便在用户返回时能够打开合适的选项卡。当保存状态时,你可使用 getSelectedNavigationIndex()  查询当前选中的选项卡,它会返回选中选项卡的索引位置。

注意:  当用户使用选项卡在fragment之间切换,为了能返回到前一个fragment的状态,保存每一个fragment的状态就显得很是重要了,这样它们看起来还像它们离开时那样。虽然某些状态默认会被保存了,可是你可能须要手动保存自定义视图的状态。更多关于保存fragment状态的信息,请参考  Fragments  API指南。

注解: 上面继承  ActionBar.TabListener  的方式只是许多合适的技巧中的一种。另一种经常使用的方式是使用  ViewPager  去管理fragment,这样用户可使用滑动手势去切换选项卡。在这种状况下,你只需在  onTabSelected()  回调里告诉  ViewPager  当前选项卡的索引位置。更多详细的信息,请阅读  Creating Swipe Views with Tabs 。

添加下拉导航


 图9 .  操做栏中的下拉导航列表。

activity还有另一种导航模式(或称为过滤),在操做栏中嵌入下拉列表(也被称为“spinner”)。例如,下拉列表能够根据activity中内容的分类提供不一样的模式。

当改变内容的很重要可是又不频繁发生时使用下拉列表就很是有用了。假如切换内容会更频繁的话,你应该使用  navigation tabs  来替代。

开启下拉导航的基本过程:

  1. 建立一个  SpinnerAdapter  为下拉列表提供可选项目列表和布局以便在绘制列表里的项目时使用。

  2. 继承  ActionBar.OnNavigationListener  来定义用户选中列表里项目时的操做。

  3. 在activity的  onCreate()  方法里调用 setNavigationMode(NAVIGATION_MODE_LIST)  启用操做栏的下拉列表。

  4. 使用  setListNavigationCallbacks()  设置下拉列表的回调。例如: 

    actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationCallback);

    这个方法须要传入  SpinnerAdapter  和 ActionBar.OnNavigationListener 。

这个过程是很是简短的,可是实现  SpinnerAdapter  和 ActionBar.OnNavigationListener  须要作大量的工做。在这个文档的范围外还有有许多方式能够实现它们来为你的下拉导航定义功能并实现不一样类别的 SpinnerAdapter (你应该查阅  SpinnerAdapter  类获取更多信息)。下面只是个让你 入手  SpinnerAdapter  和  ActionBar.OnNavigationListener  的简单事例。

  SpinnerAdapter 和 OnNavigationListener

SpinnerAdapter  是为spinner控件提供数据的适配器,例如操做栏中的下拉列表。 SpinnerAdapter  是一个你能够继承的接口,然而安卓提供了一些有用的实现你能够直接拿来扩展,好比  ArrayAdapter  和  SimpleCursorAdapter 。例如,经过使用  ArrayAdapter  实现能够很轻松的建立一个使用字符数组做为数据源的   SpinnerAdapter  实例:

SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.action_list,
          android.R.layout.simple_spinner_dropdown_item);

createFromResource()  方法接收3个参数:应用的  Context ,字符数组的资源ID和每一个列表项目使用的布局。

资源文件中的字符输入定义以下:

<?xml version="1.0" encoding="utf-8"?><resources>
    <string-array name="action_list">
        <item>Mercury</item>
        <item>Venus</item>
        <item>Earth</item>
    </string-array></pre>

经过  createFromResource()  方法返回  ArrayAdapter  后你就能够直接把他传递给  setListNavigationCallbacks() (上面的步骤4)。在这么作以前,你须要建立  OnNavigationListener 。

你能够实现  ActionBar.OnNavigationListener  在用户选择下拉列表里的项目时来处理fragment的变化或activity的修改。继承这个监听器只须要实现一个回调方法:  onNavigationItemSelected() 。

onNavigationItemSelected()  方法接受列表中项目的位置和 SpinnerAdapter  提供的项目惟一ID做为参数。

下面是实例化一个  OnNavigationListener  匿名实现的事例,把一个 Fragment  插入到  R.id.fragment_container  标识的布局容器中:

mOnNavigationListener = new OnNavigationListener() {
  // 获取字符串给下拉列表的适配器使用
  String[] strings = getResources().getStringArray(R.array.action_list);

  @Override
  public boolean onNavigationItemSelected(int position, long itemId) {
    // 建立咱们定制的fragment事例
    ListContentFragment newFragment = new ListContentFragment();
    FragmentTransaction ft = openFragmentTransaction();
    // 无论fragment是否在fragment容器里都直接替换掉,而且用选中位置的字符串做为fragment的标签名
    ft.replace(R.id.fragment_container, newFragment, strings[position]);
    // 提交改变
    ft.commit();
    return true;
  }};

OnNavigationListener  实例化后你就能够把  ArrayAdapter  和这个 OnNavigationListener  传递给  setListNavigationCallbacks() (步骤4)并调用。

在这个事例中,当用户选中下拉列表中的项目时,把一个fragment添加到布局中(替换  R.id.fragment_container  视图中的当前fragment)。被添加的fragment被指定一个标签来做为它的惟一标识,而且标签名与下拉列表中标识fragment的字符串同样。

在这个事例中, ListContentFragment  类定义了每一个fragment:

public class ListContentFragment extends Fragment {
    private String mText;

    @Override
    public void onAttach(Activity activity) {
      // 这是接收到的第一个回调;咱们能够在fragment事务期间在此用指定的标签名为fragment设置文本内容
      super.onAttach(activity);
      mText = getTag();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,      Bundle savedInstanceState) {  // 这里为fragment定义布局  // 咱们只建立一个文本而且文本内容设置为fragment标签名  TextView text = new TextView(getActivity());  text.setText(mText);  return text;
    }}

设置操做栏的样式


若是你想创造能够表明你应用品牌的视觉设计,那么你能够经过定制操做栏的每一个显示细节达到效果,包括操做栏的颜色,文本的颜色,按钮的样式等等。这么作的话,你须要使用安卓的  style and theme  框架的专门的样式属性为操做栏重塑样式。

注意: 确保全部你使用到的背景图都是能够拉伸的  Nine-Patch drawables 。Nine-Patch图的高度应该小于40dp,宽小于30dp。

大致外观

  • windowActionBarOverlay

  • 声明操做栏是否应该覆盖在activity布局上,而不是平移activity布局的位置(例如:Gallery应用使用的是覆盖模式)。默认是 false 。

    一般操做栏在屏幕上有本身的空间且activity布局填充剩下的空间。当操做栏是覆盖模式时,activity布局使用全部可用的空间,而后系统在顶部绘制出操做栏。若是你想在操做栏隐藏和显示时使内容保持固定的大小和位置,使用覆盖模式就颇有用了。由于你能够为操做栏设置半透明背景以便用户始终能够看到操做栏后的某些activity布局,因此你也能够纯粹想把它做为一种视觉效果来使用。

    注解: Holo  主题家族默认使用半透明背景绘制操做栏。然而,你能够在你的样式里修改它而且在不一样设备上的  DeviceDefault  主题可能默认使用的是不透明的背景。

    当覆盖模式开启时,你的activity布局不会意识到操做栏在它的顶部趴着。因此你必须注意不要去替换操做栏覆盖区域里重要的信息或UI组建。在适当状况下,你能够在XML里从新指定  actionBarSize  在平台上的值来决定操做栏的高度。例如:

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

    你也能够在运行时经过   getHeight()  获取操做栏的高度。 在它被调用时能够反射出操做栏的高度,但是若是调用发生在activity的早期生命周期里可能获取的高度不包含堆叠操做栏(导航标签栏)。要了解如何在运行时肯定包含堆叠操做栏的总高度,请参考  Honeycomb Gallery  事例应用中的  TitlesFragment  类。

  • 操做项

    导航标签栏

    下拉列表

    主题事例

    下面是关于为activity定义自定义主题的事例, CustomActivityTheme 包含了自定义操做栏的一些样式。

    注意每一个操做栏的样式属性有两种版本。第一种版本在属性名里包含了 android:前缀的能够支持API等级11或更高框架里的全部属性。第二种版本不包含 android:前缀的是用来支持旧版本平台的,系统使用的支持库里的样式属性。每种效果都是同样的。

    <?xml version="1.0" encoding="utf-8"?><resources>
        <!-- 应用或activity应用的主题 -->
        <style name="CustomActionBarTheme"      parent="@style/Theme.AppCompat.Light">   <item name="android:actionBarStyle">@style/MyActionBar</item>   <item name="android:actionBarTabTextStyle">@style/TabTextStyle</item>   <item name="android:actionMenuTextColor">@color/actionbar_text</item>   <!-- 支持库兼容模式 -->   <item name="actionBarStyle">@style/MyActionBar</item>   <item name="actionBarTabTextStyle">@style/TabTextStyle</item>   <item name="actionMenuTextColor">@color/actionbar_text</item>
        </style>
    
        <!-- 操做栏的大致样式 -->
        <style name="MyActionBar"      parent="@style/Widget.AppCompat.ActionBar">   <item name="android:titleTextStyle">@style/TitleTextStyle</item>   <item name="android:background">@drawable/actionbar_background</item>   <item name="android:backgroundStacked">@drawable/actionbar_background</item>   <item name="android:backgroundSplit">@drawable/actionbar_background</item>   <!-- 支持库兼容模式 -->   <item name="titleTextStyle">@style/TitleTextStyle</item>   <item name="background">@drawable/actionbar_background</item>   <item name="backgroundStacked">@drawable/actionbar_background</item>   <item name="backgroundSplit">@drawable/actionbar_background</item>
        </style>
    
        <!-- 操做栏标题文本 -->
        <style name="TitleTextStyle"      parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Title">   <item name="android:textColor">@color/actionbar_text</item>
        </style>
    
        <!-- 操做栏标签栏文本 -->
        <style name="TabTextStyle"      parent="@style/Widget.AppCompat.ActionBar.TabText">   <item name="android:textColor">@color/actionbar_text</item>
        </style></resources>
  • 在清单文件中,你能够为整个应用应用这个主题:

    <application android:theme="@style/CustomActionBarTheme" ... />
  • 或者只为个别的activity应用:

    <activity android:theme="@style/CustomActionBarTheme" ... />
  • 注意: 请确保每一个在 <style> 标签里的主题和样式都声明了父主题,它从中继承了全部你的主题里没有明确声明的样式。在修改操做栏时使用父主题是很重要的,这样你就只需重写你想要改变的操做栏样式,而不用重写那些你不想修改的样式(好比操做项中的文本大小或边距)。

    更多关于在应用中使用主题和样式资源的信息,请阅读  Styles and Themes 。

相关文章
相关标签/搜索