Action Bar是一个用来标明用户在app中所处的位置,呈现用户action和导航模式的窗口功能。Action bar可以让系统优雅地在不同屏幕配置上进行适配,为你的用户提供跨应用的统一使用体验。
图1:一个包括【1】app图标,【2】两个action项,以及【3】overflow的Action bar
对于API level 11及以上
所有使用
Theme.Holo
主题(或其继承者)的activity都会包含action bar,当
targetSdkVersion
或
者
minSdkVersion
属性设为“11”或以上时,默认主题就是它。如果不需要某个activity显示action bar,可以将
它的主题设置为
Theme.Holo.NoActionBar
。
图2:带有3个action按钮和overflow按钮的action bar
图3. 模拟图,带有标签的action bar(左);分离式action bar(中);不显示图标和标题(右)
图4. Gmail中的Up按钮
图5. 带有可收起的
SearchView的action bar
图6. 一个带有
ShareActionProvider的action bar,显示了分享目标
图7. 宽屏上的action bar tab
图8. 窄屏上的tab
图9. 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以下:
如只支持API level 11及以上:import android.support.v7.app.ActionBar
注意:如果想查找关于用来显示情境相关action项的情境相关action bar,见 Menu指南。import android.app.ActionBar
添加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运行在Android2.1(API level 7)及以上时就会有action bar了。<activity android:theme="@style/Theme.AppCompat.Light" ... >
对于API level 11及以上
移除Action Bar
在程序运行时,可以调用
hide()隐藏action bar,如下:
对于API level 11及以上ActionBar actionBar =getSupportActionBar()
; actionBar.hide();
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菜单。
Activity启动时,系统通过调用activity的
onCreateOptionsMenu()来生成action项,用这个方法来加载定义action项的
菜单资源。下面是个菜单资源的例子:
res/menu/main_activity_actions.xml
然后在activity的 onCreateOptionsMenu()方法中,把菜单资源加载到参数传入的 Menu对象,这样每一项就都被添加到了action bar:<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>
要让action项直接显示为action按钮,须要在<item>标签中加入showAsAction="ifRoom",如下:@Override public 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 bar上的空间不够,该项就会被放进overflow。<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>
使用support library中的XML属性
要注意到上例中的showAsAction属性用到了<menu>标签中的一个自定义名字空间,使用support library中定义的任何
XML属性都必须这样做,因为这些属性在老设备的Android framework中并不存在,所以必须用自己的名字空间来作为
support library的所有属性的前缀。
如果你的菜单项既有标题又有图标——通过title和icon属性指定——那么默认会只显示图标,如果要显示文字标题,就须要在showAsAction属性里添加"withText",如下:
注意: "withText"只是提示action bar应该显示文字标题,action bar会在可能的情况下显示文字,但如果该项有图标而 且action bar没有空间,那么文字还是有可能不显示。<item yourapp:showAsAction="ifRoom|withText" ... />
但对于每个菜单项都应该定义title,哪怕你不显示它,原因如下:
如果action bar没有空间显示action项,那么这些项就会出现在overflow中,而在overflow中只会显示文字标题;
视力障碍用户使用的屏幕阅读功能会读出菜单项的文字标题;
如果action项只显示为图标,用户可以长按图标来显示标题作为提示。
可以用"always"来声明action项永远显示为action按钮,然而,你
不应该用这种方式让一个action项始终显示为action按钮,否则在较窄的屏幕上可能造成布局问题。最好还是用"ifRoom"请求让它显示为action按钮,同时也允许系统在空间不足时把它放入overflow。需要用到这个值的情况是当这个项包含一个无法折叠,并且要永远可见,提供关键功能的
action view。
处理action项的点击事件
当用户点按一个action时,系统调用activity的
onOptionsItemSelected()方法,用传入这个方法的
MenuItem对象调用
getItemId()可以辨别是哪个action,方法返回<item>标签的id属性设置的唯一ID,然后就可以根据ID执行相应的action。如下:
注:如果你通过 Fragment类的 onCreateOptionsMenu()回调来从fragment加载菜单项,用户选择action项时系 统会调用该fragment的 onOptionsItemSelected()。不过,activity会先得到一个处理这个事件的机会,系统会在 调用fragment的同方法之前先调用activity的 onOptionsItemSelected()。为确保activity的所有fragment都有处 理事件的机会,在自己处理完事件后不要直接返回false,而应该总是默认把调用传递给父类。@Override public 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); } }
使用分离式action bar
分离式action bar可以在屏幕比较窄的情况下(比如竖屏的设备)提供一个位于屏幕底部的action bar来放置action项。
这种把action item分离出来的方式确保了在窄屏上能有足够的空间来显示action项,同时还在顶部留出了用于页面导航和标题显示的空间。
在support library中启用分离式action bar,需要两个步骤:
1. 在manifest文件中为每个
activity或者为
application添加
uiOptions="splitActionBarWhenNarrow",这个属性只有API level 14及以上才能解析(老版本上会直接忽略)。
例子:
分离式action bar还允许 导航标签收缩到主action bar,前提是不显示图标和标题(如图3中右侧所示),要达到这个效果,必须用 setDisplayShowHomeEnabled(false)和 setDisplayShowTitleEnabled(false)来禁用图标和标题。<manifest ...> <activity uiOptions="splitActionBarWhenNarrow" ... > <meta-data android:name="android.support.UI_OPTIONS" android:value="splitActionBarWhenNarrow" /> </activity> </manifest>
用App图标返回上层界面
把app图标用作Up按钮使用户能够在层级关系的界面中进行导航,比如,A界面显示一个list,点击其中一项进入界面B,这时界面B就应该包含一个能够返回A界面的Up按钮。
注:Up导航和系统的back导航有区别,back按钮用来按用户最近访问的事件顺序进行逆向回退,基本上基于界面间
的临时关系,而非app的层级结构(up导航的基础)。
要把app图标用作Up按钮,须调用
setDisplayHomeAsUpEnabled(),例子:
这样action bar的图标旁就多了一个Up括号(如图4),不过,它默认是不会做任何事情的,要指定用户点击时打开的activity,你有两个选择:@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_details); ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); ... }
在manifest文件中指定父activity
如果
父activity永远是同一个时这是最好的选择。通过在manifest中声明哪个是父activity,用户点击Up按钮时,
action bar会自动执行正确的action。
这样指定后,再用 setDisplayHomeAsUpEnabled() 来启用Up按钮,action bar就已经能实现返回上级的功能了。<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>
或者,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。
要声明一个action view,使用
actionLayout
或者
actionViewClass
属性分别来指定一个布局资源或要使用的widget类。下面是添加
SearchView widget的例子:
注意 showAsAction 属性中还多了 "collapseActionView" 这样一个值,这是可选的,声明了该action view可以收起为一个按钮。(这个行为将在下一节处理可收起的action view中详细解释)<?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>
如需配置action view(如增加事件监听),可在
onCreateOptionsMenu()回调中进行。你可以调用static方法
MenuItemCompat.getActionView()来获得action view对象,并传入相应的
MenuItem。下面是获取这个搜索widget的例子:
对于API level 11及以上@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); // Configure the search info and add any event listeners ... return super.onCreateOptionsMenu(menu); }
要了解更多搜索widget的信息,见 Creating a Search Interface 。menu.findItem(R.id.action_search).getActionView()
处理可收起的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()来接收展开收起时的回调。例子:
@Override public 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的所有行为,而且点击时可以显示一个子菜单。
你可以继承
ActionProvider类来创建自己的action provider,不过Android提供了一些预先建好的action provider,比如
ShareActionProvider,它简化了“分享”action,它会在action bar上展示一个所有可供直接分享的app列表(如图6)。
因为每个
ActionProvider都定义了自己的action行为,所以无需在
onOptionsItemSelected()方法中监听action。当然,如果需要,你还是可以在该方法中监听点击事件,以便你可以同时执行别的action,但要一定要返回
false
,确保action provider还能接收到
onPerformDefaultAction()回调来执行既定的action。
不过,如果action provider提供自己的action子菜单,用户打开子菜单或选中子菜单项时你的activity是不会收到
onOptionsItemSelected()调用的。
使用ShareActionProvider
要使用
ShareActionProvider添加“分享”action,须用
ShareActionProvider
为
<item>
标签定义一个
actionProviderClass
属性,例子:
这样这个action provider就接管了这个action项,包括它的行为和外观,但是标题仍然是必须的,以供它在overflow中时使用。<?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(),传入拥有action provider的
MenuItem,然后在返回的
ShareActionProvider对象上调用
setShareIntent(),传入一个附有相应内容的
ACTION_SEND intent。
你应该在
onCreateOptionsMenu()时调用
setShareIntent()来初始化分享action,但由于用户情境可能会改变,你必须在可分享内容变化时再次调用
setShareIntent()来更新intent。
例子:
这样 ShareActionProvider就会处理该action项的所有用户行为,你也 无需调用 onOptionsItemSelected() 处理点击事件。private ShareActionProvider mShareActionProvider; @Override public 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 bar上。这个排序信息默认会存储在一个私有文件中,文件名由
DEFAULT_SHARE_HISTORY_FILE_NAME指定,如果你只为一种action使用
ShareActionProvider或它的一个子类,那你应该继续使用默认的历史文件,不用做任何处理。但如果要为多个不同含义的action使用,那么每个
ShareActionProvider都应该指定自己的历史文件,来维护自己的历史记录。要为
ShareActionProvider指定不同的历史文件,要调用
setShareHistoryFileName()并提供一个XML文件名(如,
"custom_share_history.xml"
)。
创建自定义Action Provider
创建你自己的action provider允许你以独立模块的方式重用和管理动态action项的行为,而不用在你的fragment或activity代码中来处理action行为。如前所述,Android已经为分享action提供了一个
ActionProvider的实现:
ShareActionProvider。
要为其它的action创建你自己的action provider,只需继承
ActionProvider类并实现相应的回调方法,其中最主要的几个:
此构造方法要传入application的
Context,你应该把它保存在成员field中,以便在其它回调方法中使用。
在这里为该action项定义action view,用构造方法中获得的
Context来实例化一个
LayoutInflater,并从XML资源中加载你的action view布局,然后再设置事件监听器。例子:
onPerformDefaultAction()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; }
当action overflow中的菜单项被选中时系统会调用此方法,action provider应该为该菜单项执行一个默认action。
但是,如果你的action provider通过
onPrepareSubMenu()
提供子菜单,那么即使action provider放置在overflow中,子菜单也一样会弹出。这样一来,有子菜单时
onPerformDefaultAction()就不会调用。
注:实现
onOptionsItemSelected()的
Activity或fragment可以通过处理选中事件(并返回
true
)来override action provider的默认行为(除非它使用了子菜单),这种情况下,系统不会调用
onPerformDefaultAction()。
添加导航tab
Action bar上的tab使用户更方便地在app中的不同视图间浏览切换,
ActionBar提供的tab可以理想地适应不同屏幕尺寸。比如,当屏幕足够宽时,tab和action按钮在action bar上一字排开(例如在平板上,如图7),而当屏幕比较窄时,这些tab就会出现在一个单独的bar(称为stacked action bar,如图8)上。某些情况下,Android系统为保证action bar的显示效果,会把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实例:
警告:一定不能在这三个回调中为fragment转换调用 commit()——系统会自动为你调用,如果你自己再调可能会抛 出异常,另外,也 不能将这些fragment转换加入back stack。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 theActionBar.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. } }
在上例中,当相应的tab被选中时,listener只是把fragment附到(
attach()
)activity布局中——或者如果fragment还没有实例化,就创建一个fragment并添加(
add()
)到布局中(作为
android.R.id.content
的子视图)。当tab不被选择时,则将fragment分离(
detach()
)。
下面的代码添加了两个tab,它们都使用了上面定义的listener:
如果activity进入了stop状态,你应该用 saved instance state来保持当前选中的tab,这样用户回来时就能恢复打开相应的tab。该保存状态时,可以用 getSelectedNavigationIndex()来获知当前tab,该方法返回选中tab的索引。@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 = 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); }
警告:保存每个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。
启用下拉导航的基本步骤:
1. 创建一个提供下拉菜单选项内容的
SpinnerAdapter,和用来绘制每个菜单项的布局文件。
2. 实现
ActionBar.OnNavigationListener来定义用户选中菜单项时的行为。
3. 在activity的
onCreate()方法中,调用
setNavigationMode(NAVIGATION_MODE_LIST)来启用action bar的下拉菜单。
4. 用
setListNavigationCallbacks()指定下拉菜单的回调方法,例如:
这个方法需要 SpinnerAdapter和 ActionBar.OnNavigationListener作为参数。actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationCallback);
步骤虽不多,但实现
SpinnerAdapter
和
ActionBar.OnNavigationListener是其中最花功夫的。有许多为你的下拉导航实现功能的方法,而且实现各种
SpinnerAdapter的方法超出了本文的范畴(参考
SpinnerAdapter类了解更多信息
),不过为让你尽快上手,这里还是提供了一个
SpinnerAdapter
和
ActionBar.OnNavigationListener的例子。
SpinnerAdapter和OnNavigationListener的例子
SpinnerAdapter是为类似action bar中的下拉菜单这样的spinner widget提供数据的adapter,它是个接口,你可以直接实现它,但Android已经提供了一些现成的有用实现供你继承使用,比如
ArrayAdapter和
SimpleCursorAdapter。下例是用
ArrayAdapter的实现简单地创建一个
SpinnerAdapter,数据源是个字符串数组:
createFromResource()方法需要3个参数:应用 Context,字符串数组的资源ID,以及列表项的布局文件。SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.action_list, android.R.layout.simple_spinner_dropdown_item);
资源文件中定义的
字符串数组如下:
createFromResource()返回的 ArrayAdapter是可以传入 setListNavigationCallbacks()直接使用的(上面的第4步),尽管如此,在这之前你还是要先创建 OnNavigationListener。<?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>
你的
ActionBar.OnNavigationListener实现是在用户选中下拉列表项时用来处理fragment切换或其它针对activity的操作的,该listener中只有一个回调方法需要实现:
onNavigationItemSelected()。
onNavigationItemSelected()会收到list项在list中的位置,还有
SpinnerAdapter提供的唯一ID。
这就成了个完整的 OnNavigationListener,你可以调用 setListNavigationCallbacks() (步骤4),传入 ArrayAdapter和这个 OnNavigationListener。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; } };
本例中,用户选中下拉菜单项时,布局中就会加入一个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宽。
总体外观
指定为action bar定义各种风格的风格资源文件
默认为
Widget.AppCompat.ActionBar,你的自定义风格应该以它为父风格
支持的各种风格如下:
定义action bar背景的图片资源
定义stacked action bar(tab标签)的图片资源
定义
分离式action bar的图片资源
为action按钮定义风格资源
默认为
Widget.AppCompat.ActionButton,
你的自定义风格应该以它为父风格
定义overflow action项的风格资源
默认为
Widget.AppCompat.ActionButton.Overflow,
你的自定义风格应该以它为父风格
定义一个或多个action bar显示选项,比如是否使用app logo,是否显示activity标题,以及是否使用Up
action。
displayOptions中列出了所有可以使用的值。
定义action项直接的分隔图片资源
为action bar标题定义风格资源
默认为
TextAppearance.AppCompat.Widget.ActionBar.Title,
你的自定义风格应该以它为父风格
声明action bar是覆盖activity布局还是将activity布局下移(例如,Gallery app使用了覆盖模式)。默认值
为
false
。
通常,action bar需要有自己的屏幕空间,activity的布局则填满剩余的空间。Action bar为覆盖模式时,activity 布局会使用所有的屏幕空间,系统会把action bar绘制在其上,如果你想让自己的内容在action bar显示和隐藏时保 持同样的大小和位置,那么覆盖模式会很有用。你也可能单纯为其视觉效果而使用它,因为你可以给action bar一个 半透明的背景,这样用户透过action bar也可以看到下面的内容。
开启覆盖模式时,你的activity布局并不知道action bar位于其上,因此在action bar覆盖区域不能放置重要信息或
UI组件,要想合理适配,你可以引用
actionBarSize的系统值来决定视图上方应该留出多少空间,例如:
也可以在运行时用 getHeight() 获取action bar的高度,这个方法返回调用时的action bar高度,在activity生命周 期的早期方法中调用时返回的高度可能不包括stacked action bar(由于navigation tabs)。要得到运行时action bar包括stacked action bar的总高度,见例子app Honeycomb Gallery中的 TitlesFragment。<SomeView ... android:layout_marginTop="?android:attr/actionBarSize" />
Action项
定义action按钮的风格资源
默认为
Widget.AppCompat.ActionButton,
你的自定义风格应该以它为父风格
定义每个action项的背景资源drawable,应为一个可以指示不同选择状态的
state-list drawable
定义每个overflow项的背景,
应为一个可以指示不同选择状态的
state-list drawable
定义action项之间分隔的drawable资源
定义action项文字的颜色
定义action项文字的外观风格资源
定义
action view widgets的主题资源
Navigation tabs
定义action bar的tab标签的风格资源
默认为
Widget.AppCompat.ActionBar.TabView,
你的自定义风格应该以它为父风格
定义navigation tab下方细条的风格资源
默认为
Widget.AppCompat.ActionBar.TabBar,
你的自定义风格应该以它为父风格
定义navigation tab中的文字风格资源
默认为
Widget.AppCompat.ActionBar.TabText,
你的自定义风格应该以它为父风格
下拉列表
定义下拉导航的风格(如背景和文字风格
)
Theme例子
这是个为activity定义自定义主题的例子,
CustomActivityTheme
,包含了几个用来自定义action bar的风格。
注意,每个action bar风格有两个版本,第一个属性名前包含了
android:
前缀,以支持framework中已包含这些属性的API level 11及以上版本;第二个版本没有
android:
前缀,针对使用support library中风格属性的老版本,效果是一样的。
在manifest文件中,可以为整个app指定theme:<?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>
或者为单个activity指定:<application android:theme="@style/CustomActionBarTheme" ... />
注意:确保每个theme和风格在<activity android:theme="@style/CustomActionBarTheme" ... />
<style>
标签中声明其父theme,这样它们会从父theme中继承所有未显式声明风格。修改action bar时,使用父theme很重要,这样你可以不用重新实现所有的风格属性(比如文字大小或action项的间距),只需override你要改变的风格。
更多关于app中风格和theme资源的信息,请阅读
Styles and Themes
原文:Action Bar