安卓开发文档翻译:动作栏,Action Bar
设计指南
文档内容
关键 类
动作栏是一个窗口特性,用于标识用户的位置,并且提供可用的用户动作及导航模式。使用动作栏,可以给用户在不同的应用程序之间提供一种熟悉的界面,而系统会将这个熟悉的界面在不同的屏幕配置情况下做适配。
图1. 一个包含以下元素的动作栏:[1] 应用程序图标、[2] 两个动作选项和[3] 动作栏溢出内容。
动作栏提供了多个关键功能:
•提供了一个固定的空间,用来标识妳的应用程序,并且用来标示用户当前位于应用程序里的什么位置。
• 使得重要的动作(例如 搜索 (Search))变得突出,并且用户能通过一种可预见的方式来找到它们。
•支持在应用程序内部进行统一风格的导航和视图切换(具体手段是使用标签页或下拉列表)。
欲知更多关于动作栏的交互模式及设计指南的信息,则阅读 动作 栏 设计指南。
ActionBar 应用编程接口首先是在安卓3.0(应用编程接口版本11)开始加入的,但是,在 支持 库 中也有对应的版本,可兼容到安卓2.1 (应用编程接口版本7)及以上版本。
这篇文档专注于说明如何使用支持库中的动作栏,但是,如果妳的应用程序只支持安卓3.0或更高版本的话,则,妳应当使用框架中提供的ActionBar应用编程接口。大部分接口都是相同的——但是其处于的包命名空间不一样——只有少数几个例外,它们的方法名或方法特征(signatures)不一样,这些情况在后续小节中会说明。
注意:要注意,确保妳是从正确的包中导入的ActionBar类(及相关的应用编程接口):
•如果要支持的应用编程接口版本低于11:
则导入android.support.v7.app.ActionBar
•如果只支持应用编程接口版本11及更高版本:
则导入android.app.ActionBar
注意:如果妳在寻找用来显示上下文相关的动作项的上下文动作栏的话,则阅读 Menu 指南。
前面已经说过,这篇指南专注于说明如何使用支持库中的 ActionBar应用编程接口。所以,在添加动作栏之前,妳必须按照 支持 库设置 中的说明来配置好妳的项目以使用appcompat v7 支持库。
当妳设置好自己的项目来使用支持库之后,按照以下步骤来添加动作栏:
1.创建妳的活动(activity),继承自ActionBarActivity。
2.针对妳的活动,使用(或继承)众多的Theme.AppCompat 主题中的一个。例如:
<activity android:theme="@style/Theme.AppCompat.Light" ... >
现在,当妳的这个活动在安卓2.1(应用编程接口版本7)或更高版本的系统中运行时,就会包含有动作栏了。
对于应用编程接口版本11或更高的版本的情况
在所有使用Theme.Holo 主题(或它的某个继承主题)的活动中,都会包含动作栏,而这个主题呢,当targetSdkVersion 或minSdkVersion 属性被设置为"11"或更高值时就会成为默认主题。如果妳不希望某个活动中带有动作栏,那么,将该活动的主题设置为Theme.Holo.NoActionBar。
妳可以通过调用hide()来在运行时隐藏掉动作栏。例如:
ActionBar actionBar = getSupportActionBar();
actionBar.hide();
对于应用编程接口版本11或更高的版本的情况
使用getActionBar()方法来获取到 ActionBar 。
当动作栏被隐藏时,系统会调整妳的界面布局,以填满屏幕上新的可用空间。妳可调用 show() 来重新显示动作栏。
注意,隐藏及删除动作栏的操作,会引起妳的活动被重新计算布局,以便计算被动作栏占用的空间。如果妳的活动会频繁地隐藏及显示动作栏的话,那么,妳可能需要启用覆盖(overlay)模式。在覆盖模式中,动作栏会被绘制在妳的活动布局的前面,使得屏幕顶部变成模糊的。这样,当动作栏隐藏及重新出现时,妳的布局会保持固定。要想启用覆盖模式的话,则为妳的活动创建一个自定义主题,并将windowActionBarOverlay 设置为真(true)。欲知更多信息,则阅读后续章节中的 自定义动作栏风格 。
默认情况下,系统会在动作栏中使用妳的应用程序图标,即为<application>或<activity>元素中的icon 属性所指定的图标。但是,如果妳同时也指定了logo属性的话,则,动作栏会使用logo指定的图片,而不是应用程序图标图片。
标志(logo)通常会比图标要宽,但是,不应当包含非必需的文字内容。通常情况下,当妳的标志图片代表着能被用户认出的某种传统格式的品牌时,就应当使用该标志图片。YouTube 应用程序的标志就是一个好例子——标志图片代表着用户预期的品牌,而该应用程序的程序图标被进行过修改,以满足启动标志的正方形规范。
图2. 带有3个动作按钮和溢出按钮的一个动作栏。
动作栏会向用户呈现出与应用程序当前的上下文相关的最重要的动作项。那些直接出现在动作栏中并且带有图标和/或文字的视图对象,被称作动作按钮。那些无法在动作栏中挤下的动作项,或者那些不太重要的动作项,都会隐藏到动作溢出菜单里。用户可通过点击右侧的溢出按钮(或者在设备带了菜单键的情况下,按 菜单 (Menu)键)来显示出其它动作项的列表。
当妳的活动启动时,系统会通过调用妳的活动的onCreateOptionsMenu()方法来填充所有的动作项。使用这个方法来实例化(inflate)一个定义了所有动作项的菜单(menu)资源。例如,下面这个菜单资源中定义了两个菜单项:
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>
然后,在妳的活动的onCreateOptionsMenu()方法中,将该菜单资源实例化到指定的Menu 对象中,以将每个菜单项添加到动作栏中:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// 实例化这些菜单项,以在动作栏中使用它们
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 属性时,这么做是必要的,因为,该属性在旧版设备的Android 框架中并 不存在。因此,妳必须针对由支持库所定义的所有属性都使用妳自己的命名空间作为前缀。
如果妳的菜单项中同时包含了文字标题和图标——即,拥有title和icon两个属性——那么,默认情况下,该动作项只显示图标。如果妳想要显示出文字标题的话,则,向showAsAction 属性中加入"withText"这个值。例如:
<item yourapp:showAsAction="ifRoom|withText" ... />
注意:"withText"这个值只是对于动作栏的一个建议,表示该动作项的文字标题应当显示出来。在可以做到的情况下,动作栏会显示该文字标题,但是,如果该动作项提供了图标并且动作栏已经挤满了,则动作栏可能不会显示出该文字标题。
即使妳不打算让文字标题与动作项一起出现,妳也应当坚持为每个动作项定义文字标题,原因如下:
•如果动作栏中已经没有空间能够容下该动作项了,那么,该菜单项会出现在溢出菜单中,而在那个菜单中,只会显示各个动作项的文字标题。
•那些为视力有障碍的用户提供辅助功能的屏幕阅读器,会朗读菜单项的文字标题。
•如果该动作项只显示图标的话,则,用户可以长按该动作项,以显示出一个提示文字(tool-tip),该提示文字的内容中就会显示出该动作项的文字标题。
图标是可选的,但是建议加上。对于图标设计方面的建议,请参考 Iconography 设计指南。妳还可以从 Downloads 页面上下载到一组标准动作栏图标(例如针对 搜索 (Search)或 忽略 (Discard)的图标)。
妳还可以使用"always"这个值来声明让某个动作项一直作为动作按钮显示出来。但是,妳不应当以这种手段来强制让某个动作项出现在动作栏中。如果妳这样做的话,在较窄的屏幕上,可能会引起布局问题。最好的处理方式就是,使用"ifRoom"这个值来要求让某个动作项出现在动作栏中,而同时也允许系统在空间不够的情况下将它移动到溢出菜单中。当然了,如果该动作项对应的是一个不可被折叠、必须一直显示以提供某个关键功能的动作视图的话,则,使用这个值可能是有必要的。
当用户按下某个动作项时,系统会调用妳的活动的onOptionsItemSelected()方法。利用这个方法中传入的MenuItem,妳可以调用getItemId()来识别出用户按的是哪个动作项。妳所调用的这个函数会返回一个唯一的编号(ID),对应着<item>标记中的id 属性,这样,妳就可以做出相应的动作了。例如:
@Override
public 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 类的onCreateOptionsMenu()回调函数来从片断(fragment)中实例化菜单项的,那么,当用户选择了其中某个菜单项时,系统会调用该片断的onOptionsItemSelected()回调函数。然而,活动本身仍然有机会先对该事件进行处理,因此,系统首先调用该活动中的onOptionsItemSelected()方法,然后才可能会去调用该片断中同名的回调函数。要想确保活动中的任何片断都有机会处理该回调函数的话,则,当妳不打算在活动中处理该菜单项点击事件时,一定要将该回调函数传递给超类以触发默认行为,而不是返回假(false)。
图3. 三个示例:带有标签栏的动作栏(左图);动作栏分离到底部(中图);禁用应用程序图标和标题(右图)。
分离的动作栏,提供了一个功能,当活动在一个较窄的屏幕(例如一个处于竖向模式的手机)上运行时,可以显示出一个位于屏幕底部的单独横条,其中显示出所有的动作项。
使用这种方式将动作项分离开来,可以确保,在一个较窄的屏幕上,仍然具有合理数量的空间,用来显示出所有的动作项,同时也让导航元素的标题元素在屏幕顶部有足够的空间显示。
要想在使用支持库时启用分离动作栏功能的话,妳必须做两件事:
1. 向每个<activity>元素或者<application>元素中加入uiOptions="splitActionBarWhenNarrow"。只有应用编程接口版本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>
使用分离动作栏功能,还可以在删除图标和标题的情况下将导航栏折叠到主动作栏中去(如图3中右图所示)。要想创造出这种效果,则,使用setDisplayShowHomeEnabled(false)和setDisplayShowTitleEnabled(false)来禁用掉动作栏图标和标题。
设计指南
图4. Gmail中的向上按钮。
将应用程序图标启用为向上按钮,使得用户可以根据不同屏幕窗口之间的层次关系来在妳的应用程序中导航。例如,屏幕A中显示了一个列表,选中其中一个条目就会切换到屏幕B,那么,屏幕B就应当包含向上按钮,该按钮会返回到屏幕A。
注意:向上导航功能与系统的后退键提供的后退导航功能是不同的。后退键是用于导航到用户按照时间顺序使用过的上一个屏幕。它通常是基于屏幕之间的临时关系的,而不是基于应用程序中的层次结构的(后者是向上导航功能的基础)。
要想将应用程序图标启用为向上按钮,则调用setDisplayHomeAsUpEnabled()。例如:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_details);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
...
}
现在,动作栏中的图标带有向上导航符号(如图4所示)。然而,在默认情况下它不会做任何事。为了指定当用户按下向上导航按钮时打开的目标活动,妳有两个选项:
•在清单文件中指定亲代活动。
当亲代活动永远不变时,这是最好的选项。在清单文件中声明了亲代活动之后,当用户按下向上导航按钮时,动作栏自动做出正确的动作。
从安卓4.1(应用编程接口版本16)开始,妳可在<activity>元素中使用parentActivityName 属性来声明亲代活动。
要想通过支持库来支持较旧版本的设备的话,则,同时加入一个<meta-data>元素,将亲代活动指定为 android.support.PARENT_ACTIVITY 的值。例如:
<application ... >
...
<!-- 主活动(没有亲代活动) -->
<activity
android:name="com.example.myfirstapp.MainActivity" ...>
...
</activity>
<!-- 主活动的一个子代活动-->
<activity
android:name="com.example.myfirstapp.DisplayMessageActivity"
android:label="@string/title_activity_display_message"
android:parentActivityName="com.example.myfirstapp.MainActivity" >
<!-- 亲代活动元数据(meta-data),用来支持应用编程接口版本7+ -->
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.myfirstapp.MainActivity" />
</activity>
</application>
当妳在清单文件中指定了亲代活动,并且通过setDisplayHomeAsUpEnabled()启用了向上导航按钮之后,就完事了,动作栏会自动完成向上导航动作。
•或者,覆盖妳的活动中的getSupportParentActivityIntent()和onCreateSupportNavigateUpTaskStack()方法。
如果亲代活动会因为用户如何到达当前屏幕而变的话,那么,这种方式就狠合适了。也就是说,如果用户可能通过多种路径到达当前屏幕,那么,向上导航按钮应当能够让用户沿着原路返回。
如果用户当前正在妳的应用程序中浏览(在妳的应用程序自身的任务中),那么,当用户按下向上导航按钮时,系统会调用getSupportParentActivityIntent()。如果此时应当打开的目标活动是取决于用户如何到达当前位置的,那么,妳应当覆盖这个方法,以返回能够启动对应的亲代活动的Intent对象。
如果妳的活动当前是运行在一个不属于妳的应用程序的任务当中,那么,当用户按下向上导航按钮时,系统会调用onCreateSupportNavigateUpTaskStack()。因此,妳必须使用这里所传入的TaskStackBuilder对象来构造一个适当的后退栈,以对用户的向上导航动作做出响应。
即使妳覆盖了getSupportParentActivityIntent()以指定当用户在妳的应用程序中浏览时的向上导航动作,妳仍然可以通过像之前的例子中那样在清单文件中声明“默认的”亲代活动来避免实现onCreateSupportNavigateUpTaskStack()方法。这样,默认的onCreateSupportNavigateUpTaskStack()实现中,就会基于清单文件中声明的亲代活动来合成一个后退栈。
注意:如果妳是使用了一系列的片断来构造妳的应用程序层次结构,而不是使用多个活动的话,那么,以上两个选项都不起作用。正确的做法是,要想在片断的层次关系中向上导航,妳应当覆盖onSupportNavigateUp()方法以进行适当的片断相关事务操作——通常是,使用popBackStack()来将当前片断从后退栈中弹出。
欲知更多关于向上导航的信息,则阅读 提供向上导航功能 。
图5. 一个动作栏,带有一个可折叠的SearchView。
动作视图,是出现在动作栏中的一种部件,用于代表一个动作按钮。动作视图,提供了一种手段,使得妳可以快速访问到一些功能强大的动作,而不用切换活动或片断,也不用替换掉动作栏。例如,妳有一个动作是用于搜索(Search)功能的,那么,妳可以添加一个动作视图,以在动作栏中嵌入一个SearchView 部件,如图5所示。
要想声明一个动作视图,则,使用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"值。这个值是可选的,它的意思是,声明要让该动作视图折叠为一个按钮。(在后续的 处理 可折叠的动作视图 小节中详细说明了这个行为。)
如果妳需要配置该动作视图(例如为它添加事件监听器),那么,妳可以在onCreateOptionsMenu()回调函数中做这件事。妳可以调用静态方法MenuItemCompat.getActionView(),并且传入对应的MenuItem,以获取到该动作视图对象。例如,使用以下代码,可以获取到上面那个例子中的搜索部件:
@Override
public 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);
}
对于应用编程接口版本11或更高的版本的情况
调用对应的MenuItem 的getActionView()方法,以获取动作视图:
menu.findItem(R.id.action_search).getActionView()
欲知更多关于如何使用搜索部件的信息,则阅读 创建 一个搜索界面 。
为了节省动作栏的空间,妳可以将妳的动作视图折叠到一个动作按钮中去。在处于折叠状态时,系统可能会将折叠之后得到的动作放置到动作溢出菜单中去,但是,当用户选中这个动作按钮时,该动作视图仍然会出现在动作栏中。妳可以向showAsAction 属性中加入"collapseActionView"这个值,以让妳的动作视图成为可折叠的,如上例的XML 所示。
因为系统会在用户选中该动作时将动作视图展开,所以,妳不需要在onOptionsItemSelected()回调函数中对该动作项作出响应。系统仍然会调用onOptionsItemSelected(),但是,如果妳返回真(true)(表示妳自己已经处理了该事件),那么,对应的动作视图不会展开。
另外,当用户点击向上导航按钮或后退键时,系统也会将妳的动作视图折叠起来。
如果妳需要根据动作视图是否可见来调整妳的活动中的内容的话,那么,妳可以接收到当该动作被展开和折叠时的回调函数,具体做法就是,定义一个OnActionExpandListener,并且将它传递给setOnActionExpandListener()。例如:
@Override
public 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; // 返回真以折叠该动作视图
}
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
// 展开时做某些处理
return true; // 返回真以展开该动作视图
}
});
}
图6. 一个动作栏,带有一个已展开的ShareActionProvider,显示出分享目标列表。
与动作视图类似,有一个动作提供者的概念,它能够用一个自定义的布局来替换掉某个动作按钮。但是,与动作视图不同的是,动作提供者会接管该动作的全部行为,并且,当它被按下时,会显示出一个子菜单。
要声明一个动作提供者,则,在菜单的<item>标记中,将actionProviderClass(☯:原文是actionViewClass)属性的值设置成某个ActionProvider 的完整类名。
妳可以继承ActionProvider 类,以创建妳自己的动作提供者,但是,安卓系统提供了一些已有的动作提供者,例如ShareActionProvider,它代表着一个“分享”动作,它会显示出一组可能用得上的应用程序列表,以便在动作栏中直接分享信息(如图6所示)。
因为每个ActionProvider类都定义了它自己的动作行为,所以,妳不需要在onOptionsItemSelected()方法中对该动作作出响应。当然,如果有必要的话,妳仍然可以在onOptionsItemSelected()方法中监听到对于该按钮的点击事件,以便同时作出另一个动作。但是,记住一定要返回假(false),这样,动作提供者就仍然能够接收到onPerformDefaultAction()回调函数,以做出应有的动作。
但是,如果该动作提供者提供了一个由动作组成的子菜单的话,则,当用户打开该列表或者选中其中某个子菜单项时,妳的活动不会收到对onOptionsItemSelected()的调用。
要想使用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()方法,调用MenuItemCompat.getActionProvider()并且传入与该动作提供者对应的MenuItem。再对所返回的ShareActionProvider 对象调用setShareIntent(),传入一个ACTION_SEND意图,其中附上适当的内容。
妳应当在onCreateOptionsMenu()中调用一次setShareIntent(),以将分享动作对象初始化,但是,因为用户的上下文可能会发生改变,所以,每当可分享的内容发生改变时妳都应当再次调用setShareIntent()以更新相应的意图。
例如:
private ShareActionProvider mShareActionProvider;
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_activity_actions, menu);
// 设置ShareActionProvider的默认分享意图
MenuItem shareItem = menu.findItem(R.id.action_share);
mShareActionProvider = (ShareActionProvider)
MenuItemCompat.getActionProvider(shareItem);
mShareActionProvider.setShareIntent(getDefaultIntent());
return super.onCreateOptionsMenu(menu);
}
/** 定义一个默认(虚设的)分享意图,用来初始化该动作提供者。
* 但是,一旦真正得知了要分享的内容,或者要用到意图中去的内容发生了改变,
* 妳必须再次调用mShareActionProvider.setShareIntent()以更新分享意图
*/
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都应当指定独自的一个历史文件,以维护它自己那一份历史记录。要想给ShareActionProvider 指定不同的历史文件的话,则调用setShareHistoryFileName()并且传入一个XML文件名(例如,"custom_share_history.xml")。
注意:尽管ShareActionProvider是按照使用频率来对分享目标进行排序的,但是,这个行为是可以扩展的,每个ShareActionProvider 子类都可以根据历史记录文件(如果用得上的话)来做出不同的行为和排序。
创建妳自己的动作提供者,好处是,可以在一个自包含的模块中重用及管理动态的动作项行为,而不是在妳的片断或活动的代码中处理动作项的变换和行为。上一小节中已经展示过了,安卓已经提供了一个用来做分享操作的ActionProvider 实现:ShareActionProvider。
要想创建妳自己的一个针对不同动作的动作提供者的话,只需简单地继承ActionProvider 类,再实现相 应的回调方法就行了。最重要的是,妳应当实现以下方法:
ActionProvider()
在构造函数中会传入应用程序的上下文(Context),妳应当将它记录到一个成员字段中,以在日后的回调方法中使用。
onCreateActionView(MenuItem)
妳在这里定义该动作项对应的动作视图。使用之前从构造函数中获取到的Context来实例化一个LayoutInflater,再用它来从一个XML 资源中实例化(inflate)妳的动作视图布局,然后,设置好事件监听器。例如:
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()的活动或片断,可以通过处理该动作项被选中( item-selected)的事件(并且返回真(true))来覆盖掉该动作提供者的默认行为(除非它使用了一个子菜单),在那种情况下,系统不会调用onPerformDefaultAction()。
若想观摩一下ActionProvider子类的实例,则参考 ActionBarSettingsActionProviderActivity 。
图7. 在较宽屏幕上,带有标签栏的动作栏。
设计指南
参考
图8. 在较窄屏幕上显示的标签栏。
在动作栏中带上标签栏,使得用户可以轻易地在妳的应用程序中不同的视图间切换、探索。由ActionBar 所提供的标签栏是一个理想的工具,因为它们会自动适应不同的屏幕尺寸。例如,当屏幕足够宽时,标签栏会出现在动作栏中,与那些动作按钮放置在一起(比如说在平板上就容易出现这种情况,如图7所示),而在一个较窄的屏幕上呢,它们会出现在一个单独的横条中(称作“层叠动作栏”,如图8所示)。在某些情况下,安卓系统甚至会将妳的那些标签栏条目显示成一个下拉列表,以确保以最佳状态适配到动作栏中。
要想加上这个功能的话,妳的布局中必须包含一个ViewGroup,其中放置了与各个标签对应的每个Fragment。确保让该ViewGroup拥有一个资源编号,这样,妳才能够在代码中引用到它并且相应地切换各个标签页的内容。或者,如果这些标签页的内容要填满整个活动的布局的话,则,妳的活动甚至都不需要有一个布局(妳甚至不需要调用setContentView())。这种情况下,妳可以将每个片断都放置在默认的根视图中,该视图可使用资源编号android.R.id.content来引用。
当妳决定了要在布局中的什么地方放置那些片断之后,就可以按照以下步骤来添加标签栏:
1.实现ActionBar.TabListener这个接口。这个接口提供了一些与标签页事件相关的回调函数,例如,用户点击了某个标签按钮,于是妳相应地切换标签页。
2.对于妳想要添加的每个标签页,实例化一个ActionBar.Tab并且调用setTabListener()以设置此处要使用的ActionBar.TabListener。另外,使用setText()设置它的标题(可选地,使用setIcon()设置一个图标)。
3.然后,对每个标签,调用addTab()以将它添加到动作栏中。
注意,ActionBar.TabListener中的那些回调函数不会指定哪个片断是与哪个标签相关联的,而只是告知是哪个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,用来实例化该片断
* @param tag 该片断的唯一标识标记
* @param clz 该片断的Class,用来实例化该片断
*/
public TabListener(Activity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
/* 以下是ActionBar.TabListener 的各个回调函数 */
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// 检查该片断是否已经被初始化了
if (mFragment == null) {
// 如果未初始化,则实例化一个,并且将它添加到活动中
mFragment = Fragment.instantiate(mActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
} else {
// 如果已经存在了,则简单地将它追加进来以便显示出来
ft.attach(mFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (mFragment != null) {
// 卸下这个片断,因为即将装上另一个片断
ft.detach(mFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// 用户选中了一个已经处于选中状态的标签页。通常什么都不用做。
}
}
警告:妳不能在这些回调函数中调用当前的片断事务的commit()方法——系统会为妳调用它,并且,如果妳自己调用该方法的话,系统可能会抛出一个异常。另外,妳不能将这些片断事务添加到后退栈中。
在这个例子中,这个监听器简单地在对应的标签页被选中之时将一个片断装入(attach())到活动的布局中——或者,如果该片断还未实例化的话,则创建该片断并且将它加入(add())到布局中(作为android.R.id.content 这个视图分组的一个子代对象)。而当对应的标签页被取消选中时,则将它卸下(detach())。
剩下的工作就是,创建各个ActionBar.Tab并且分别添加到ActionBar中。另外,妳必须调用setNavigationMode(NAVIGATION_MODE_TABS)以让标签栏变得可见。
例如,以下代码会使用之前定义的监听器来添加两个标签页:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 注意,这里没有使用setContentView(),因为,我们直接使用根视图
// android.R.id.content来作为每个片断的容器
// 设置动作栏,以显示标签栏
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);
}
当妳的活动被停止时,妳应当使用被保存的实例状态来记录当前被选中的标签页,这样,当用户再次切换到妳的应用程序时,妳就可以打开相应的标签页。到了该保存状态的时候,妳可以使用getSelectedNavigationIndex()来查询当前被选中的标签页。这个方法返回的是被选中的标签页的顺序位置。
警告:有一点狠重要,就是,要保存每个片断的状态,这样,当用户通过标签栏切换到其它的页面又切换回来之后,该页面看起来跟被切换出去之前是一样的。某些状态是默认就会保存的,但是妳可能需要手动保存那些自定义视图的状态。欲知更多关于如何保存片断的状态的信息,则参考 Fragments应用编程接口指南。
注意:以上代码中对于ActionBar.TabListener 的实现,只是众多可能的实现方式中的一种。另外一种流行的实现方式就是使用ViewPager来管理这些片断,这样,用户就可以使用滑动手势来切换标签页了。在这种实现方式下,妳只需要在onTabSelected()回调函数中向ViewPager 告知当前的标签页编号。欲知更多信息,则阅读 创建带滑动切换视图的 标签 页 。
图9. 动作栏中的一个下拉导航列表。
作为妳的活动的另一个导航(或过滤)模式,动作栏还提供了一个内置的下拉列表(也可称作“旋转器”("spinner"))。例如,下拉列表可以提供多种不同据以对活动中的内容进行排序的模式。
在这种情况下使用下拉列表是狠有用的:改变内容是狠重要的事,但却不是经常发生的事。如果对内容的切换是频繁发生的事,那么,妳应当使用导航标签栏。
启用下拉导航的基本过程是:
1.创建一个SpinnerAdapter,用来提供下拉列表中可选中的条目的列表,以及在绘制列表中的各个条目时使用的布局。
2.实现ActionBar.OnNavigationListener,以定义好,当用户选中列表中的某个条目时,发生的行为。
3. 在妳的活动的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)、字符串数组的资源编号和用于每个列表项的布局。
在某个资源文件中定义这样一个字符串数组:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="action_list">
<item>Mercury</item>
<item>Venus</item>
<item>Earth</item>
</string-array>
</resources>
createFromResource()中返回的ArrayAdapter 已经是完整功能的了,妳可以将它传递给setListNavigationCallbacks()(在以上的第4步中做这件事)。当然,在妳做这件事之前,需要创建一个OnNavigationListener。
妳要在 ActionBar.OnNavigationListener 的实现中处理当用户从下拉列表中选择一个条目时对应的片断的改变或对于活动的其它方面的修改。在该监听器中只有一个回调函数需要实现:onNavigationItemSelected()。
在onNavigationItemSelected()方法中能够得到当前条目在列表中的位置以及由SpinnerAdapter 提供的一个唯一的条目编号。
以下是一个示例,其中实例化了一个匿名的OnNavigationListener 实现,在该实现中,会将一个Fragment插入到一个资源编号为 R.id.fragment_container 的布局容器中:
mOnNavigationListener = new OnNavigationListener() {
// 获取由该下拉列表的ArrayAdapter所提供的同一个字符串数组
String[] strings = getResources().getStringArray(R.array.action_list);
@Override
public boolean onNavigationItemSelected(int position, long itemId) {
// 从我们自己的Fragment 类中创建新的片断
ListContentFragment newFragment = new ListContentFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
// 使用这个片断来替换掉片断容器中已有的东西,
// 并且给该片断设置一个标记名字,即为所选中的位置所对应的字符串
ft.replace(R.id.fragment_container, newFragment, strings[position]);
// 应用这些修改
ft.commit();
return true;
}
};
这个OnNavigationListener 实例的功能是完整的,现在妳可以调用setListNavigationCallbacks()(前面说的第4步),传入之前那个ArrayAdapter 和这个OnNavigationListener 了。
在这个示例中,当用户从下拉列表中选中一个条目时,便会有一个片断被添加到布局中(替换掉R.id.fragment_container 视图中的当前片断)。该片断会获得到一个标记,该标记用于唯一地标识它,其内容与下拉列表中用来标记该片断的字符串相同。
以下是这个示例中用来定义各个片断的ListContentFragment类:
public class ListContentFragment extends Fragment {
private String mText;
@Override
public void onAttach(Activity activity) {
// 这是我们会收到的第一个回调函数;这里,我们可以将之前在片断事务中指定的标记
// 设置为此片断的文字
super.onAttach(activity);
mText = getTag();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// 这个方法的作用是定义该片断的布局;
// 我们在这里只是创建一个TextView,并且将它的文字内容设置为片断的标记字符串
TextView text = new TextView(getActivity());
text.setText(mText);
return text;
}
}
如果妳想以某种视觉设计来表达妳的应用程序的品牌的话,正好,动作栏允许妳对它的外观的每个细节都做定制,包括动作栏的颜色、文字颜色、按钮样式和更多的细节。要做到这一点,妳需要使用安卓的 样式和主题 框架来利用特殊的样式属性修改动作栏的样式。
警告:对于妳提供的所有背景图片,都要确保使用 点9 (Nine-Patch )图片,以允许缩放。所用的点9图片应当小于40dp高和30dp宽。
actionBarStyle
指定一个样式资源,其中定义了动作栏的各个样式属性。
针对这个东西的默认样式是Widget.AppCompat.ActionBar,妳也应当使用该样式来作为亲代样式。
支持以下样式属性:
background
定义一个用于动作栏背景的绘图资源。
backgroundStacked
定义一个用于被堆叠的动作栏(标签栏)背景的绘图资源。
backgroundSplit
定义一个用于被分离的动作栏背景的绘图资源。
actionButtonStyle
定义一个用于动作按钮的样式资源。
对于这个东西的默认样式是Widget.AppCompat.ActionButton,妳也应当使用该样式作为亲代样式。
actionOverflowButtonStyle
定义一个用于溢出菜单中的动作项的样式资源。
对于这个东西的默认样式是Widget.AppCompat.ActionButton.Overflow,妳也应当使用该样式作为亲代样式。
displayOptions
定义一个或多个动作栏显示选项,例如,是否使用应用程序图标,是否显示活动的标题,是否启用向上导航动作。参考 displayOptions 以了解所有可能的值。
divider
定义一个用于显示动作项之间的分隔符的绘图资源。
titleTextStyle
定义一个用于动作栏标题文字的样式资源。
对于这个东西的默认样式是TextAppearance.AppCompat.Widget.ActionBar.Title,妳也应当使用该样式作为亲代样式。
windowActionBarOverlay
声明,动作栏是否应当覆盖活动的布局的一部分,而不是与活动的布局平等地占用屏幕空间(例如,Gallery这个应用程序就使用了覆盖模式)。默认值为假(false)。
一般情况下,动作栏会独自占用屏幕空间,而妳的活动的布局就占用剩余的空间。当动作栏处于覆盖模式时,妳的活动的布局就会占满所有可用的空间,而系统会将动作栏绘制在其上一层。如果妳希望自己应用程序中的内容在动作栏被隐藏和显示时保持固定的尺寸和位置的话,那么覆盖模式就是狠有用的。妳也可以单纯将它用作一个视觉效果,因为,妳可以给动作栏设置一个半透明的背景,这样,用户仍然可以看到妳的活动布局中位于动作栏之后的某些内容。
注意: Holo系列的主题在默认情况下会给动作栏绘制一个半透明的背景。但是,妳可以按照自己的样式来修改它,并且,在不同设备上的DeviceDefault主题可能会默认使用不透明的背景。
启用了覆盖模式的情况下,妳的活动的布局并不知道在其上被绘制了一个动作栏。因此,妳需要注意,不能在会被动作栏覆盖的区域放置任何重要的信息及界面组件。如果有必要的话,妳可以在XML 布局文件中引用到当前平台上actionBarSize 的值,以确定出动作栏的高度。例如:
<SomeView
...
android:layout_marginTop="?android:attr/actionBarSize" />
妳还可以在运行时使用getHeight()来获取到动作栏的高度。这个方法反映的是在它被调用时动作栏的高度,如果是在活动的生命周期中较早阶段的方法中调用的话,可能不会包含堆叠的动作栏(因为导航标签栏的缘故)的调试。欲知如何在运行时确定整体的高度,包括堆叠的动作栏的高度,则参考 Honeycomb Gallery 示例应用程序中的 TitlesFragment 类。
actionButtonStyle
定义一个用于动作项按钮的样式资源。
这个样式的默认值是Widget.AppCompat.ActionButton,妳也应当使用它来作为亲代样式。
actionBarItemBackground
定义一个用于绘制各个动作项的背景的绘图资源。这个值应当是一个 状态列表绘图 集 ,用来表示不同的选中状态。
itemBackground
定义一个用于绘制动作溢出菜单中各个菜单项的背景的绘图资源。这个值应当是一个 状态列表绘图 集 ,用来表示不同的选中状态。
actionBarDivider
定义一个用来表示相信动作项之间的分隔符的绘图资源。
actionMenuTextColor
定义那些会现出在动作项中的文字的颜色。
actionMenuTextAppearance
为那些出现在动作项中的文字定义一个样式资源。
actionBarWidgetTheme
为那些被实例化到动作栏中去成为动作视图的部件定义一个主题资源。
actionBarTabStyle
为那些位于动作栏中的标签定义一个样式资源。
这个东西的默认样式是Widget.AppCompat.ActionBar.TabView,妳也应当使用它来作为亲代样式。
actionBarTabBarStyle
为那个位于导航标签栏下方的窄窄的横条定义一个样式资源。
这个东西的默认样式是Widget.AppCompat.ActionBar.TabBar,妳也应当使用它来作为亲代样式。
actionBarTabTextStyle
为那些出现在导航标签栏中的文字定义一个样式资源。
这个东西的默认样式是Widget.AppCompat.ActionBar.TabText,妳也应当使用它来作为亲代样式。
actionDropDownStyle
为下拉导航部件定义一个样式(例如背景和文字样式)。
这个东西的默认样式是Widget.AppCompat.Spinner.DropDown.ActionBar,妳也应当使用它来作为亲 代样式。
以下是一个示例,它为一个活动定义了一个自定义的主题CustomActivityTheme,其中包含了多个样式,用来对动作栏进行自定义。
注意,对于每个动作栏样式属性,都有两个版本的值。第一个版本的属性名字中带有android:前缀,以支持那些应用编程接口版本11及更高的版本,那些版本的框架中已经包含了这些属性。第二个版本中不包含android:前缀,是针对开发平台的旧版本来使用的,在那些版本中,系统会使用支持库中的样式属性。两个版本的效果是相同的。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 这个主题会被应用到整个应用程序或当前活动 -->
<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 android:theme="@style/CustomActionBarTheme" ... />
警告:确保每个主题和样式的<style>标记中都要声明一个亲代主题,所有没在妳的主题中显式声明的样式都会从该亲代主题中继承下来。在修改动作样的过程中,使用一个亲代主题是狠重要的事,这样,妳可以简单地将那些妳想要修改的动作栏样式覆盖掉,而不用把那些妳想要保持原样的样式重写一遍(例如动作项中的文字尺寸或间距)。
欲知更多关于如何在妳的应用程序中使用样式和主题的信息,则阅读 样式 及主题 。
闫凤姣
Your opinionsHxLauncher: Launch Android applications by voice commands