从零学Android(十一)、ActionBar基础知识

时间:2022-04-18 03:28:19

ActionBar是一种标识了用户的当前位置,并且提供用户行为及导航模式的窗口功能。由于系统对于ActionBar在不同屏幕大小下都做了很好的支持,所以使用ActionBar可以给用户提供一个统一的体验。

如上图所示,ActionBar提供了一些主要功能:

【1】将一些重要的行为动作(如:搜索)放在一个显而易见的位置上。

【2】支持统一的导航模式和视图的切换。

【3】通过给那些较少使用的行为动作提供一个行为溢出器(action overflow)减少行为干扰。

【4】提供了一个专门的区域,用来标识我们的APP

从零学Android(十一)、ActionBar基础知识

如上图所示,ActionBar一般分为4个功能区域:

1.App icon(应用图标区域)

应用图标是我们应用的一个标识。注意:如果用户当前不是处于最上层的页面(如首页),那么我们就需要展示最左侧的导航箭头,以便用户能返回到上一级页面。

2.View control(视图控制区域)

如果你的app在不同的视图中展示数据(比如,通过筛选状态展示数据),这个区域就是用来让用户切换视图的。如果你的app没有这个需求,那么这部分可以展示一个非交互的内容,比如一个页面标题等。

3.Action buttons(动作按钮)

显示一些最重要的动作按钮。其它一些不适合放在ActionBar上的动作都被放置到行为溢出器(action overflow)中。长按显示动作的名字。

4.行为溢出器(action overflow)

将那些不常用的动作行为都放置到这个溢出器内。


ActionBar是在Android 3.0(API level 11)时被添加的,但是Android 2.1(API level 7)及以上版本也可以通过Support Library支持包去使用。这里我们主要去学习低版本中的ActionBar,而API level 11以上的版本只是引用包的不同而已,如果App的最低版本低于Api level 11,需要使用import android.support.v7.app.ActionBar。如果App最低版本为11或者以上,则可以直接使用import android.app.ActionBar


1. 添加ActionBar

在上面我们有提到,这次我们学习的是Support Library支持包中的ActionBar,所以在我们学习添加一个ActionBar之前,我们需要给我们的App建立appcompat v7支持包。(Android Studio会帮我们默认引用,Eclipse则需要自己去设置,设置方式点击这里

当你建立好appcompat v7支持包后,就可以开始添加ActionBar了:

【1】创建我们的Activity,并且继承自ActionBarActivity

【2】使用Theme.AppCompat下面的一个主题包,如:

<activity android:theme="@style/Theme.AppCompat.Light" ... >
这时,在API level大于等于7的机器上,我们的Activity就包含了ActionBar了。

注意:在API level 11或以上的系统上,即targetSdkVersionminSdkVersion属性设置为11或者以上时,Theme.Holo是默认主题,这个时所有的Activity都包含有ActionBar,如果你某个Activity不希望包含ActionBar,你可以设置这个Activity的主题为Theme.Holo.NoActionBar

1.1 移除ActionBar

你可以在运行时通过调用hide()方法动态去隐藏ActionBar。如:

ActionBar actionBar = getSupportActionBar();//在API>=11时,使用getActionBar()
actionBar.hide();
当ActionBar被隐藏之后,系统会调整我们的布局,以便全屏显示。你可以通过调用 show()方法重新显示ActionBar。

注意:隐藏和显示ActionBar都会导致系统重新布局我们的Activity。所以如果你需要频繁地去隐藏和显示ActionBar,你可能需要启用"遮盖模式"(overlay mode)。覆盖模式会将ActionBar绘制在Activity布局的前端,且覆盖Activity布局的上方。这个时候,当ActionBar被显示和隐藏的时候,我们的Activity只需要去控制marginTop。为了启用遮盖模式,我们需要创建一个自定义的主题,并且设置windowActionBarOverlay属性为true

1.2 使用Logo替代app icon

默认情况下,系统会使用我们的应用图标作为ActionBar中的icon,也可以在<application><activity>标签中指定icon属性。同时,你也可以指定logo属性,然后ActionBar就会使用这个logo而不是icon。

一般logo会比icon要大,但是logo不应该包含一些不必要的文字。logo是一个用户品牌,而icon则是一个符合应用icon必须为正方形的logo修改版本。(比如圆形的logo,方形的icon)。


2. 添加行为动作(Action Items)

ActionBar可以让用户能直接使用那些和当前App上下文关联的最重要的行为动作。那些直接以icon或文本的形式出现在ActionBar中的视图称为动作按钮(Action Buttons)。一些不适合放在ActionBar上或不重要的Action都被放置到了action overflow中。用户可以通过点击右边的overflow按钮(有菜单按钮也可以点击菜单按钮)来展开这个Action列表。

当Activity启动的时候,系统会调用Activity的onCreateOptionsMenu()方法去填充ActionBar的Action items。使用这个方法可以加载一个定义了所有的Action items的菜单资源。比如说:

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中,添加每一个item到ActionBar中:
@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 item以Action Button的形式直接出现在ActionBar上,需要在 <item>标签下包含 showAsAction="ifRoom"属性。
<menu xmlns:android="http://schemas.android.com/apk/res/android"
<strong>xmlns:yourapp="http://schemas.android.com/apk/res-auto"</strong> >
<item android:id="@+id/action_search"
android:icon="@drawable/ic_action_search"
android:title="@string/action_search"
<strong>yourapp:showAsAction="ifRoom"</strong> />
...
</menu>

如果ActionBar上没有足够的空间给Action item显示,它就会出现在Action overflow中。

注意:在上面的例子中,showAsAction属性有引用到定义在<menu>标签中的名称空间。当使用定义在支持包中的XML属性时,这个是必须的,因为这些属性在Android早期的版本是不存在的。所以你必须使用你自己的名称空间作为定义在支持包中的XML属性的字头。

如果你给menu item提供了一个标题和icon,即设置了title属性和icon属性,那么Action item会默认只显示icon。如果你希望标题,那么你可以在showAsAction属性中添加"withText"取值,如:

<item yourapp:showAsAction="ifRoom|withText" ... />
注意: "withText"是给ActionBar的一个暗示,表示希望显示title文字。ActionBar会尽可能地去显示这个标题,但是如果icon可用且ActionBar没有足够的空间,那么这个标题也可能不会被显示。

即使你不声明action item显示标题,你也需要去定义title属性,有如下几个原因:

【1】如果ActionBar没有足够的空间,overflow中的menu item可能只会显示title。

【2】屏幕阅读器给视力障碍的用户只显示Menu item的title。

【3】对于那些只显示了icon的action item,用户可以通过长按来显示title。

你也可以使用"always"取值来要求一个item总是以Action Button的形式展示在ActionBar上。但是你不应该这么去干,因为这样做在小屏设备上可能会导致一些布局问题,最好是使用"ifRoom"去替代。

2.1 处理Action item的点击事件

当用户点击了一个action,系统会回调我们Activity的onOptionsItemSelected()方法。这个方法传递了一个MenuItem的参数,你可以通过它的getItemId()方法获取到其ID。这个id就是<item>标签的id属性。然后你可以根据这个id去执行一些对应的操作。如:

@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);
}
}
注意:当在Fragment中加载了一个menu,即通过Fragment的onCreateOptionsMenu()方法,当用户点击了一个item的时候,系统会回调Fragment的onOptionsItemSelected()方法。但是,宿主Activity会优先获取到处理这个事件的权利,所以系统会先调用宿主Activity的onOptionsItemSelected()方法,为了确保宿主Activity中的Fragment有机会去处理这个事件,当宿主Activity不需要处理某个事件时,应该调用super的onOptionsItemSelected()方法,而不是返回false。(为了执行Framgment的onCreateOptionsMenu()方法,我们需要在宿主Activity的onCreate()方法中调用Fragment对象的setHasOptionsMenu(true)方法)

2.2 使用分割ActionBar

分割ActionBar是当Activity运行在小屏手机上时,将ActionBar的action item全部显示在屏幕底部。这种做法是为了在小屏设备上运行时,保证导航和标题能正常显示。在支持包下使用分割ActionBar时,我们需要做两件事:

【1】在<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>
使用分割ActionBar时,如果你移除掉icon和title,还可以让隐藏掉上方ActionBar导航部分(即下图最右边的效果)。为了达到这种效果,我们需要调用 setDisplayShowHomeEnabled(false)setDisplayShowTitleEnabled(false)方法禁用掉title和icon。 从零学Android(十一)、ActionBar基础知识


3. 导航箭头

启用应用图标作为一个导航按钮可以让用户在基于屏幕的层次关系来进行导航。比如说,屏幕A展示了一个列表,当选择一个子项时,进入屏幕B,而屏幕B则展示了一个详情,

那么屏幕B页面应该包含一个导航按钮,可以点击返回到屏幕A。

注意:ActionBar的导航按钮和系统的返回按钮不同。系统返回按钮是基于用户历史浏览记录来进行导航,它一般是基于屏幕间的时间关系,而ActionBar的导航则是基于App的继承结构。举个例子:比如屏幕A展示了所有文章的列表,而屏幕B则展示的文章详情,而且在屏幕B中能点击阅读下一篇文章,如果我们浏览了下一篇文章,点击屏幕返回按钮我们会回到上一篇文章的详情页面,而点击ActionBar的导航按钮,我们会回到文章列表页面。

启用app icon作为导航按钮,需要调用setDisplayHomeAsUpEnabled()方法,如:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_details);

ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
...
}
这样,就会出现导航箭头了,但是这时候的导航箭头没有任何的作用;为了指定用户点击导航箭头打开的Activity,你有两个选择:

【1】在manifest文件中指定parent Activity

当parent Activity的取值相同时,最好采用这种方式。通过在Manifest文件中指定具体的parent Activity,当用户按下ActionBar的导航按钮时,ActionBar会自动去执行正确的操作。

在Android 4.1即API level = 16开始,你可以通过<activity>元素标签下的parentActivityName属性去指定parent Activity的值。而在更早的版本之上,需要通过<meta-data>元素标签指定parent 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>
一旦指定了parent Activity,而且你也通过调用 setDisplayHomeAsUpEnabled()方法启用了ActionBar的导航功能,那么ActionBar的导航功能就能正常使用了。

【2】在Activity中覆盖getSupportParentActivityIntent()onCreateSupportNavigateUpTaskStack()方法

另外,当parent Activity的取值不同时,而是根据当前具体用户所在的页面而确定parent Activity时,我们一般采用覆盖getSupportParentActivityIntent()onCreateSupportNavigateUpTaskStack()方法的途径去指定ActionBar的导航。也就是说,假如有多个页面可以到达当前页面,那么我们就需要根据用户具体是从哪一个页面进入当前页面的情况来设定parent Activity。

当用户按下ActionBar导航按钮时,系统会回调getSupportParentActivityIntent()方法,如果需要根据用户具体从哪个页面进入当前页来确定导航的位置,那么你就需要重写该方法,并且返回一个合适的Intent,来打开合适的parent Activity。

如果我们当前的Activity正运行在一个不属于我们自己APP的task中,这个时候当用户按下ActionBar的导航按钮,系统会回调onCreateSupportNavigateUpTaskStack()方法,因此,我们必须这个方法中的TaskStackBuilder参数去构建一个合适的回退栈。

即使我们已经覆盖了getSupportParentActivityIntent()方法指定了ActionBar导航的parent Activity,我们也可以通过在manifest文件中指定一个“默认”的parent Activtiy来避免重写onCreateSupportNavigateUpTaskStack()方法,因为默认实现的onCreateSupportNavigateUpTaskStack()方法会根据在manifest中指定的parent Activity去组合一个合适的回退栈。

注意:如果你是通过使用很多个Fragment进行页面切换,而不是切换Activity,那么上面的两种方法都不会奏效。这个时候,如果需要通过Fragment导航,那么我们需要重写onSupportNavigateUp()方法,在其下执行一些合适的Fragment事务操作,一般操作是调用popBackStack()方法将当前的Fragment弹出栈。


4. 添加一个Action View

ActionBar就是一个Action Button点击后出现在ActionBar上的替身。Action View提供一个快速的执行动作的途径,而不用去改变Activity和Fragment。比如说,在你的ActionBar上有一个用来搜索的Action,你可以添加一个SearchView控件作为Action View嵌入到ActionBar中,如下图所示。从零学Android(十一)、ActionBar基础知识

为了声明一个Action View,可以使用actionLayoutactionViewClass属性指定一个layout布局资源或者控件;例如,加入一个SearchView控件作为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>
注意上面的 showAsAction属性有包含一个 "collapseActionView"取值。这个取值声明了这个Action View可以被折叠到一个Action Button中,当点击这个按钮时,才会展开这个Action View。

如果你需要配置这个Action View,比如添加一个事件监听,你可以在onCreateOptionsMenu()方法中处理。你可以通过MenuItemCompat.getActionView()方法传递响应的MenuItem去获取到Action View;例如获取上面例子中的SearchView:

@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);
}
在API level 11或者之上,可以响应的MenuItem的 getActionView()方法去获取Action View:
menu.findItem(R.id.action_search).getActionView()
4.1 处理可折叠的Action Views

为了节约ActionBar的空间,你可以将Action View折叠到一个Action Button中。当折叠时,系统可能会将这个Action放置到Action overflow中,但是当点击这个Action时,Action View仍然会会出现。为了让Action View可折叠,你需要设置showAsAction属性取值为"collapseActionView"

因为系统会在我们点击Action Button时展开我们的Action View,所以我们不用在onOptionsItemSelected()方法中去特殊处理。由于系统还是会回调onOptionsItemSelected()方法,所以如果你返回了true(说明你已经自己处理了该事件),那么系统就不会展开Action View。

如果你需要根据action view的可见性来更新Activity,那么你可以通过setOnActionExpandListener()方法添加一个监听器OnActionExpandListener到Action View,然后在Action View展开和折叠时,它会回调监听器的方法:

@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
}
});
}

5. 添加一个Action Provoder

和action view类似,action provider是用一个自定义布局去代替一个action button。和action view不同的是,action provider需要去控制所有action的行为,另外action provider被按下时还能展示一个子菜单。

为了声明一个Action provider,你需要在菜单资源的<item>标签下使用actionProviderClass属性,并且设置其值为一个ActionProvider的完整类名。

你可以通过继承ActionProvider类去创建自己的Action Provider,但是Android系统也帮我们提供了一些Action Provider,如ShareActionProvider,它直接在Action Bar上提供一个可分享的app列表选择。如下图:

从零学Android(十一)、ActionBar基础知识

因为每一个ActionProvider类都定义了它的action行为,所以你可以不用在onOptionsItemSelected()方法中去监听action。当然,如果你还需要执行一些其它的动作的话,你还是可以在onOptionsItemSelected()方法中去监听action的,但是如果这么做了,记得在onOptionsItemSelected()方法中返回false,以便Action Provider还能回调onPerformDefaultAction()方法执行它的操作。

但是,如果Action Provider提供了一个actions子菜单,那么当用户选中子菜单的某项时是不会回调Activity的onOptionsItemSelected()方法的。

5.1 使用系统提供的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>
现在,action provider就能控制每个action item,并且处理它们的外观和行为了。但是你必须给这个item提供一个title,以防它出现在action overflow中。

然后最后一个需要做的事,就是定义你希望用来分享的Intent了。为了去定义这个Intent,你需要在nCreateOptionsMenu()方法中通过MenuItem找到对应的item标签,并且根据它作为参数调用MenuItemCompat.getActionProvider()方法找到对应的Action Provider,然后根据返回的ShareActionProvider调用setShareIntent()方法绑定一个带有分享内容的ACTION_SEND的Intent。

我们在onCreateOptionsMenu()方法中初始化分享action的时候调用了一次setShareIntent()方法,但是由于用户的context可能会发生改变,所以你必须在分享内容改变时你必须再次调用setShareIntent()方法来更新Intent。

比如:

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;
}
当用户点击某个分享item时, ShareActionProvider 会处理与用户的交互,而不需要你在 onOptionsItemSelected()方法中去处理点击事件。

5.2 创建一个自定义的Action Provider

创建自定义的action provider可以让你在模块内重用和管理动态的action item的行为,而不用在Fragment或者Activity的代码中去处理action item的变化和行为。为了创建属于我们自己的不同行为的action provider,我们只需要继承ActionProvider类并且实现它的一些回调方法即可。一些重要的回调方法如下:

【1】ActionProvider(Context context)

这个是构造方法,需要传递一个Context对象。

【2】onCreateActionView(MenuItem forItem)

这个方法是你定义item的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;
}

【3】 onPerformDefaultAction()

当action overflow中的menu item被选中时,系统会回调该方法,action provider需要执行一个默认的action。但是,如果你的action provider包含有一个子菜单,那么你就要用到onPrepareSubMenu(SubMenu subMenu)方法,即使action provider被放置在action overflow中,子菜单也会出现。这个时候onPerformDefaultAction()方法就不会被调用。

注意:Fragment或者Activity实现onOptionsItemSelected()方法通过处理item的选中事件(并且返回true),会覆盖掉action provider的默认行为(除非action provider存在有子菜单),这种情况下,系统不会回调onPerformDefaultAction()方法。


6. 添加导航Tabs

ActionBar上的导航Tabs让View页面的切换变得十分简单。ActionBar的Tabs的好处在于它对于不同大小的屏幕的支持很好。比如说,当屏幕大小足够的时候,Tabs会出现在Action Button的一侧(如下图中的第一个图),而的那个显示在小屏幕设备上时,Tabs会显示在分离的ActionBart上(如下图中的第二个图),有时候系统甚至将Tabs通过一个下拉列表来显示。

从零学Android(十一)、ActionBar基础知识从零学Android(十一)、ActionBar基础知识

为了达到这种效果,你的layout布局中必须包含有一个ViewGroup,以便用来放置那些和Tabs关联的Fragment页面。这个ViewGroup需要有一个ID,这样你才可以在代码中引用它,并且在其中切换Tabs。另外,如果Tab的内容页面是填充整个Activity布局的话,那么我们的Activity就不需要有layout布局了,甚至都不用去调用setContentView()。这个时候们我们可以将Fragment放置到默认的root View下面,它的ID为android.R.id.content

一旦你决定了Fragment的布局位置,那么你就可以按照下面的流程来添加导航Tabs了:

【1】实现ActionBar.TabListener接口。这个接口提供了关于Tabs的一些回调方法,如当用户按下某个Tab时,你可以切换Tab。

【2】对于每一个你要添加的Tab,你需要初始化一个ActionBar.Tab的对象,并且调用setTabListener()方法设置一个ActionBar.TabListener监听器。另外可以调用setText()设置标题,通过调用setIcon()设置icon。

【3】然后通过调用addTab()方法加入Tab。

注意ActionBar.TabListener监听器的回调方法不会指定哪个Fragment和Tab关联,仅仅表明当前哪个Tab被选中。你必须自己去关联tab和Fragment页面。具体关联的方法有很多种,需要根据你的设计而定。

比如说,你可以实现ActionBar.TabListener接口,然后每个Tab使用这个监听器的实例:

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.
}
}
注意:你不能在这些回调方法中调用 commit()方法,因为系统会帮我们调用。另外你也不能将这些Fragment事务加入到回退栈中。

在这个例子中,当某个tab被选中时,监听器只是简单地将一个Fragment关联到Activity中,当Fragment实例不存在时,则创建这个Fragment,然后添加到Activity的android.R.id.content下,当tab没被选中,则解除掉Fragment与Activity的关联。

剩下的工作就是创建ActionBar.Tab和将它加入到ActionBar了。另外,你必须调用setNavigationMode(NAVIGATION_MODE_TABS)确保Tabs可见。

如下面的代码:

@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);
}
当你需要保存当前Activity状态时,你可以通过 getSelectedNavigationIndex()方法查询到当前选中的tab,然后保存这个值即可。

(另外也可以通过ViewPager来关联Tab和Fragment,只需要在onTabSelected()回调方法中调用setCurrentItem()方法即可,具体参考官方实例


7. 添加下拉式导航

作为另外一个导航模式或者称为过滤模式,ActionBar提供了一个spinner下拉导航。一般用于过滤数据。当然,如果是很频繁的数据切换,就应该使用上面的Tab导航模式要更好一点。

使用下拉过滤的流程如下:

【1】创建一个SpinnerAdapter适配器用来给下拉Spinner提供数据显示。

【2】实现ActionBar.OnNavigationListener接口来定义用户选中下拉item的行为。

【3】在Activity的onCreate()方法中调用setNavigationMode(NAVIGATION_MODE_LIST)方法启动下拉过滤。

【4】通过setListNavigationCallbacks()方法设置下拉列表的数据和回调。如:

actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationCallback);
这个流程很简单,主要工作还是在适配器 SpinnerAdapter的实现和 ActionBar.OnNavigationListener接口的实现。

具体例子如下:

创建一个简单的SpinnerAdapter

SpinnerAdapter mSpinnerAdapter = ArrayAdapter.createFromResource(this,
R.array.action_list, android.R.layout.simple_spinner_dropdown_item);
其中的列表数据数组如下:
<?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>
然后就对 ActionBar.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 = getSupportFragmentManager().beginTransaction();    // 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;  }};
最后就是调用 setListNavigationCallbacks(),传入我们创建好的适配器和接口对象即可。


8. 修改ActionBar的样式

如果你希望定制你自己的ActionBar外观属性,ActionBar是允许你定制每一个细节的,包括ActionBar的bar颜色,文字颜色,按钮样式等。为了定制自己的ActionBar样式,你需要使用Android系统的styletheme框架。

注意:对于你提供的背景图,必须使用.9图,且.9图的高度应该小于40dp,宽度小于30dp。

一般外观样式

【1】actionBarStyle

指定action的样式资源。默认值为Widget.AppCompat.ActionBar,当你需要定制时,需要用该值作为你的parent样式。

支持的样式包括:

background :定义ActionBar的drawable背景资源。

backgroundStacked : 定义ActionBar的Tabs的drawable背景资源。

backgroundSplit : 分离ActionBar的drawable背景资源。

actionButtonStyle : 定义了Action Button的样式资源。默认值为Widget.AppCompat.ActionButton,当你需要定制时,需要用该值作为你的parent样式。

actionOverflowButtonStyle : overflow中action item的样式资源。默认值为Widget.AppCompat.ActionButton.Overflow ,当你需要定制时,需要用该值作为你的parent样式。

displayOptions : 定义一个或多个ActionBar展示的选项,比如是否显示app logo,是否启用导航,是否显示Activity标题等,具体取值有noneuseLogoshowHomehomeAsUpshowTitleshowCustomdisableHome

divider : 定义Action item之间的分割线。

titleTextStyle : 定义了ActionBar的标题样式,默认值为TextAppearance.AppCompat.Widget.ActionBar.Title,当你需要定制时,需要用该值作为你的parent样式。

windowActionBarOverlay:定义了ActionBar是否需要遮盖在Activity布局之上,默认值为false。在上面有讲过。

注意:Holo系列样式默认ActionBar背景为半透明。但是你可以自己定义自己的样式,同时不同设备下的DeviceDefault主题可能使用一些不透明的背景色。


Action items的样式

actionButtonStyle:定义了action item的按钮样式。默认值为Widget.AppCompat.ActionButton,当你需要定制时,需要用该值作为你的parent样式。

actionBarItemBackground:定义了每一个item的drawable背景,它应该是一个状态列表Drawable资源,以便不同状态背景不同。

itemBackground :定义了overflow中每一个item的drawable背景,它应该是一个状态列表Drawable资源,以便不同状态背景不同。

actionBarDivider : 定义了action item之间的分割线drawable资源。

actionMenuTextColor : 定义了action item中的文字颜色。

actionMenuTextAppearance:定义了action item中的文字样式资源。

actionBarWidgetTheme :定义了作为Action View加载到ActionBar中的组件的主题资源。


导航Tab的样式

actionBarTabStyle:定义了ActionBar中导航Tab的样式。默认值为Widget.AppCompat.ActionBar.TabView,当你需要定制时,需要用该值作为你的parent样式。

actionBarTabBarStyle:定义了导航Tab下面的细条(选中条)样式。默认值为Widget.AppCompat.ActionBar.TabBar,当你需要定制时,需要用该值作为你的parent样式。

actionBarTabTextStyle:定义了ActionBar中导航Tab的文本样式。默认值为Widget.AppCompat.ActionBar.TabText,当你需要定制时,需要用该值作为你的parent样式。


下拉过滤列表样式

actionDropDownStyle:定义了下拉导航的样式资源,如背景,文字样式等。默认值为Widget.AppCompat.Spinner.DropDown.ActionBar,当你需要定制时,需要用该值作为你的parent样式。


示例主题:

下面是一个示例主题,注意这个示例中包含了2个版本的样式属性,一种是带有android:字头的样式属性,它们是用于在API level >=11时使用,不带android:字头的样式属性则是给低版本设备使用的。

<?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文件中使用如下:
<application android:theme="@style/CustomActionBarTheme" ... />