Android开发指南:Action Bar

来源:互联网 发布:网络投资被骗 编辑:程序博客网 时间:2024/04/29 17:31
Action Bar是一个用来标明用户在app中所处的位置,呈现用户action和导航模式的窗口功能。Action bar可以让系统优雅地在不同屏幕配置上进行适配,为你的用户提供跨应用的统一使用体验。
Android开发指南:Action Bar - Pizzanicky - Pizzanicky的小窝
 图1:一个包括【1】app图标,【2】两个action项,以及【3】overflow的Action bar

Action bar提供下面几个主要功能:
提供一个用来展示app身份和用户在app中所处位置的固定专用空间;
让重要的action处于明显的易操作的位置(如搜索);
支持持续的应用中导航及视图切换(利用tab标签和下拉菜单)。

要了解更多关于Action bar的互动模式和设计准则,参见Android设计指南:Action Bar。

ActionBar API在Android3.0(API level 11)首次引入,同时为兼容Android2.1(API level 7)以上,这些API也存在于Support Library中。

本指南主要介绍support library的action bar使用,不过如果你的app只支持Android3.0以上,那么你可以直接使用framework提供的ActionBar API,多数API都是一样的——只不过是存在于不同的包命名空间——除了一些方法名等,后面会提到。

注意:确保你从正确的包import了ActionBar类(和相关API):
如支持API level 11以下:

import android.support.v7.app.ActionBar

如只支持API level 11及以上:

import android.app.ActionBar

注意:如果想查找关于用来显示情境相关action项的情境相关action bar,见Menu指南。

添加Action Bar

如上所述,本指南主要讲述support library中的ActionBar,因此在添加action bar之前,你必须先在你的project中设置appcompat v7 support library,参照Support Library Setup。

Project设置好support library之后,按下面的步骤添加action bar:
1. 继承ActionBarActivity,创建你的activity;
2. 为你的activity使用(或继承)Theme.Appcompat中的主题。如下:
<activity android:theme="@style/Theme.AppCompat.Light" ... >
这样你的activity运行在Android2.1(API level 7)及以上时就会有action bar了。

对于API level 11及以上
所有使用Theme.Holo主题(或其继承者)的activity都会包含action bar,当targetSdkVersionminSdkVersion属性设为“11”或以上时,默认主题就是它。如果不需要某个activity显示action bar,可以将它的主题设置为Theme.Holo.NoActionBar

移除Action Bar
在程序运行时,可以调用hide()隐藏action bar,如下:
ActionBar actionBar = getSupportActionBar();actionBar.hide();
对于API level 11及以上
getActionBar()方法获得ActionBar。

Action Bar隐藏时,系统会自动调整页面布局来填满空余出来的屏幕空间,用show()可以重新显示出action bar。

注意隐藏或者移除action bar会使activity重新对页面进行布局来补充action bar占用的部分,如果你的activity经常显示和隐藏action bar,那么使用overlay模式会更好。Overlay模式会把action bar绘制在布局之上,遮挡顶部的一部分,这样,你的action bar隐藏或者重新出现时,activity的布局就不会改变。要开启overlay模式,须要为你的activity创建一个自定义主题,并将其windowActionBarOverlay属性设为true。更多信息参见Styling the Action Bar。

用logo替换图标
系统会默认在action bar上使用manifest文件中application或activity元素里icon属性指定的图标,不过如果你同时还指定了logo属性,action bar则会使用logo图片。

通常logo应该比图标宽一些,但是不应包含不必要的文字。一般来说,应该在需要使用传统的用户认可熟悉的商标等标志时使用logo,YouTube app的logo就是个很好的例子——logo是用户期望中的标志,而app图标则是为遵循方形图标而进行过修改的。

添加Action项

Action bar为用户提供每个情境下最重要的action的操作,直接以图标和/或文字形式出现在action bar上的项称为action按钮,action bar无法容纳或不那么重要的action则放在overflow中。用户可通过点按右侧的overflow按钮(或者设备上的Menu按键)来显现overflow菜单。
Android开发指南:Action Bar - Pizzanicky - Pizzanicky的小窝
图2:带有3个action按钮和overflow按钮的action bar

Activity启动时,系统通过调用activity的onCreateOptionsMenu()来生成action项,用这个方法来加载定义action项的菜单资源。下面是个菜单资源的例子:
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对象,这样每一项就都被添加到了action bar:
@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);}
要让action项直接显示为action按钮,须要在<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>
如果action bar上的空间不够,该项就会被放进overflow。
使用support library中的XML属性
要注意到上例中的showAsAction属性用到了<menu>标签中的一个自定义名字空间,使用support library中定义的任何XML属性都必须这样做,因为这些属性在老设备的Android framework中并不存在,所以必须用自己的名字空间来作为support library的所有属性的前缀。

如果你的菜单项既有标题又有图标——通过title和icon属性指定——那么默认会只显示图标,如果要显示文字标题,就须要在showAsAction属性里添加"withText",如下:
<item yourapp:showAsAction="ifRoom|withText" ... />
注意:"withText"只是提示action bar应该显示文字标题,action bar会在可能的情况下显示文字,但如果该项有图标而且action bar没有空间,那么文字还是有可能不显示。

但对于每个菜单项都应该定义title,哪怕你不显示它,原因如下:
如果action bar没有空间显示action项,那么这些项就会出现在overflow中,而在overflow中只会显示文字标题;
视力障碍用户使用的屏幕阅读功能会读出菜单项的文字标题;
如果action项只显示为图标,用户可以长按图标来显示标题作为提示。

icon属性是可选的,但建议对其进行指定。对于图标设计方面的建议,参见Iconography设计指南。你还可以从下载页面下载整套的标准action bar图标(比如搜索和放弃修改)。

可以用"always"来声明action项永远显示为action按钮,然而,你不应该用这种方式让一个action项始终显示为action按钮,否则在较窄的屏幕上可能造成布局问题。最好还是用"ifRoom"请求让它显示为action按钮,同时也允许系统在空间不足时把它放入overflow。需要用到这个值的情况是当这个项包含一个无法折叠,并且要永远可见,提供关键功能的action view。

处理action项的点击事件
当用户点按一个action时,系统调用activity的onOptionsItemSelected()方法,用传入这个方法的MenuItem对象调用getItemId()可以辨别是哪个action,方法返回<item>标签的id属性设置的唯一ID,然后就可以根据ID执行相应的action。如下:
@Overridepublic boolean onOptionsItemSelected(MenuItem item) {    // Handle presses on the action bar items    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类的onCreateOptionsMenu()回调来从fragment加载菜单项,用户选择action项时系统会调用该fragment的onOptionsItemSelected()。不过,activity会先得到一个处理这个事件的机会,系统会在调用fragment的同方法之前先调用activity的onOptionsItemSelected()。为确保activity的所有fragment都有处理事件的机会,在自己处理完事件后不要直接返回false,而应该总是默认把调用传递给父类。

使用分离式action bar
分离式action bar可以在屏幕比较窄的情况下(比如竖屏的设备)提供一个位于屏幕底部的action bar来放置action项。

这种把action item分离出来的方式确保了在窄屏上能有足够的空间来显示action项,同时还在顶部留出了用于页面导航和标题显示的空间。
Android开发指南:Action Bar - Pizzanicky - Pizzanicky的小窝
 图3. 模拟图,带有标签的action bar(左);分离式action bar(中);不显示图标和标题(右)

在support library中启用分离式action bar,需要两个步骤:
1. 在manifest文件中为每个activity或者为application添加uiOptions="splitActionBarWhenNarrow",这个属性只有API level 14及以上才能解析(老版本上会直接忽略)。
2. 要支持老版本,须要为每个activity元素添加一个meta-data子元素,名称设为"android.support.UI_OPTIONS"
例子:
<manifest ...>    <activity uiOptions="splitActionBarWhenNarrow" ... >        <meta-data android:name="android.support.UI_OPTIONS"                   android:value="splitActionBarWhenNarrow" />    </activity></manifest>
分离式action bar还允许导航标签收缩到主action bar,前提是不显示图标和标题(如图3中右侧所示),要达到这个效果,必须用setDisplayShowHomeEnabled(false)和setDisplayShowTitleEnabled(false)来禁用图标和标题。

用App图标返回上层界面

把app图标用作Up按钮使用户能够在层级关系的界面中进行导航,比如,A界面显示一个list,点击其中一项进入界面B,这时界面B就应该包含一个能够返回A界面的Up按钮。
注:Up导航和系统的back导航有区别,back按钮用来按用户最近访问的事件顺序进行逆向回退,基本上基于界面间的临时关系,而非app的层级结构(up导航的基础)。
Android开发指南:Action Bar - Pizzanicky - Pizzanicky的小窝
 图4. Gmail中的Up按钮

要把app图标用作Up按钮,须调用setDisplayHomeAsUpEnabled(),例子:
@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_details);    ActionBar actionBar = getSupportActionBar();    actionBar.setDisplayHomeAsUpEnabled(true);    ...}
这样action bar的图标旁就多了一个Up括号(如图4),不过,它默认是不会做任何事情的,要指定用户点击时打开的activity,你有两个选择:
在manifest文件中指定父activity
如果父activity永远是同一个时这是最好的选择。通过在manifest中声明哪个是父activity,用户点击Up按钮时,action bar会自动执行正确的action。
从Android4.1(API level 16)开始,可以通过activity元素的parentActivityName属性来声明父activity。
如要通过support library来支持较老的设备,加入一个meta-data元素,将android.support.PARENT_ACTIVITY的值指定为父activity。例子:
<application ... >    ...    <!-- The main/home activity (has no parent activity) -->    <activity        android:name="com.example.myfirstapp.MainActivity" ...>        ...    </activity>    <!-- A child of the main activity -->    <activity        android:name="com.example.myfirstapp.DisplayMessageActivity"        android:label="@string/title_activity_display_message"        android:parentActivityName="com.example.myfirstapp.MainActivity" >        <!-- Parent activity meta-data to support API level 7+ -->        <meta-data            android:name="android.support.PARENT_ACTIVITY"            android:value="com.example.myfirstapp.MainActivity" />    </activity></application>
这样指定后,再用setDisplayHomeAsUpEnabled()来启用Up按钮,action bar就已经能实现返回上级的功能了。

或者,override你的activity中的getSupportParentActivityIntent()和onCreateSupportNavigateUpTaskStack()
如果由于用户到达目前界面方式不同而造成父activity不一定相同,那么这种方法会比较合适。也就是说,如果用户有多种途径可以到达当前界面,那么Up按钮应该沿着该途径返回上层。
当用户在你的app中导航(在你app自己的task中)时按了Up按钮,系统会调用getSupportParentActivityIntent()。如果Up应该打开的activity由于用户到达当前界面的途径不同而不同,那么就应该override此方法并返回启动相应activity的Intent。
当你的activity运行在属于你的app的task中时,如果用户点按Up按钮,系统会为你的activity调用onCreateSupportNavigateUpTaskStack(),这样,你必须用传入此方法的TaskStackBuilder对象来创建相应的back stack。
即使你通过override getSupportParentActivityIntent()指定了上层activity,你还是可以在manifest文件中声明“默认”父activity,这样可以不用重新实现onCreateSupportNavigateUpTaskStack(),它的默认实现即会基于这个声明的父activity来构建back stack。
注:如果你用一系列fragment来构建你app的层级结构,而非用多个activity,那么上面两个选择就都不行了,这时,要在fragment间进行up操作,就需要override onSupportNavigateUp()来进行相应的fragment切换——通常是调用popBackStack()把fragment从back stack中弹出。

更多关于实现Up导航,见Providing Up Navigation。

添加Action View

Action View是一个出现在action bar上用来替代action按钮的部件,它能在不改变activity或者fragment,并且不替换action bar的情况下提供快捷丰富的action。比如,你有一个搜索action,那么你可以在action bar上添加一个嵌有SearchView的action view,如图5。
Android开发指南:Action Bar - Pizzanicky - Pizzanicky的小窝
 图5. 带有可收起的SearchView的action bar

要声明一个action view,使用actionLayout或者actionViewClass属性分别来指定一个布局资源或要使用的widget类。下面是添加SearchView widget的例子:
<?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"这样一个值,这是可选的,声明了该action view可以收起为一个按钮。(这个行为将在下一节处理可收起的action view中详细解释)

如需配置action view(如增加事件监听),可在onCreateOptionsMenu()回调中进行。你可以调用static方法MenuItemCompat.getActionView()来获得action view对象,并传入相应的MenuItem。下面是获取这个搜索widget的例子:
@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);    // Configure the search info and add any event listeners    ...    return super.onCreateOptionsMenu(menu);}
对于API level 11及以上
对相应的MenuItem调用getActionView()来获得action view:
menu.findItem(R.id.action_search).getActionView()
要了解更多搜索widget的信息,见Creating a Search Interface

处理可收起的action view
为节省action bar空间,可以把你的action view收起成一个action按钮,收起后,系统可能会把它放进overflow,但是用户选中它时,action view还是会照样出现在action bar上。你可以通过在showAsAction属性中添加"collapseActionView"来使你的action view能够折叠起来,如前面的XML所示。

因为系统会在用户选择action时展开action view,所以你无需响应onOptionsItemSelected()回调。系统仍然会调用onOptionsItemSelected(),但如果你返回true(表明你已经处理了事件),action view就不会展开了。

系统也会在用户按Up或Back按钮时收起你的action view。

如需根据action view的可见性来更新你的activity,可以通过定义一个OnActionExpandListener并传给setOnActionExpandListener()来接收展开收起时的回调。例子:
@Overridepublic boolean onCreateOptionsMenu(Menu menu) {    getMenuInflater().inflate(R.menu.options, menu);    MenuItem menuItem = menu.findItem(R.id.actionItem);    ...    // When using the support library, the setOnActionExpandListener() method is    // static and accepts the MenuItem object as an argument    MenuItemCompat.setOnActionExpandListener(menuItem, 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        }    });}

添加Action Provider

类似于action view,action provider也是用自定义的布局来替换action按钮,和action view不同的是,action provider接管了该action的所有行为,而且点击时可以显示一个子菜单。

要声明action provider,给菜单<item>标签的actionViewClass属性中为ActionProvider添加一个完整的类名。

你可以继承ActionProvider类来创建自己的action provider,不过Android提供了一些预先建好的action provider,比如ShareActionProvider,它简化了“分享”action,它会在action bar上展示一个所有可供直接分享的app列表(如图6)。
Android开发指南:Action Bar - Pizzanicky - Pizzanicky的小窝
图6. 一个带有ShareActionProvider的action bar,显示了分享目标

因为每个ActionProvider都定义了自己的action行为,所以无需在onOptionsItemSelected()方法中监听action。当然,如果需要,你还是可以在该方法中监听点击事件,以便你可以同时执行别的action,但要一定要返回false,确保action provider还能接收到onPerformDefaultAction()回调来执行既定的action。

不过,如果action provider提供自己的action子菜单,用户打开子菜单或选中子菜单项时你的activity是不会收到onOptionsItemSelected()调用的。

使用ShareActionProvider
要使用ShareActionProvider添加“分享”action,须用ShareActionProvider<item>标签定义一个actionProviderClass属性,例子:
<?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>
这样这个action provider就接管了这个action项,包括它的行为和外观,但是标题仍然是必须的,以供它在overflow中时使用。

然后唯一需要做的事就是定义用来分享的Intent,在你的onCreateOptionsMenu()中调用MenuItemCompat.getActionProvider(),传入拥有action provider的MenuItem,然后在返回的ShareActionProvider对象上调用setShareIntent(),传入一个附有相应内容的ACTION_SEND intent。

你应该在onCreateOptionsMenu()时调用setShareIntent()来初始化分享action,但由于用户情境可能会改变,你必须在可分享内容变化时再次调用setShareIntent()来更新intent。

例子:
private ShareActionProvider mShareActionProvider;@Overridepublic boolean onCreateOptionsMenu(Menu menu) {    getMenuInflater().inflate(R.menu.main_activity_actions, menu);    // Set up ShareActionProvider's default share intent    MenuItem shareItem = menu.findItem(R.id.action_share);    mShareActionProvider = (ShareActionProvider)            MenuItemCompat.getActionProvider(shareItem);    mShareActionProvider.setShareIntent(getDefaultIntent());    return super.onCreateOptionsMenu(menu);}/** Defines a default (dummy) share intent to initialize the action provider.  * However, as soon as the actual content to be used in the intent  * is known or changes, you must update the share intent by again calling  * mShareActionProvider.setShareIntent()  */private Intent getDefaultIntent() {    Intent intent = new Intent(Intent.ACTION_SEND);    intent.setType("image/*");    return intent;}
这样ShareActionProvider就会处理该action项的所有用户行为,你也无需调用onOptionsItemSelected()处理点击事件。

默认设置下,ShareActionProvider会根据用户对分享目标的选择频度对各分享目标进行排序,最常使用的目标会出现在下拉菜单的顶端,而且会作为默认分享目标直接显示在action bar上。这个排序信息默认会存储在一个私有文件中,文件名由DEFAULT_SHARE_HISTORY_FILE_NAME指定,如果你只为一种action使用ShareActionProvider或它的一个子类,那你应该继续使用默认的历史文件,不用做任何处理。但如果要为多个不同含义的action使用,那么每个ShareActionProvider都应该指定自己的历史文件,来维护自己的历史记录。要为ShareActionProvider指定不同的历史文件,要调用setShareHistoryFileName()并提供一个XML文件名(如,"custom_share_history.xml")。

注:尽管ShareActionProvider根据使用频率来给分享目标排名,但这个行为是可扩展的,而且ShareActionProvider的子类可以有不同的行为和对历史文件的排名方式。

创建自定义Action Provider
创建你自己的action provider允许你以独立模块的方式重用和管理动态action项的行为,而不用在你的fragment或activity代码中来处理action行为。如前所述,Android已经为分享action提供了一个ActionProvider的实现:ShareActionProvider。

要为其它的action创建你自己的action provider,只需继承ActionProvider类并实现相应的回调方法,其中最主要的几个:
ActionProvider()
此构造方法要传入application的Context,你应该把它保存在成员field中,以便在其它回调方法中使用。
onCreateActionView(MenuItem)
在这里为该action项定义action view,用构造方法中获得的Context来实例化一个LayoutInflater,并从XML资源中加载你的action view布局,然后再设置事件监听器。例子:
public View onCreateActionView(MenuItem forItem) {    // 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()
当action overflow中的菜单项被选中时系统会调用此方法,action provider应该为该菜单项执行一个默认action。
但是,如果你的action provider通过onPrepareSubMenu()提供子菜单,那么即使action provider放置在overflow中,子菜单也一样会弹出。这样一来,有子菜单时onPerformDefaultAction()就不会调用。
注:实现onOptionsItemSelected()的Activity或fragment可以通过处理选中事件(并返回true)来override action provider的默认行为(除非它使用了子菜单),这种情况下,系统不会调用onPerformDefaultAction()。

要看继承ActionProvider的例子,见ActionBarSettingsActionProviderActivity。

添加导航tab

Android开发指南:Action Bar - Pizzanicky - Pizzanicky的小窝
 图7. 宽屏上的action bar tab

Action bar上的tab使用户更方便地在app中的不同视图间浏览切换,ActionBar提供的tab可以理想地适应不同屏幕尺寸。比如,当屏幕足够宽时,tab和action按钮在action bar上一字排开(例如在平板上,如图7),而当屏幕比较窄时,这些tab就会出现在一个单独的bar(称为stacked action bar,如图8)上。某些情况下,Android系统为保证action bar的显示效果,会把tab显示为一个下拉菜单。
Android开发指南:Action Bar - Pizzanicky - Pizzanicky的小窝
图8. 窄屏上的tab

要使用tab,你的布局必须有一个ViewGroup,用来放置和tab相关的Fragment。这个ViewGroup一定要有资源ID,这样你才能在代码中引用它来切换tab。如果tab的内容会充满activity,那么你的activity就完全不需要布局了(你甚至不用调用setContentView()),你可以把fragment都放在默认的根视图里,根视图可以用android.R.id.content这个ID来引用。

在确定了fragment都出现在布局的哪些地方后,添加tab的其他基本步骤如下:
1. 实现ActionBar.TabListener接口,该接口提供tab事件的回调,比如用户点击其中一个时你可以实现切换。
2. 为你要添加的每个tab,实例化一个ActionBar.Tab,并调用setTabListener()来设置ActionBar.TabListener,还须用setText()给tab设置标题(可选的,还可以用setIcon()设置图标)。
3. 调用addTab()把tab添加到action bar。

注意,ActionBar.TabListener的回调方法不指定哪个fragment是和tab相关联的,而只是哪个ActionBar.Tab被选中了。你必须自己定义ActionBar.Tab和其代表的Fragment之间的关系,根据你的设计不同,有好几种方法可以定义这个关系。

例如,下面时一种可能的ActionBar.TabListener实现,每个tab都要其自己的listener实例:
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转换加入back stack。

在上例中,当相应的tab被选中时,listener只是把fragment附到(attach())activity布局中——或者如果fragment还没有实例化,就创建一个fragment并添加(add())到布局中(作为android.R.id.content的子视图)。当tab不被选择时,则将fragment分离(detach())。

剩下的就是创建各ActionBar.Tab并将其加入ActionBar,另外,你必须调用setNavigationMode(NAVIGATION_MODE_TABS)使tab可见。

下面的代码添加了两个tab,它们都使用了上面定义的listener:
@Overrideprotected 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 = 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进入了stop状态,你应该用saved instance state来保持当前选中的tab,这样用户回来时就能恢复打开相应的tab。该保存状态时,可以用getSelectedNavigationIndex()来获知当前tab,该方法返回选中tab的索引。

警告:保存每个fragment的状态至关重要,这样用户用tab切换fragment并返回到之前的fragment时,能看上去和离开时一样。有些状态是默认会保存的,但是对于自定义view你可能就得手动保存。要了解关于保存fragment状态的信息,见Fragment API指南。

注:上述ActionBar.TabListener的实现只是若干种技术中的一种,另一个广泛使用的方法是用ViewPager来管理fragment,这样用户就可以用滑动手势来切换tab,这种情况下,你只要在onTabSelected()回调中告诉ViewPager当前的tab位置。要了解更多,见Creating Swipe Views with Tabs。

添加下拉导航

作为activity导航(或条件过滤)的另一种模式,action bar提供了内建的下拉菜单(也称为spinner),例如,下拉菜单能为activity内容的排序提供不同的方式选择。

下拉菜单适用于内容的改变比较重要但不需很频繁的情况,对于内容切换比较频繁的情况,则应该使用导航tab。
Android开发指南:Action Bar - Pizzanicky - Pizzanicky的小窝
 图9. Action bar上的下拉导航菜单

启用下拉导航的基本步骤:
1. 创建一个提供下拉菜单选项内容的SpinnerAdapter,和用来绘制每个菜单项的布局文件。
2. 实现ActionBar.OnNavigationListener来定义用户选中菜单项时的行为。
3. 在activity的onCreate()方法中,调用setNavigationMode(NAVIGATION_MODE_LIST)来启用action bar的下拉菜单。
4. 用setListNavigationCallbacks()指定下拉菜单的回调方法,例如:
actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationCallback);
这个方法需要SpinnerAdapter和ActionBar.OnNavigationListener作为参数。

步骤虽不多,但实现SpinnerAdapterActionBar.OnNavigationListener是其中最花功夫的。有许多为你的下拉导航实现功能的方法,而且实现各种SpinnerAdapter的方法超出了本文的范畴(参考SpinnerAdapter类了解更多信息),不过为让你尽快上手,这里还是提供了一个SpinnerAdapterActionBar.OnNavigationListener的例子。

SpinnerAdapter和OnNavigationListener的例子
SpinnerAdapter是为类似action bar中的下拉菜单这样的spinner widget提供数据的adapter,它是个接口,你可以直接实现它,但Android已经提供了一些现成的有用实现供你继承使用,比如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的操作的,该listener中只有一个回调方法需要实现:onNavigationItemSelected()。

onNavigationItemSelected()会收到list项在list中的位置,还有SpinnerAdapter提供的唯一ID。

下例实例化了一个OnNavigationListener的匿名实现,在一个id为R.id.fragment_container的container中插入一个Fragment
mOnNavigationListener = new OnNavigationListener() {  // Get the same strings provided for the drop-down's ArrayAdapter  String[] strings = getResources().getStringArray(R.array.action_list);  @Override  public boolean onNavigationItemSelected(int position, long itemId) {    // Create new fragment from our own Fragment class    ListContentFragment newFragment = new ListContentFragment();    FragmentTransaction ft = openFragmentTransaction();    // Replace whatever is in the fragment container with this fragment    //  and give the fragment a tag name equal to the string at the position selected    ft.replace(R.id.fragment_container, newFragment, strings[position]);    // Apply changes    ft.commit();    return true;  }};
这就成了个完整的OnNavigationListener,你可以调用setListNavigationCallbacks()(步骤4),传入ArrayAdapter和这个OnNavigationListener。

本例中,用户选中下拉菜单项时,布局中就会加入一个fragment(替换R.id.fragment_container视图中的当前fragment)。加入时fragment会带有一个tag字符串,也就是对应的下拉菜单项的字符串。

下面是本例中定义各fragment的ListContentFragment类:
public class ListContentFragment extends Fragment {    private String mText;    @Override    public void onAttach(Activity activity) {      // This is the first callback received; here we can set the text for      // the fragment as defined by the tag specified during the fragment transaction      super.onAttach(activity);      mText = getTag();    }    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,            Bundle savedInstanceState) {        // This is called to define the layout for the fragment;        // we just create a TextView and set its text to be the fragment tag        TextView text = new TextView(getActivity());        text.setText(mText);        return text;    }}

Action Bar的风格化
如果想实现能代表品牌风格的视觉设计,action bar允许你对其每个细节进行定制,包括action bar颜色,文字颜色,按钮风格等等。你须使用Android的style and theme框架来定义自己的风格属性。
注意:提供背景图资源时,确保使用Nine-Patch drawables来保证拉伸时的效果,nine-patch图片应小于40dp 高,30dp宽。

总体外观
actionBarStyle
指定为action bar定义各种风格的风格资源文件
默认为Widget.AppCompat.ActionBar,你的自定义风格应该以它为父风格
支持的各种风格如下:
background
定义action bar背景的图片资源
backgroundStacked
定义stacked action bar(tab标签)的图片资源
backgroundSplit
定义分离式action bar的图片资源
actionButtonStyle
为action按钮定义风格资源
默认为Widget.AppCompat.ActionButton,你的自定义风格应该以它为父风格
actionOverflowButtonStyle
定义overflow action项的风格资源
默认为Widget.AppCompat.ActionButton.Overflow,你的自定义风格应该以它为父风格
displayOptions
定义一个或多个action bar显示选项,比如是否使用app logo,是否显示activity标题,以及是否使用Up action。displayOptions中列出了所有可以使用的值。
divider
定义action项直接的分隔图片资源
titleTextStyle
为action bar标题定义风格资源
默认为TextAppearance.AppCompat.Widget.ActionBar.Title,你的自定义风格应该以它为父风格

windowActionBarOverlay
声明action bar是覆盖activity布局还是将activity布局下移(例如,Gallery app使用了覆盖模式)。默认值false
通常,action bar需要有自己的屏幕空间,activity的布局则填满剩余的空间。Action bar为覆盖模式时,activity布局会使用所有的屏幕空间,系统会把action bar绘制在其上,如果你想让自己的内容在action bar显示和隐藏时保持同样的大小和位置,那么覆盖模式会很有用。你也可能单纯为其视觉效果而使用它,因为你可以给action bar一个半透明的背景,这样用户透过action bar也可以看到下面的内容。
注:Holo类主题默认使用半透明背景的action bar,不过也可以修改为你自己的风格,另外不同设备上的 DeviceDefault主题也可能会默认使用不透明的背景。

开启覆盖模式时,你的activity布局并不知道action bar位于其上,因此在action bar覆盖区域不能放置重要信息或UI组件,要想合理适配,你可以引用actionBarSize的系统值来决定视图上方应该留出多少空间,例如:
<SomeView    ...    android:layout_marginTop="?android:attr/actionBarSize" />
也可以在运行时用getHeight()获取action bar的高度,这个方法返回调用时的action bar高度,在activity生命周期的早期方法中调用时返回的高度可能不包括stacked action bar(由于navigation tabs)。要得到运行时action bar包括stacked action bar的总高度,见例子app Honeycomb Gallery中的TitlesFragment。

Action项

actionButtonStyle
定义action按钮的风格资源
默认为Widget.AppCompat.ActionButton,你的自定义风格应该以它为父风格
actionBarItemBackground
定义每个action项的背景资源drawable,应为一个可以指示不同选择状态的state-list drawable
itemBackground
定义每个overflow项的背景,应为一个可以指示不同选择状态的state-list drawable
actionBarDivider
定义action项之间分隔的drawable资源
actionMenuTextColor
定义action项文字的颜色
actionMenuTextAppearance
定义action项文字的外观风格资源
actionBarWidgetTheme
定义action view widgets的主题资源

Navigation tabs

actionBarTabStyle
定义action bar的tab标签的风格资源
默认为Widget.AppCompat.ActionBar.TabView,你的自定义风格应该以它为父风格
actionBarTabBarStyle
定义navigation tab下方细条的风格资源
默认为Widget.AppCompat.ActionBar.TabBar,你的自定义风格应该以它为父风格
actionBarTabTextStyle
定义navigation tab中的文字风格资源
默认为Widget.AppCompat.ActionBar.TabText,你的自定义风格应该以它为父风格

下拉列表

actionDropDownStyle
定义下拉导航的风格(如背景和文字风格
默认为Widget.AppCompat.Spinner.DropDown.ActionBar,你的自定义风格应该以它为父风格

Theme例子

这是个为activity定义自定义主题的例子,CustomActivityTheme,包含了几个用来自定义action bar的风格。

注意,每个action bar风格有两个版本,第一个属性名前包含了android:前缀,以支持framework中已包含这些属性的API level 11及以上版本;第二个版本没有android:前缀,针对使用support library中风格属性的老版本,效果是一样的。
<?xml version="1.0" encoding="utf-8"?><resources>    <!-- the theme applied to the application or 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>        <!-- Support library compatibility -->        <item name="actionBarStyle">@style/MyActionBar</item>        <item name="actionBarTabTextStyle">@style/TabTextStyle</item>        <item name="actionMenuTextColor">@color/actionbar_text</item>    </style>    <!-- general styles for the action bar -->    <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>        <!-- Support library compatibility -->        <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>    <!-- action bar title text -->    <style name="TitleTextStyle"           parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Title">        <item name="android:textColor">@color/actionbar_text</item>    </style>    <!-- action bar tab text -->    <style name="TabTextStyle"           parent="@style/Widget.AppCompat.ActionBar.TabText">        <item name="android:textColor">@color/actionbar_text</item>    </style></resources>
在manifest文件中,可以为整个app指定theme:
<application android:theme="@style/CustomActionBarTheme" ... />
或者为单个activity指定:
<activity android:theme="@style/CustomActionBarTheme" ... />
注意:确保每个theme和风格在 <style> 标签中声明其父theme,这样它们会从父theme中继承所有未显式声明风格。修改action bar时,使用父theme很重要,这样你可以不用重新实现所有的风格属性(比如文字大小或action项的间距),只需override你要改变的风格。

更多关于app中风格和theme资源的信息,请阅读Styles and Themes

原文:Action Bar
链接:http://developer.android.com/guide/topics/ui/actionbar.html
0 0