回到首页

输入关键词,多个关键词用空格分隔。

← 编程语言与英语 · 系列目录

Android 开发笔记 1 - ActionBar

约 8 分钟
本页目录

因为在项目中需要使用 ActionBar 进行一些自定义操作以提高整体的视觉效果,所以研究了一下 ActionBar 的用法,在这里记录一下。


隐藏 ActionBar

比较简单的方式就是换主题,在 Android 5 中就是在对应的 xml 文件里修改 android:theme 字段,或者也可以直接在 Activity 中用以下代码来隐藏:

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

修改图标和标题

这一部分的操作都需要在 AndroidManifest.xml 中进行,而对于标题的绑定一般来说是在 strings.xml 里进行统一管理

<activity
  android:name=".ui.MainActivity"
  android:label="@string/title_activity_main" >
</activity>

修改标题,就是修改 label 属性,需要添加图标的话,就是添加一个 android:logo 字段即可

添加按钮

ActionBar还可以根据应用程序当前的功能来提供与其相关的Action按钮,这些按钮都会以图标或文字的形式直接显示在ActionBar上。当然,如果按钮过多,ActionBar上显示不完,多出的一些按钮可以隐藏在overflow里面(最右边的三个点就是overflow按钮),点击一下overflow按钮就可以看到全部的Action按钮了。

当Activity启动的时候,系统会调用Activity的 onCreateOptionsMenu()方法来取出所有的Action按钮,我们只需要在这个方法中去加载一个menu资源,并把所有的Action按钮都定义在资源文件里面就可以了。像下面这样

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="lgm.cmu.spotagram.ui.MainActivity">

    <item android:id="@+id/action_search"
        android:title="Search"
        android:icon="@drawable/ic_check"
        app:showAsAction="always" />

    <item android:id="@+id/action_nearby"
        android:title="Near By"
        android:logo="@drawable/ic_cast_dark"
        app:showAsAction="always" />

    <item android:id="@+id/action_settings"
        android:title="Settings"
        android:orderInCategory="100"
        app:showAsAction="never" />
</menu>

这里主要注意一下 showAsAction 属性即可:always 表示永远显示在 ActionBar 中,如果屏幕空间不够则无法显示,ifRoom 表示屏幕空间够的情况下显示在 ActionBar 中,不够的话就显示在 overflow 中,never 则表示永远显示在 overflow 中

然后重写对应 Activity 的 onCreateOpitonsMenu() 方法

@Override
public boolean onCreateOptionsMenu(Menu menu){
   MenuInflater inflater = getMenuInflater();
   inflater.inflate(R.menu.menu_main, menu);
   return super.onCreateOptionsMenu(menu);
}

是显示图标还是文字这个大家可以根据具体的需求来做,但是给每个 item 一个 title 属性是比较好的习惯,因为:

  • 当 ActionBar 中的剩余空间不足的时候,如果 Action 按钮指定的 showAsAction 属性是 ifRoom 的话,该 Action 按钮就会出现在 overflow 当中,此时就只有 title 能够显示了。
  • 如果 Action 按钮在 ActionBar 中显示,用户可能通过长按该 Action 按钮的方式来查看到 title 的内容。

给按钮添加点击事件

当用户点击 Action 按钮的时候,系统会调用 Activity 的 onOptionsItemSelected() 方法,通过方法传入的 MenuItem 参数,我们可以调用它的 getItemId() 方法和 menu 资源中的 id 进行比较,从而辨别出用户点击的是哪一个 Action 按钮

@Override
public boolean onOptionsItemSelected(MenuItem item) {
   Intent intent;
   switch (item.getItemId()){
       case R.id.action_search:
           Toast.makeText(this, "Search", Toast.LENGTH_SHORT).show();
           return true;
       case R.id.action_nearby:
           intent = new Intent(MainActivity.this, NearByActivity.class);
           startActivity(intent);
           return true;
       case R.id.action_settings:
           intent = new Intent(MainActivity.this, SettingsActivity.class);
           startActivity(intent);
           return true;
       case R.id.action_aboutme:
           intent = new Intent(MainActivity.this, AboutMeActivity.class);
           startActivity(intent);
           return true;
       default:
           return super.onOptionsItemSelected(item);
   }
}

通过 ActionBar 进行导航

除了通过 back 按钮,同样可以在 ActionBar 上实现导航,只需要调用 setDisplayHomeAsUpEnabled() 方法启用即可:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_near_by);
   // 重点是这行
   getSupportActionBar().setDisplayHomeAsUpEnabled(true);

	 // 以下是其他代码
   initComponments();
   initViews();
   initListeners();
}

这样一来就会出现一个向左的箭头,我们给它加上点击事件,注意这里一定要是 android.R.id.home 才可以:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
   switch (item.getItemId()){
       case android.R.id.home:
           finish();
           return true;
       default:
           return super.onOptionsItemSelected(item);
   }
}

但这样的用法实际上和 back 键没啥区别,不过其设计的初衷是支持层次调用的,比如说:

上图中的 Conversation List 是收件箱的主界面,现在我们点击第一封邮件会进入到Conversation1 details 界面,然后点击下一封邮件会进入到 Conversation 2 details 界面,再点击下一封邮箱会进入到 Conversation3 details 界面。好的,这个时候如果我们按下 Back 键,应该会回到 Conversation 2 details 界面,再按一次 Back 键应该回到 Conversation1 details 界面,再按一次 Back 键才会回到 Conversation List。而 ActionBar 导航则不应该表现出这种行为,无论我们当前在哪一个 Conversation details 界面,点击一下导航按钮都应该回到 Conversation List 界面才对。

实现标准的ActionBar导航功能只需三步走。

第一步我们已经实现了,就是调用setDisplayHomeAsUpEnabled()方法,并传入true。

第二步需要在AndroidManifest.xml中配置父Activity,如下所示:

<activity
    android:name="com.example.actionbartest.MainActivity"
    android:logo="@drawable/weather" >
    <meta-data
        android:name="android.support.PARENT_ACTIVITY"
        android:value="com.example.actionbartest.LaunchActivity" />
</activity>

可以看到,这里通过meta-data标签指定了 MainActivity 的父 Activity 是LaunchActivity,在 Android 4.1 版本之后,也可以直接使用android:parentActivityName 这个属性来进行指定,如下所示:

<activity
    android:name="com.example.actionbartest.MainActivity"
    android:logo="@drawable/weather"
    android:parentActivityName="com.example.actionbartest.LaunchActivity" >
</activity>

第三步则需要对android.R.id.home这个事件进行一些特殊处理,如下所示:

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

其中,调用NavUtils.getParentActivityIntent()方法可以获取到跳转至父Activity的Intent,然后如果父Activity和当前Activity是在同一个Task中的,则直接调用navigateUpTo()方法进行跳转,如果不是在同一个Task中的,则需要借助TaskStackBuilder来创建一个新的Task。

这样,就按照标准的规范成功实现ActionBar导航的功能了。

添加 Action View

ActionView 是一种可以在 ActionBar 中替换 Action 按钮的控件,它可以允许用户在不切换界面的情况下通过 ActionBar 完成一些较为丰富的操作。比如说,你需要完成一个搜索功能,就可以将 SeachView 这个控件添加到 ActionBar 中。在我的这个应用中,将提供在搜索地点功能。

为了声明一个ActionView,我们可以在 menu 资源中通过 actionViewClass 属性来指定一个控件,例如可以使用如下方式添加 SearchView:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
	  xmlns:appcompat="http://schemas.android.com/apk/res-auto"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="lgm.cmu.spotagram.ui.MainActivity">

    <item android:id="@+id/action_search"
        android:title="Search"
        app:showAsAction="ifRoom|collapseActionView"
        appcompat:actionViewClass="android.support.v7.widget.SearchView"/>

    ....
</menu>

注意在 showAsAction 属性中我们还声明了一个 collapseActionView,这个值表示该控件可以被合并成一个 Action 按钮。然后需要在对应的 Activity 中添加方法:

@Override
public boolean onCreateOptionsMenu(Menu menu){
   MenuInflater inflater = getMenuInflater();
   inflater.inflate(R.menu.menu_main, menu);

   MenuItem searchItem = menu.findItem(R.id.action_search);
   SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
   

   searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
       @Override
       public boolean onQueryTextChange(String queryText) {
           Toast.makeText(getApplicationContext(), "onQueryTextChange:" + queryText, Toast.LENGTH_SHORT).show();
           return true;
       }

       @Override
       public boolean onQueryTextSubmit(String queryText) {
           Toast.makeText(getApplicationContext(), "onQueryTextSubmit:" + queryText, Toast.LENGTH_SHORT).show();
           return true;
       }
   });

   MenuItemCompat.OnActionExpandListener expandListener = new OnActionExpandListener() {
       @Override
       public boolean onMenuItemActionCollapse(MenuItem item) {
           Toast.makeText(getApplicationContext(), "onMenuItemActionCollapse", Toast.LENGTH_SHORT).show();
           return true;
       }

       @Override
       public boolean onMenuItemActionExpand(MenuItem item) {
           Toast.makeText(getApplicationContext(), "onMenuItemActionExpand", Toast.LENGTH_SHORT).show();
           return true;
       }
   };


   MenuItemCompat.setOnActionExpandListener(searchItem, expandListener);

   return super.onCreateOptionsMenu(menu);
}

这里同时也注册了监听器,所以只要在监听器中进行时间处理即可

Overflow 显示问题

overflow 按钮的显示情况和手机的硬件情况是有关系的,如果手机没有物理 Menu 键的话, overflow 按钮就可以显示,如果有物理 Menu 键的话,overflow 按钮就不会显示出来。

根据这个原理,我们只要默认无论如何都是没有 menu 按键即可。在 ViewConfiguration 这个类中有一个叫做 sHasPermanentMenuKey 的静态变量,系统就是根据这个变量的值来判断手机有没有物理 Menu 键的。当然这是一个内部变量,我们无法直接访问它,但是可以通过反射的方式修改它的值,让它永远为 false 就可以了,代码如下所示

@Override
protected void onCreate(Bundle savedInstanceState) {
	......
	setOverflowShowingAlways();
}

private void setOverflowShowingAlways() {
	try {
		ViewConfiguration config = ViewConfiguration.get(this);
		Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
		menuKeyField.setAccessible(true);
		menuKeyField.setBoolean(config, false);
	} catch (Exception e) {
		e.printStackTrace();
	}
}

Overflow 中的选项显示图标

如果你点击一下overflow按钮去查看隐藏的Action按钮,你会发现这部分Action按钮都是只显示文字不显示图标的。这是官方的默认效果,Google 认为隐藏在 overflow 中的 Action 按钮都应该只显示文字。当然,如果你认为这样不够美观,希望在 overflow 中的 Action 按钮也可以显示图标,我们仍然可以想办法来改变这一默认行为。
其实,overflow 中的 Action 按钮应不应该显示图标,是由 MenuBuilder 这个类的 setOptionalIconsVisible 方法来决定的,如果我们在 overflow 被展开的时候给这个方法传入 true,那么里面的每一个 Action 按钮对应的图标就都会显示出来了。调用的方法当然仍然是用反射了,代码如下所示

@Override
public boolean onMenuOpened(int featureId, Menu menu) {
	if (featureId == Window.FEATURE_ACTION_BAR && menu != null) {
		if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
			try {
				Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
				m.setAccessible(true);
				m.invoke(menu, true);
			} catch (Exception e) {
			}
		}
	}
	return super.onMenuOpened(featureId, menu);
}

可以看到,这里我们重写了一个 onMenuOpened() 方法,当 overflow 被展开的时候就会回调这个方法,接着在这个方法的内部通过返回反射的方法将 MenuBuilder 的 setOptionalIconsVisible 变量设置为 true 就可以了。

参考资料

相关文章