《Android4高级编程》节选之实例分析
操作栏的操作、应用程序菜单和弹出式菜单是访问菜单的新方法,并针对现代的触屏设备进行了优化。作为AndroidUI模型检查的一部分,本文着眼于如何在你的应用程序中创建和使用它们。特别地,你将学习如何确定操作栏上哪个菜单项应该作为一个操作来显示。
在没有Activity的情况下,Android也为应用程序提供了一些技术来和用户进行通信。你将学习在不打断处于活动状态的应用程序的情况下,如何使用Notification和Toast来警示和更新用户。
Toast是一个短暂的、非模态的对话框机制,用来在不获取当前活动的应用程序焦点的情况下向用户展示信息。你将学习在任意的应用程序组件上显示Toast,它会向用户发送一条在屏幕上显示的不显眼的消息。
Toast是静默而短暂的,Notification则代表一个更加健壮的机制来提醒用户。在许多情况下,当用户不使用手机时,手机会放在口袋里或桌子上,在没有响铃、震动或闪烁的时候,它都会保持安静。如果用户错过这些警示,状态栏的图标就会指示发生了事件。通过使用Notification,所有这些引人注目的事件对于Android应用程序都是可用的。
你还将学习当Notification出现在通知托盘中时,如何自定义该Notification的外观和功能。通知托盘为用户提供了一种机制,能够在不需要先打开应用程序的情况下与该应用程序进行交互。
(一).操作栏简介
如图1所示的操作栏组件是在Android 3.0(API level 11)中引入的。它是一个导航面板,代替了每个Activity上方的标题栏,并正式成为了一个通用的Android设计模式。
可以隐藏操作栏,但最好的方式是保留它并自定义,使它能够适合应用程序的样式和导航要求。在应用程序中,操作栏可以添加到每个Activity中,它的作用是在应用程序之间和特定应用程序的Activity内提供一个一致性的UI外观。
操作栏为品牌打造、导航和在Activity内执行的关键操作提供了一致的框架。虽然操作栏为在应用程序间呈现这种一致性的功能提供了一个框架,但下面的章节还将描述如何选择哪些选项适合你的应用程序,以及应该如何实现它们。
如果任意的Activity使用了(默认的)Theme.Holo主题,并且它的应用程序的目标(或最小)SDK版本为11或者更高,那么它的操作栏是启用的。
程序清单1显示了在不修改默认主题的情况下,通过设置目标SDK为Android 4.0.3(API level 15)启用操作栏。
要想在运行时设置操作栏的可见性,可以使用它的show和hide方法:
ActionBar actionBar = getActionBar();
// 隐藏操作栏
actionBar.hide();
// 显示操作栏
actionBar.show();
作为一种选择,也可以应用一个不包含操作栏的主题,例如Theme.Holo.NoActionBar主题,如程序清单2所示。
android:name=".MyNonActionBarActivity"
android:theme="@android:style/Theme.Holo.NoActionBar">
通过设置android:windowActionBar的样式属性为false,可以创建或者自定义移除操作栏的主题。
当一个Activity应用了一个不含有操作栏的主题后,将不能通过程序在运行时显示它,调用getActionBar会返回null。
(二).自定义操作栏
除了能够控制这个标准功能的实现,每个应用程序还可以修改操作栏的外观,同时保持相同的一致性行为和常规的布局。
操作栏的一个主要的目的就是提供应用程序间统一的UI。因此,尽管可以自定义操作栏来提供自己应用程序的商标和标识,但这种自定义选项行为还是刻意有所限制的。
通过指定图像(如果有的话)出现在最左边,可以控制品牌打造,此外还可以控制应用程序的标题显示以及背景Drawable的使用。图2显示了自定义的操作栏,使用徽标位图来标识应用程序以及使用一个渐变Drawable作为背景图像。
默认情况下,操作栏是通过使用应用程序或者Activity中指定的android:icon属性来显示Drawable的,旁边则是黑色背景上相应的android:label属性。
可以使用android:logo属性指定一个可选的图形。和正方形的图标不同,对于徽标图形的宽度是没有限制的,但最好限制它的宽度大概为图标宽度的两倍。
徽标图像通常为应用程序提供顶层的品牌打造,因此在使用徽标图像的时候最好隐藏标题标签。可以通过在运行时设置setDisplayShowTitleEnabled 方法为false来实现这一点。
ActionBar actionBar = getActionBar();
actionBar.setDisplayShowTitleEnabled(false);
在图标和徽标图像都提供的地方,可以通过使用setDisplayUseLogoEnabled方法在它们之间转换。
actionBar.setDisplayUseLogoEnabled(displayLogo);
如果选择隐藏图标和徽标,可以通过设置setDisplayShowHomeEnabled为false来实现。
actionBar.setDisplayShowHomeEnabled(false);
还可以使用图标和标题文本来提供导航和上下文线索。在运行时,使用setTitle和setSubTitle方法来修改图标旁边显示的文本,如程序清单3和图3所示。
这些文本字符串用来描述用户在应用程序中的位置和他们工作时的上下文情况。这在使用Fragment而非传统的Activity栈方式来改变上下文时是非常有用的。下面的小节将提供更多和导航选项相关的细节。
自定义背景
操作栏默认的背景颜色取决于底层的主题。Android原生的操作栏背景是透明的,使用Holo主题后的背景颜色为黑色。
使用setBackgroundDrawable方法,可以指定任意Drawable作为操作栏的背景图像,如程序清单4所示。
ActionBar actionBar = getActionBar();
Resources r = getResources();
Drawable myDrawable = r.getDrawable(R.drawable.gradient_header);
actionBar.setBackgroundDrawable(myDrawable);
代码片段PA4AD_Ch10_ActionBar/src/ActionBarActivity.java
操作栏会拉伸你提供的图像,因此最好是创建一个可拉伸的Drawable,通常使用通过9-patch或者XML定义的Drawable。
通常情况下,操作栏会在Activity的顶部保留一部分空间,Activity布局则会填充到剩余的空间中。作为一种选择,通过请求窗口特性为FEATURE_ACTION_BAR_OVERLAY,可以在Activity布局之上重叠显示操作栏。
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
setContentView(R.layout.main);
}
当启用重叠模式时,操作栏会浮动在Activity之上,有可能掩盖布局顶部的内容。
操作栏最初是在Android 3.0中引入的这个平台版本专注于在平板设备上提供极佳的用户体验。Android 4.0(API level 14)则试图优化很多起初为平板电脑所设计的功能,使它们可以在更小的设备或者智能设备上使用。
对于操作栏来说,这就意味着拆分操作栏的引入。可以通过在应用程序或者Activity的清单节点中设置android:uiOptions属性值为splitActionBarWhenNarrow来启用拆分操作栏,如程序清单5所示。
android:label="My Activity"
android:name=".ActionBarActivity"
android:logo="@drawable/ic_launcher"
android:uiOptions="splitActionBarWhenNarrow">
代码片段PA4AD_Ch10_ActionBar/AndroidManifest.xml
在窄屏的支持设备(如竖屏模式下的智能手机)上,启用拆分操作栏模式可以让系统将操作栏拆分成多个独立的部分。图4显示了一个示例,带有品牌打造和导航部分的操作栏布局在屏幕的顶部,操作部分则与屏幕的底部对齐。
操作栏的布局是由运行时计算和执行的,并且可能根据宿主设备的方向和在运行时对操作栏进行的配置而改变。
(三)自定义操作栏来控制应用程序的导航行为
操作栏引入了很多选项,用于在应用程序内部提供一致且可预见的导航功能。从广义来讲,这些选项可以分为两类:
● 应用程序图标 应用程序的图标或者徽标用来提供一种统一的导航路径,通常是通过将应用程序复位到它的主Activity。还可以配置图标,使其能够实现向“上”移动上下文的一个层次(即回到上一层的界面)。
● Tab键和下拉列表 操作栏支持内置Tab键或者下拉列表,用来代替Activity中可见的Fragment。
图标导航可以认为是Activity栈的导航方法,而Tab键和下拉列表则用作Activity内的Fragment过渡。实际上,当应用程序图标被单击或者一个Tab键改变时所执行的操作依赖于实现UI的方式。选择应用程序的图标应该像Activity切换一样改变UI的整体上下文,而改变Tab键或者选择一个下拉列表应该只改变显示的数据。
大多数情况下,应用程序图标应该充当返回到“主”Activity的快捷方式,通常是回到Activity栈的根部。要想应用程序图标能够被单击,需要调用操作栏的setHomeButtonEnabled方法:
actionBar.setHomeButtonEnabled(true);
应用程序图标/徽标的单击由系统作为一个特殊的菜单项单击事件而广播。菜单项的选择是由Activity中的onOptionsItemSelected处理程序进行处理的,其中会把传入的菜单项参数的ID设置为android.R.id.home,如程序清单6所示。
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case (android.R.id.home) :
Intent intent = new Intent(this, ActionBarActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
代码片段PA4AD_Ch10_ActionBar/src/ActionBarActivity.java
传统来说,Android应用程序启动一个新的Activity来进行不同上下文间的过渡。反过来,按下返回按钮会关闭活动Activity并且返回到上一个界面。
想要补充以上这种行为,可以配置应用程序图标以提供“向上”的导航功能。
返回按钮通常将用户从他们可见的上下文(通常以Activity的形式)中返回,有效地反转了到达当前Activity或屏幕所遵循的导航路径。这可能会导致实现应用程序结构中的“兄弟姐妹”界面间的导航。
相反,“向上”导航通常将用户移到当前Activity的父界面。因此,它能将用户移到他们之前未访问过的界面。这对那些有多个入口点的应用程序尤其有用,允许用户在应用程序中导航,而无须返回到父应用程序。
要想启用应用程序图标的“向上”导航功能,可以调用操作栏的setDisplayHomeAsUpEnabled 方法。
actionBar.setDisplayUseLogoEnabled(false);
actionBar.setDisplayHomeAsUpEnabled(true);
这样会在应用程序图标上呈现一个重叠的“向上”图形,如图5所示。在希望启用向上导航的时候,最好使用图标而不是徽标。
导航的处理由你实现。操作栏依然会触发菜单项的onOptionsItemSelected处理程序,并使用android.R.id.home作为标识符,如程序清单6所示。
这种行为也引入了一定的危险,特别是当用户有多种方式访问一个特殊的Activity时。如果有顾虑的话,向上行为就应该和返回按钮的行为一致。在所有的情况下,导航行为应该是可预见的。
2. 使用导航Tab键
除了应用程序图标导航,操作栏还提供了导航Tab键和下拉列表。注意,一次只可以启用一种导航形式。这些导航选项旨在与Fragment密切搭配使用,并提供一种机制,就是通过替换可见的Fragment达到改变当前Activity内容的目的。导航Tab键(如图6所示)可以被认为是TabWidget控件的一种替代。
要想配置操作栏以显示Tab键,需要调用它的setNavigationMode方法,并指定参数为ActionBar.NAVIGATION_MODE_TABS。Tab键应该提供应用程序的上下文信息,因此最好同样禁用标题文本,如程序清单7所示。
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
actionBar.setDisplayShowTitleEnabled(false);
代码片段PA4AD_Ch10_ActionBar/src/ActionBarTabActivity.java
Tab键导航是通过Action Bar的addTab方法添加到操作栏上的,如程序清单8所示。首先创建一个新的Tab并使用它的setText和setIcon方法来设置要显示的标题和图像。另外,可以使用setCustomView方法,用你自定义的View代替操作栏标准的文本和图像布局。
Tab tabOne = actionBar.newTab();
tabOne.setText("First Tab")
.setIcon(R.drawable.ic_launcher)
.setContentDescription("Tab the First")
.setTabListener(
new TabListener
(this, R.id.fragmentContainer, MyFragment.class));
actionBar.addTab(tabOne);
代码片段PA4AD_Ch10_ActionBar/src/ActionBarTabActivity.java
Tab键的切换是通过TabListener来处理的,它允许创建Fragment事务来响应Tab键的选中、未选中和重新选中的操作,如程序清单9所示。注意,你不需要执行在每个处理程序中创建的Fragment事务操作栏会在必要的时候为你执行它。
public static class TabListener
implements ActionBar.TabListener {
private MyFragment fragment;
private Activity activity;
private Class
private int fragmentContainer;
public TabListener(Activity activity, int fragmentContainer,
Class
this.activity = activity
this.fragmentContainer = fragmentContainer;
this.fragmentClass = fragmentClass;
}
// 当一个新的Tab键被选中时调用
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if (fragment == null) {
String fragmentName = fragmentClass.getName();
fragment =
(MyFragment)Fragment.instantiate(activity, fragmentName);
ft.add(fragmentContainer, fragment, null);
fragment.setFragmentText(tab.getText());
} else {
ft.attach(fragment);
}
}
// 当其他的Tab键被选中时,在当前选中的Tab键上调用
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
if (fragment != null) {
ft.detach(fragment);
}
}
// 当选中的Tab键被再次选中时调用
public void onTabReselected(Tab tab, FragmentTransaction ft) {
//TODO:当选中的Tab键被再次选中时响应
}
}
代码片段PA4AD_Ch10_ActionBar/src/ActionBarTabActivity.java
在这个Tab Listener中,基本的工作流程是初始化、配置新的Fragment然后在onTabSelected处理程序中将此Fragment添加到布局中。在Tab键未选中时,它关联的Fragment应该从布局中分离,当其Tab键被重新选中时,该Fragment应该被回收利用。
作者Reto Meier,Google Android团队的一名Android开发人员倡导者,帮助Android开发人员创建最优秀的应用程序。Reto是一位经验丰富的软件开发人员,拥有逾10年的GUI应用程序开发经验。进入Google之前,他曾在多种行业中工作过,包括海洋石油、天然气以及金融业。
本文节选自《Android 4高级编程》(第3版),Reto Meier著,佘建伟、赵凯译,清华大学出版社出版。
查看评论 回复