【转】Android ActionBar完全解析,使用官方推荐的最佳导航栏(上)
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/18234477
本篇文章主要内容来自于Android Doc,我翻译之后又做了些加工,英文好的朋友也可以直接去读原文。
http://developer.android.com/guide/topics/ui/actionbar.html
Action Bar是一种新増的导航栏功能,在Android 3.0之后加入到系统的API当中,它标识了用户当前操作界面的位置,并提供了额外的用户动作、界面导航等功能。使用ActionBar的好处是,它可以 给提供一种全局统一的UI界面,使得用户在使用任何一款软件时都懂得该如何操作,并且ActionBar还可以自动适应各种不同大小的屏幕。下面是一张使 用ActionBar的界面截图:
其中,[1]是ActionBar的图标,[2]是两个action按钮,[3]是overflow按钮。
由于Action Bar是在3.0以后的版本中加入的,如果想在2.x的版本里使用ActionBar的话则需要引入Support Library,不过3.0之前版本的市场占有率已经非常小了,这里简单起见我们就不再考虑去做向下兼容,而是只考虑4.0以上版本的用法。
添加和移除Action Bar
ActionBar 的添加非常简单,只需要在AndroidManifest.xml中指定Application或Activity的theme是Theme.Holo或 其子类就可以了,而使用Eclipse创建的项目自动就会将Application的theme指定成Theme.Holo,所以ActionBar默认 都是显示出来的。新建一个空项目并运行,效果如下图所示:
而如果想要移除ActionBar的话通常有两种方式,一是将theme指定成Theme.Holo.NoActionBar,表示使用一个不包含ActionBar的主题,二是在Activity中调用以下方法:
1 ActionBar actionBar = getActionBar(); 2 actionBar.hide();
现在重新运行一下程序,就可以看到ActionBar不再显示了,如下图所示:
修改Action Bar的图标和标题
默 认情况下,系统会使用<application>或者<activity>中icon属性指定的图片来作为ActionBar的 图标,但是我们也可以改变这一默认行为。如果我们想要使用另外一张图片来作为ActionBar的图标,可以在<application>或 者<activity>中通过logo属性来进行指定。比如项目的res/drawable目录下有一张weather.png图片,就可以 在AndroidManifest.xml中这样指定:>
1 <activity 2 android:name="com.example.actionbartest.MainActivity" 3 android:logo="@drawable/weather" > 4 </activity>
现在重新运行一下程序,效果如下图所示:
OK,ActionBar的图标已经修改成功了,那么标题中的内容该怎样修改呢?其实也很简单,使用label属性来指定一个字符串就可以了,如下所示:
1 <activity 2 android:name="com.example.actionbartest.MainActivity" 3 android:label="天气" 4 android:logo="@drawable/weather" > 5 </activity>
添加Action按钮
那么我们先来看下menu资源文件该如何定义,代码如下所示:
1 <menu xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 tools:context="com.example.actionbartest.MainActivity" > 4 5 <item 6 android:id="@+id/action_compose" 7 android:icon="@drawable/ic_action_compose" 8 android:showAsAction="always" 9 android:title="@string/action_compose"/> 10 <item 11 android:id="@+id/action_delete" 12 android:icon="@drawable/ic_action_delete" 13 android:showAsAction="always" 14 android:title="@string/action_delete"/> 15 <item 16 android:id="@+id/action_settings" 17 android:icon="@drawable/ic_launcher" 18 android:showAsAction="never" 19 android:title="@string/action_settings"/> 20 21 </menu>
可以看到,这里我们通过三个<item>标签定义了三个Action按 钮。<item>标签中又有一些属性,其中id是该Action按钮的唯一标识符,icon用于指定该按钮的图标,title用于指定该按钮 可能显示的文字(在图标能显示的情况下,通常不会显示文字),showAsAction则指定了该按钮显示的位置,主要有以下几种值可选:always表 示永远显示在ActionBar中,如果屏幕空间不够则无法显示,ifRoom表示屏幕空间够的情况下显示在ActionBar中,不够的话就显示在 overflow中,never则表示永远显示在overflow中。接着,重写Activity的onCreateOptionsMenu()方法,代码如下所示:
@Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main, menu); return super.onCreateOptionsMenu(menu); }
这部分代码很简单,仅仅是调用了MenuInflater的inflate()方法来加载menu资源就可以了。现在重新运行一下程序,结果如下图所示:
- 当ActionBar中的剩余空间不足的时候,如果Action按钮指定的showAsAction属性是ifRoom的话,该Action按钮就会出现在overflow当中,此时就只有title能够显示了。
- 如果Action按钮在ActionBar中显示,用户可能通过长按该Action按钮的方式来查看到title的内容。
响应Action按钮的点击事件
当 用户点击Action按钮的时候,系统会调用Activity的onOptionsItemSelected()方法,通过方法传入的MenuItem参 数,我们可以调用它的getItemId()方法和menu资源中的id进行比较,从而辨别出用户点击的是哪一个Action按钮,比如:
@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_compose: Toast.makeText(this, "Compose", Toast.LENGTH_SHORT).show(); return true; case R.id.action_delete: Toast.makeText(this, "Delete", Toast.LENGTH_SHORT).show(); return true; case R.id.action_settings: Toast.makeText(this, "Settings", Toast.LENGTH_SHORT).show(); return true; default: return super.onOptionsItemSelected(item); } }
可以看到,我们让每个Action按钮被点击的时候都弹出一个Toast,现在重新运行一下代码,结果如下图所示:
通过Action Bar图标进行导航
我们可以通过调用setDisplayHomeAsUpEnabled()方法来启用ActionBar图标导航功能,比如:
1 @Override 2 protected void onCreate(Bundle savedInstanceState) { 3 super.onCreate(savedInstanceState); 4 setTitle("天气"); 5 setContentView(R.layout.activity_main); 6 ActionBar actionBar = getActionBar(); 7 actionBar.setDisplayHomeAsUpEnabled(true); 8 }
现在重新运行一下程序,结果如下图所示:
可以看到,在ActionBar图标的左侧出现了一个向左的箭头,通常情况下这都表示返回的意思,因此最简单的实现就是在它的点击事件里面加入finish()方法就可以了,如下所示:
1 @Override 2 public boolean onOptionsItemSelected(MenuItem item) { 3 switch (item.getItemId()) { 4 case android.R.id.home: 5 finish(); 6 return true; 7 …… 8 } 9 }
当点击ActionBar图标的时候,系统同样会调用onOptionsItemSelected()方法,并且此时的itemId是android.R.id.home,所以finish()方法也就是加在这里的了。
第二步需要在AndroidManifest.xml中配置父Activity,如下所示:
1 <activity 2 android:name="com.example.actionbartest.MainActivity" 3 android:logo="@drawable/weather" > 4 <meta-data 5 android:name="android.support.PARENT_ACTIVITY" 6 android:value="com.example.actionbartest.LaunchActivity" /> 7 </activity>
可以看到,这里通过meta-data标签指定了MainActivity的父Activity是LaunchActivity,在Android 4.1版本之后,也可以直接使用android:parentActivityName这个属性来进行指定,如下所示:
1 <activity 2 android:name="com.example.actionbartest.MainActivity" 3 android:logo="@drawable/weather" 4 android:parentActivityName="com.example.actionbartest.LaunchActivity" > 5 </activity>
第三步则需要对android.R.id.home这个事件进行一些特殊处理,如下所示:
1 @Override 2 public boolean onOptionsItemSelected(MenuItem item) { 3 switch (item.getItemId()) { 4 case android.R.id.home: 5 Intent upIntent = NavUtils.getParentActivityIntent(this); 6 if (NavUtils.shouldUpRecreateTask(this, upIntent)) { 7 TaskStackBuilder.create(this) 8 .addNextIntentWithParentStack(upIntent) 9 .startActivities(); 10 } else { 11 upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 12 NavUtils.navigateUpTo(this, upIntent); 13 } 14 return true; 15 ...... 16 } 17 }
其中,调用NavUtils.getParentActivityIntent()方法 可以获取到跳转至父Activity的Intent,然后如果父Activity和当前Activity是在同一个Task中的,则直接调用 navigateUpTo()方法进行跳转,如果不是在同一个Task中的,则需要借助TaskStackBuilder来创建一个新的Task。
添加Action View
为了声明一个ActionView,我们可以在menu资源中通过actionViewClass属性来指定一个控件,例如可以使用如下方式添加SearchView:
1 <menu xmlns:android="http://schemas.android.com/apk/res/android" > 2 3 <item 4 android:id="@+id/action_search" 5 android:icon="@drawable/ic_action_search" 6 android:actionViewClass="android.widget.SearchView" 7 android:showAsAction="ifRoom|collapseActionView" 8 android:title="@string/action_search" /> 9 ...... 10 11 </menu>
注意在showAsAction属性中我们还声明了一个collapseActionView,这个值表示该控件可以被合并成一个Action按钮。
如果你还希望在代码中对SearchView的属性进行配置(比如添加监听事件等),完全没有问题,只需要在onCreateOptionsMenu()方法中获取该ActionView的实例就可以了,代码如下所示:
1 @Override 2 public boolean onCreateOptionsMenu(Menu menu) { 3 MenuInflater inflater = getMenuInflater(); 4 inflater.inflate(R.menu.main, menu); 5 MenuItem searchItem = menu.findItem(R.id.action_search); 6 SearchView searchView = (SearchView) searchItem.getActionView(); 7 // 配置SearchView的属性 8 ...... 9 return super.onCreateOptionsMenu(menu); 10 }
在得到了SearchView的实例之后,就可以任意地配置它的各种属性了。关于SearchView的更多详细用法,可以参考官方文档 http://developer.android.com/guide/topics/search/search-dialog.html 。除此之外,有些程序可能还希望在ActionView展开和合并的时候显示不同的界面,其实我们只需要去注册一个ActionView的监听器就能实现这样的功能了,代码如下所示:
1 @Override 2 public boolean onCreateOptionsMenu(Menu menu) { 3 MenuInflater inflater = getMenuInflater(); 4 inflater.inflate(R.menu.main, menu); 5 MenuItem searchItem = menu.findItem(R.id.action_search); 6 searchItem.setOnActionExpandListener(new OnActionExpandListener() { 7 @Override 8 public boolean onMenuItemActionExpand(MenuItem item) { 9 Log.d("TAG", "on expand"); 10 return true; 11 } 12 13 @Override 14 public boolean onMenuItemActionCollapse(MenuItem item) { 15 Log.d("TAG", "on collapse"); 16 return true; 17 } 18 }); 19 return super.onCreateOptionsMenu(menu); 20 }
可以看到,调用MenuItem的 setOnActionExpandListener()方法就可以注册一个监听器了,当SearchView展开的时候就会回调 onMenuItemActionExpand()方法,当SearchView合并的时候就会调用 onMenuItemActionCollapse()方法,我们在这两个方法中进行相应的UI操作就可以了。
Overflow按钮不显示的情况
实 际上,在ViewConfiguration这个类中有一个叫做sHasPermanentMenuKey的静态变量,系统就是根据这个变量的值来判断手 机有没有物理Menu键的。当然这是一个内部变量,我们无法直接访问它,但是可以通过反射的方式修改它的值,让它永远为false就可以了,代码如下所 示:
1 @Override 2 protected void onCreate(Bundle savedInstanceState) { 3 ...... 4 setOverflowShowingAlways(); 5 } 6 7 private void setOverflowShowingAlways() { 8 try { 9 ViewConfiguration config = ViewConfiguration.get(this); 10 Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey"); 11 menuKeyField.setAccessible(true); 12 menuKeyField.setBoolean(config, false); 13 } catch (Exception e) { 14 e.printStackTrace(); 15 } 16 }
这里我们在onCreate()方法的最后调用了setOverflowShowingAlways()方法,而这个方法的内部就是使用反射的方式将sHasPermanentMenuKey的值设置成false,现在重新运行一下代码,结果如下图所示:
让Overflow中的选项显示图标
其 实,overflow中的Action按钮应不应该显示图标,是由MenuBuilder这个类的setOptionalIconsVisible方法来 决定的,如果我们在overflow被展开的时候给这个方法传入true,那么里面的每一个Action按钮对应的图标就都会显示出来了。调用的方法当然 仍然是用反射了,代码如下所示:
1 @Override 2 public boolean onMenuOpened(int featureId, Menu menu) { 3 if (featureId == Window.FEATURE_ACTION_BAR && menu != null) { 4 if (menu.getClass().getSimpleName().equals("MenuBuilder")) { 5 try { 6 Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE); 7 m.setAccessible(true); 8 m.invoke(menu, true); 9 } catch (Exception e) { 10 } 11 } 12 } 13 return super.onMenuOpened(featureId, menu); 14 }
可以看到,这里我们重写了一个onMenuOpened()方法,当overflow被展开的时候就会回调这个方法,接着在这个方法的内部通过返回反射的方法将MenuBuilder的setOptionalIconsVisible变量设置为true就可以了。