首页 > 代码库 > Android导航栏ActionBar的详细分析 一

Android导航栏ActionBar的详细分析 一

尊重原创:http://blog.csdn.net/yuanzeyao/article/details/39378825

关于ActionBar,相信大家并不陌生,但是真正能够熟练使用的也不是很多,这篇文章主要为大家详细介绍ActionBar的相关知识,ActionBar是在Android3.0中引入的概念,所以在2.x系统中使用ActionBar我们需要依赖ActionBarSherklock或者androi-support-v7库,ActionBarSherklock是anroid中非常有名的一个开源项目,android-support-v7是Google后来推出的一个库,有了v7库后AndroidBarSherklock这个开源项目基本可以退出历史舞台了,在使用android-support-v7的过程中,一定不能仅仅使用它的jar包,因为它的jar包不包含一些重要的资源文件,我们必须导入android-support-v7工程,然后让我们的工程依赖它。


ActionBar的作用
1、帮助用户知道你现在处于哪个页面
2、为用户提供统一的导航界面

关于更多的ActionBar导航功能请参见 http://developer.android.com/guide/topics/ui/actionbar.html 我的这篇文章基本也是参考这里的

ActionBar在界面中的展现形式 如下图:



从图中可以看到一个ActionBar包括 APP icon,ActionItems,Action Overflow,其实ActionBar基本就代替了以前版本中的菜单的概念,在不支持ActionBar的App中,如果你创建了菜单,当你点击菜单键时,下方会出来菜单,而在支持Actionbar的菜单中这些菜单向就出现在了ActionItem和Action Overflow里面了(详细请见后面),有些细心同学在平时可能会发现同一款App,在有些手机上出现了Overflow,而有些手机却没有,其实这个是有规律的,在有菜单物理键的手机上是没有Overflow的,在没有物理菜单的手机上才有,其实Google一直主张Android设备去掉物理菜单键,所以相信以后大部分手机都没有菜单物理键的,所以菜单的概念也会慢慢的淡化。

下面我们开始学习ActionBar吧 ,由于3.0之后的系统和2.x系统还是有稍微的区别,所以我今天打算先讲讲2.x系统中ActionBar的使用,然后讲解3.0之后系统ActionBar的使用。
这里我们就使用android-support-v7吧,毕竟它是Google推出来的。


在2.x上使用ActionBar的步骤

1、导入android-support-v7库,这个库其实在你的sdk里面就有(前提是你已经下载下来了),如我的路径:D:\android-sdk-windows\extras\android\support\v7\appcompat
2、创建项目,让我们的库依赖andorid-support-v7,并让需要使用ActionBar的Activity依赖ActionBarActivity,并进行如下配置:

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

就这么简单,你的项目就已经引入了ActionBar了,如果你想隐藏ActionBar,如下操作即可

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

这里要提醒一下:由于ActionBar在隐藏的时候会重现绘制Activity的界面,从而填充ActionBar的空白,所以当你频繁的隐藏和显示ActionBar时,会导致Activity的界面频繁重绘,为了避免这种情况发生,你可以再actionBarStyle中将 windowActionBarOverlay这个属性设置为true,也就是说ActionBar会在Activity的上面,隐藏和显示不会影响Activity


我上面引入的ActionBar没有引入任何内容,默认里面就是一个设置项,现在我们就像里面加入自己的item吧
创建res/menu/main_activity_actions.xml文件并加入如下内容,


<menu xmlns:android="http://schemas.android.com/apk/res/android" >
   <item
        android:id="@+id/action_copy"
        android:icon="@drawable/copy"
        android:title="复制"/>
     <item
        android:id="@+id/action_cut"
        android:icon="@drawable/cut"
        android:title="剪切"/>
      <item
        android:id="@+id/action_discard"
        android:icon="@drawable/discard"
        android:title="删除"/>
       <item
        android:id="@+id/action_edit"
        android:icon="@drawable/edit"
        android:title="编辑"/>
       
        <item
        android:id="@+id/action_email"
        android:icon="@drawable/email"
        android:title="邮箱"/>
        
</menu>

然后重写下面的方法:

@Override
  public boolean onCreateOptionsMenu(Menu menu)
  {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
	//返回true才会显示overflow按钮
    return true;
  }

通过以上步骤,我们就已经向ActionBar中加入了5个Item,运行一下看看效果吧,如下图

ActionBar是出来了,但是和我们上面说的ActionBar貌似有些区别,这些item都进入了overflow了,另外就是我明明设置了icon属性,却没有看见icon
对于这两个问题,我们一一解决吧
item默认都是进入Overflow的,如果想有些重要的Item 进入ActionItem 里面,那么需要配置一个属性showAsAction

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:yourapp="http://schemas.android.com/apk/res-auto" >
    <item 
          android:id="@+id/action_copy"
		  android:icon="@drawable/copy"
		  android:title="复制"
          yourapp:showAsAction="always"  />
    ...
</menu>

注意这里的showAsActoin命名空间是自定义的,不是"android",因为这个属性在2.x系统中没有,是andorid-support-v7定义的,你运行一下,会发现复制已经到了ActionItem的位置了

第二个问题就是设置了icon为什么没有显示?其实在Actionbar中,处于ActionItem位置的item默认是显示icon而不显示title的,我们可以通过在showAsActoin中配置"always|withText",让Tiele显示出来(但是也不一定会显示,只是尽量显示),而处于Overflow的item 默认是显示title而不显示icon的,这个可以通过反射机制来改变,方案如下:

public void setOverflowIconVisiable(Menu menu)
  {
    try
    {
      Class clazz=Class.forName("com.android.internal.view.menu.MenuBuilder");
      Field field=clazz.getDeclaredField("mOptionalIconsVisible");
      if(field!=null)
      {
        field.setAccessible(true);
        field.set(menu , true);
      }
    } catch (ClassNotFoundException e)
    {
      e.printStackTrace();
    } catch (NoSuchFieldException e)
    {
      e.printStackTrace();
    } catch (IllegalArgumentException e)
    {
      e.printStackTrace();
    } catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
    
  }
然后再onMenuOpened方法中调用

 @Override
  public boolean onMenuOpened(int featureId, Menu menu)
  {

    if(featureId == Window.FEATURE_ACTION_BAR && menu != null)
    {
      setOverflowIconVisiable(menu);
    }
    
    return super.onMenuOpened(featureId, menu);
  }

运行效果如下图,可以看到icon已经显示了


目前这些ActionItem和Overflowsitems都是没有响应事件的,现在为他们添加吧,如果以前使用过菜单的同学会觉得和菜单是一样的

 @Override
  public boolean onOptionsItemSelected(MenuItem item)
  {
    switch(item.getItemId())
    {
      case R.id.action_copy:
        Toast.makeText(this, item.getTitle(), 1000).show();
        return true;
      case R.id.action_cut:
        Toast.makeText(this, item.getTitle(), 1000).show();
        return true;
      case R.id.action_delete:
        Toast.makeText(this, item.getTitle(), 1000).show();
        return true;
      case R.id.action_edit:
        Toast.makeText(this, item.getTitle(), 1000).show();
        return true;
      case R.id.action_email:
        Toast.makeText(this, item.getTitle(), 1000).show();
        return true;
    }
    return super.onOptionsItemSelected(item);
  }

Fragment中加入ActionItem

接下来学习一下在Fragment里面使用ActionItem吧

public class MyFragment extends Fragment
{
  private static final String TAG = "MyFragment";
  
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
	//一定要调用,否则无法将菜单加入ActionItem
    setHasOptionsMenu(true);
  }
  
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
  {
    
    TextView tv=new TextView(this.getActivity());
    tv.setText("Hello Gavin");
    return tv;
  }
  
  @Override
  public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
  {
    inflater.inflate(R.menu.fragment_men, menu);
    super.onCreateOptionsMenu(menu, inflater);
  }
  
  @Override
  public boolean onOptionsItemSelected(MenuItem item)
  {
    switch(item.getItemId())
    {
      case R.id.action_share:
        Toast.makeText(this.getActivity(), item.getTitle(), 1000).show();
        return true;
      
    }
    return super.onOptionsItemSelected(item);
  }
}

使用上和Activity差不多,有一点需要注意,对于菜单的响应事件,Activity优于Fragment响应,所以在Activity的onOptionsItemSelected方法中对于没有处理的事件需要调用super.onOptionsItemSelected(item)(也可以是false,但是推荐前者),如果返回true,那么Fragment是不会执行onOptionsItemSelectedd的。

将ActionBar置于屏幕下方
如果需要将ActionBar置于屏幕下方,那么仅仅需要如下配置即可

<manifest ...>
    <activity  ... >
        <meta-data android:name="android.support.UI_OPTIONS"
                   android:value=http://www.mamicode.com/"splitActionBarWhenNarrow" />>

ActionBar的导航功能

ActionBar最重要的功能就是其导航功能,使用其导航功能时,需要进行如下配置
1、显示导航按钮
ActionBar actionBar = getSupportActionBar();
    actionBar.setDisplayHomeAsUpEnabled(true);
配置完了后,运行如下图,你会发现Actionbar多了一个类似返回的icon

2、为导航添加事件
在onOptionsItemSelected方法中加入如下分支判断
case android.R.id.home:
        finish();
        return true;
这个是最简单的处理,就是finish掉自身,更多强大的导航功能请参见后面


看到上面的导航功能估计很多同学会觉得它的功能和物理返回键的功能不是一样吗?如果按照我上面的实现,确实差不多,但是ActionBar真正的意图不是这样的,我们知道物理返回键时根据Activity栈回退到前一个Activity,是不能直接回退到前前一个Activity的,但是ActionBar是可以的,下面就来实现以下吧

实现这个导航功能有两种实现方式
a、为当前Activity指定父Activity
b、重写 getSupportParentActivityIntent() 和onCreateSupportNavigateUpTaskStack()


其中a比较适合需要返回的父Activity比较 固定,不会因为上下文环境而改变,实现也简单,只需要进行如下配置

<activity
            android:name="com.action.demo.SecondActivity">
             
            <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value=http://www.mamicode.com/"com.action.demo.MainActivity" />>并重写SecondActivity的  onOptionsItemSelected方法

@Override
  public boolean onOptionsItemSelected(MenuItem item)
  {
    switch(item.getItemId())
    {
      case android.R.id.home:
        Intent upIntent = NavUtils.getParentActivityIntent(this);  
        if (NavUtils.shouldUpRecreateTask(this, upIntent)) {  
            TaskStackBuilder.create(this)  
                    .addNextIntentWithParentStack(upIntent)  
                    .startActivities();  
        } else {  
            upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  
            NavUtils.navigateUpTo(this, upIntent);  
        }  
        return true;  
    }
    return super.onOptionsItemSelected(item);
  }

b比较适合根据上下文环境选择导航到某一个Activity,例如,可以分别从MainActivity,ThreeActivity进入到SecondActivity,那么将来源标记为1和2(通过Intent 传递),重写getSupportParentActivityIntent

 @Override
  public Intent getSupportParentActivityIntent()
  {
    if(from==1)
    {
      return new Intent().setClassName(this.getPackageName(), "com.action.demo.ThreeActivity");
    }else if(from==2)
    {
      return new Intent().setClassName(this.getPackageName(), "com.action.demo.ThreeActivity");
    }
  
    return super.getSupportParentActivityIntent();
  }

 这样便可以动态的实现导航

添加ActionView

   一个ActionView就是一个Widget,用来替换ActionBar中的一些按钮,可以在不用切换Activity和 Fragment的情况下实现一些丰富的功能,下面使用SearchView来作为例子讲解ActionView的使用
   在main.xml中加入item

 <item android:id="@+id/action_search"
          android:title="搜索"
          android:icon="@drawable/search"
          myapp:showAsAction="ifRoom|collapseActionView"
          myapp:actionViewClass="android.support.v7.widget.SearchView" />

然后在onCreateOptionsMenu中加入如下代码

 MenuItem searchItem=menu.findItem(R.id.action_search);
    final SearchView searchView=(SearchView) MenuItemCompat.getActionView(searchItem);
    searchView.setOnQueryTextListener(new OnQueryTextListener()
    {
      
      @Override
      public boolean onQueryTextSubmit(String arg0)
      {
        Toast.makeText(MainActivity.this, arg0, 1000).show();
        searchView.onActionViewCollapsed();
        return true;
      }
      
      @Override
      public boolean onQueryTextChange(String arg0)
      {
        return false;
      }
    });

添加Action Provider

Action Provider和Action View 有些类似,和Action View不同的是当Action Provider按下的时候可以显示子菜单。下面使用ShareActionProvider为例讲解ActionProvider的使用

<item android:id="@+id/action_share1"
          android:title="分享"
          myapp:showAsAction="ifRoom"
          myapp:actionProviderClass="android.support.v7.widget.ShareActionProvider"
          />

在onCreateOptionsMenu中加入如下代码

ShareActionProvider mShareActionProvider = (ShareActionProvider)
            MenuItemCompat.getActionProvider(menu.findItem(R.id.action_share1));
    mShareActionProvider.setShareIntent(getDefaultIntent());
运行效果如下图:




ShareActionProvider是Android系统自带的一个Provider,下面我们试试自定义的Provider,我们来模仿微信中的右侧的“+”号功能
public class CustomActionProvider extends ActionProvider
{
  private static final String TAG = "CustomActionProvider";
  public CustomActionProvider(Context context)
  {
    super(context);
  }

  

  @Override
  public View onCreateActionView()
  {
    return null;//LayoutInflater.from(getContext()).inflate(R.layout.provider_layout, null);
  }
  //这个方法是点击“+”号会默认执行的地方,注意这个方法可以被onOptionsItemSelected方法拦截掉
  @Override
  public boolean onPerformDefaultAction()
  {
    Toast.makeText(this.getContext(), "onPerformDefaultAction", 1000).show();
    return super.onPerformDefaultAction();
  }
  
  @Override
  public void onPrepareSubMenu(SubMenu subMenu)
  {
    super.onPrepareSubMenu(subMenu);
    subMenu.clear();
    subMenu.add("发起群聊").setIcon(R.drawable.alluser).setOnMenuItemClickListener(new OnMenuItemClickListener()
    {
      
      @Override
      public boolean onMenuItemClick(MenuItem item)
      {
        return true;
      }
    });
  }
  //一定要重写该方法,并返回true,否则不会出现子菜单
  @Override
  public boolean hasSubMenu()
  {
    return true;
  }
  
}

在ActionBar中添加Tab

使用ActionBar中的Tab功能,使用ActionBar中的Tab功能非常简单
ActionBar bar=this.getSupportActionBar();
    bar.setDisplayHomeAsUpEnabled(true);
    bar.setDisplayShowTitleEnabled(false);
    bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    bar.addTab(bar.newTab().setText("电影").setTabListener(this));
    bar.addTab(bar.newTab().setText("电视剧").setTabListener(this));
    bar.addTab(bar.newTab().setText("直播").setTabListener(this));

效果如下图



ActionBar的Style和Theme



在学习ActionBar的style和Theme之前,我们先来了解一下Style和Theme是啥吧,Style即使一些属性的集合,类似于css文件(前端开发的同学都非常熟悉),Theme其实就是Style,只不过Theme是用于Activity的Style,在Android系统中自带了很多了Theme,这些Theme中有的有ActionBar,有的没有ActionBar,对于没有Actionbar的Theme对于的Activity,当调用getSupportActoin时返回null,

虽然说ActionBar是为用户提供的统一的导航功能,但是这个并不意味着所有的ActionBar都长一个样,我们还是可以根据需求进行定制的,下面我们就来学习定制自己的ActionBar
在学习自定义ActionBar之前给大家推荐一个网址:http://jgilfelt.github.io/android-actionbarstylegenerator/  可以帮助我们进行定制ActionBar

这里我先给出竖屏和横屏的效果图,然后给出style文件,ActionBar的style属性非常多,我们只需要知道一些比较常用的,然后知道怎么查询这个属性就行了,我可以到http://developer.android.com/guide/topics/ui/actionbar.html  ActionBar更多属性的使用

效果图如下:
竖屏图:


横屏图:




style.xml

<style name="MyActionBarTheme" parent="@style/Theme.AppCompat">
		
       	<item name="android:actionBarStyle">@style/ActionBarStyle</item>
       	<item name="android:actionMenuTextColor">@color/menu_color</item>
       	<item name="android:windowActionBarOverlay">false</item>
        <item name="android:itemBackground">@drawable/action_item_bg_selector</item> 
        <item name="android:actionOverflowButtonStyle">@style/ActionBarOverflowStyle</item>
        <item name="android:actionBarTabTextStyle">@style/TabTextStyle</item>
        <item name="android:actionBarTabStyle">@style/ActionBarTab</item>
       
		<!--Support Library-->
        <item name="actionBarStyle">@style/ActionBarStyle</item>
       	<item name="actionMenuTextColor">@color/menu_color</item>
       	<item name="windowActionBarOverlay">false</item>
        <item name="actionOverflowButtonStyle">@style/ActionBarOverflowStyle</item>
        <item name="actionBarTabTextStyle">@style/TabTextStyle</item>
        <item name="actionOverflowButtonStyle">@style/ActionBarOverflowStyle</item>
        <item name="actionBarTabStyle">@style/ActionBarTab</item>
    </style>
    
    <style name="ActionBarStyle" parent="@style/Widget.AppCompat.ActionBar">
       <item name="android:background">@drawable/action_back</item>
       <item name="android:titleTextStyle">@style/TitleTextStyle</item>
       <item name="background">@drawable/action_back</item>
       <item name="titleTextStyle">@style/TitleTextStyle</item>
       <item name="backgroundStacked">@drawable/action_back_stack</item>
       <item name="backgroundSplit">@drawable/action_back_stack</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>
        <item name="android:textSize">15sp</item>
        <item name="android:textStyle">normal</item>
        <item name="android:maxLines">1</item>
    </style>
    
    <style name="ActionBarOverflowStyle" parent="Widget.AppCompat.ActionButton.Overflow">
        <item name="android:src">@drawable/more</item>
        
    </style>
    
    <style name="ActionBarTab" parent="Widget.AppCompat.ActionBar.TabView">
         <item name="android:background">@drawable/tab_indicator</item>
    </style>


细心的同学会发现这里每个属性声明了两遍(个别只有一个),这是因为我们使用的是support-library-v7库,带前缀“android:”的是对应Android系统的,没有这个前缀的是support-librayr的。


最后给出tab_indicator的定义,这个是tab指示器的背景图片,这个并不是我手动写的,我是利用上面给出的网址生成的。

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Non focused states -->
    <item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@android:color/transparent" />
    <item android:state_focused="false" android:state_selected="true"  android:state_pressed="false" android:drawable="@drawable/tab_selected" />

    <!-- Focused states -->
    <item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/tab_unselected_focused" />
    <item android:state_focused="true" android:state_selected="true"  android:state_pressed="false" android:drawable="@drawable/tab_selected_focused" />

    <!-- Pressed -->
    <!--    Non focused states -->
    <item android:state_focused="false" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_unselected_pressed" />
    <item android:state_focused="false" android:state_selected="true"  android:state_pressed="true" android:drawable="@drawable/tab_selected_pressed" />

    <!--    Focused states -->
    <item android:state_focused="true" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_unselected_pressed" />
    <item android:state_focused="true" android:state_selected="true"  android:state_pressed="true" android:drawable="@drawable/tab_selected_pressed" />
</selector>

先写到这里吧,下篇文章我将会介绍一下ActionBar 在3.0上的使用以及魅族手机SmartBar的用法

Android导航栏ActionBar的详细分析 一