第 16 章 操作栏

请参考教材,全面理解和完成本章节内容... ...

复制工程ch12,将工程目录改名为ch16.

在Honeycomb版本系统中,Android引入了全新的操作栏。操作栏不仅取代了用来显示标题和应用图标的传统标题栏(title bar),还带来了更多其他功能,例如,安置菜单选项、配置应用图标作为导航按钮,等等。

本章,我们将为CriminalIntent应用创建一个菜单,并在其中提供可供用户新增crime记录的菜单项,然后让应用的图标支持向上的导航操作,如图16-1所示。

图16-1 创建选项菜单文件

16.1 选项菜单

可显示在操作栏上的菜单被称作选项菜单。选项菜单提供了一些选项,用户选择后可以弹出一个全屏activity界面,也可以退出当前应用。新增一条crime记录就是一个很好的例子。而从列表中删除crime记录的操作,使用上下文菜单(context menu)来处理则更合适。因为删除记录的操作需要知道上下文信息,即应该删除哪一条crime记录。第18章,我们将学习如何使用上下文菜单。

本章的选项菜单以及第18章的上下文菜单均需要一些字符串资源。参照代码清单16-1,将这两章所需的字符串资源添加到string.xml文件中。虽然现在可能还不太明白这些新增的字符串资源,但有必要现在就完成添加。这样,在需要它们的时候,就可以直接使用,而无需停下手头的工作。

代码清单16-1 为菜单添加字符串资源(res/values/strings.xml)

在操作栏上放置选项菜单虽然比较新颖,但选项菜单本身早在Android问世的时候就已经存在了,如图16-2所示。

图16-2 Honeycomb以前的选项菜单

还不错,选项菜单基本没什么兼容性问题。不过,代码虽然都是一样的,根据不同的API级别,各设备呈现选项菜单的方式会稍有不同。本章后续学习过程中,我们会再来看看可能会涉及到的兼容性、选项菜单以及操作栏问题。

16.1.1 在XML文件中定义选项菜单

菜单是一种类似于布局的资源。创建一个定义菜单的XML文件,然后将其放置在项目的res/menu目录下。Android会自动生成该XML文件的对应资源ID,以供在代码中生成菜单之用。

在工程中,首先找到res目录下menu子目录。然后右键单击menu子目录,选择New → Menu Resource File菜单项。在弹出的窗口界面,确保选择了Menu文件资源类型,并命名新建文件为fragment_crime_list.xml,如图16-3所示。

16-3 创建选项菜单文件

打开新建的fragment_crime_list.xml, 参照代码清单16-2,添加新的item元素。

代码清单16-2 创建菜单资源(fragment_crime_list.xml)

showAsAction属性用于指定菜单选项是显示在操作栏上,还是隐藏到溢出菜单(overflow menu)中。该属性当前设置为ifRoomwithText的一个组合值。因此,只要空间足够,菜单项图标及其文字描述都会显示在操作栏上。如空间仅够显示菜单项图标,则不会显示文字描述。如空间大小不够任何一项显示,则菜单项会被转移隐藏到溢出菜单中。(复制图标文件ic_menu_add.png后,在项目里粘贴到drawable目录里)

如何访问溢出菜单取决于具体设备。如设备具有物理菜单键,则必须单击该键查看溢出菜单。目前,大多数较新设备都已取消物理菜单键,因此可通过操作栏最右端带有三个点的图标来访问溢出菜单,如图16-4所示。

16-4 操作栏中的溢出菜单

属性showAsAction还有另外两个可选值:alwaysnever。不推荐使用always,应尽量使用更为方便的ifRoom属性值,让操作系统决定如何显示菜单项。对于那些很少用到的菜单项,使用never是个不错的选择。总的来说,为避免看到混乱的用户界面,只应将用户经常使用的菜单项放置在操作栏上。

16.1.2 创建选项菜单

在代码中,Activity类提供了管理选项菜单的回调函数。在需要选项菜单时,Android会调用ActivityonCreateOptionsMenu(Menu)方法。

然而,按照CriminalIntent应用的设计, 选项菜单相关的回调函数需在fragment而非activity里实现。不用担心,Fragment也有自己的一套选项菜单回调函数。稍后,我们会在CrimeListFragment中实现这些方法。以下为创建选项菜单和响应菜单项选择事件的两个回调方法:

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)

public boolean onOptionsItemSelected(MenuItem item)

在CrimeListFragment.java中,覆盖onCreateOptionsMenu(Menu, MenuInflater)方法,inflate在fragment_crime_list.xml中定义的菜单,如代码清单16-3所示。

代码清单16-3 Inflating选项菜单(CrimeListFragment.java)

在以上方法中,调用MenuInflater.inflate(int, Menu)方法并传入菜单文件的资源ID,我们将文件中定义的菜单项目填充到Menu实例中。

注意,我们调用了超类的onCreateOptionsMenu( )方法。虽然不是必须的,但作为一种约定的开发规范,我们推荐这么做。通过该超类方法的调用,任何超类定义的选项菜单功能在子类方法中也能获得应用。不过,这里的超类方法调用仅仅是遵循约定而已,因为Fragment超类的onCreateOptionsMenu()方法什么也没做。

FragmentonCreateOptionsMenu(Menu, MenuInflater)方法是由FragmentManager负责调用的。因此,当activity接收到来自操作系统的onCreateOptionsMenu()方法回调请求时,我们必须明确告诉FragmentManager:其管理的fragment应接收onCreateOptionsMenu()方法的调用指令。

要通知FragmentManager,需调用以下方法:

public void setHasOptionsMenu(boolean hasMenu)

CrimeListFragment.onCreate()方法中,让FragmentManager知道CrimeListFragment需接收选项菜单方法回调的方法,如代码清单16-4所示。

代码清单16-4 调用SethasOptionsMenu方法(CrimeListFragment.java)

运行CriminalIntent应用,查看新创建的选项菜单,如图16-5所示。

16-5 显示在操作栏上的菜单项图标

菜单项标题怎么没有显示?大多数设备在竖直模式下屏幕空间都有限,因此,应用的操作栏上只够显示菜单项图标。点击操作栏上的菜单图标,可弹出菜单标题,如图16-6所示。

16-6 长按操作栏上的图标,显示菜单项标题

水平模式下,操作栏上会有足够的空间同时显示菜单图标和菜单项标题,如图16-7所示。

16-7 同时显示在操作栏上的菜单图标和菜单标题

16.1.3 响应菜单项选择

为响应用户点击[新手记]菜单项,需实现新方法以添加新的Crime到crime数组列表中。在CrimeLab.java中,新增以下方法,实现添加Crime到数组列表中,如代码清单16-5所示。

代码清单16-5 添加新的crime(CrimeLab.java)

既然可以手动添加crime记录,也就没必要再让程序自动生成100条crime记录了。在CrimeLab.java中,删除生成随机crime记录的代码,如代码清单16-6所示。

代码清单16-6 再见,随机crime记录!(CrimeLab.java)

用户点击选项菜单中的菜单项时,fragment会收到onOptionsItemSelected(MenuItem)方法的回调请求。该方法接受的传入参数是一个描述用户选择的MenuItem实例。

尽管当前的选项菜单只包含一个菜单项,但通常菜单可包含多个菜单项。通过检查菜单项ID,可确定被选中的是哪一个菜单项,然后做出相应的响应。代码中使用的菜单项ID实际就是在菜单XML定义文件中赋予菜单项的资源ID。

在CrimeListFragment.java中,实现onOptionsItemSelected(MenuItem)方法响应菜单项的选择事件。在该方法中,创建一个新的Crime实例,并将其添加到CrimeLab中,然后启动一个CrimePagerActivity实例,让用户可以编辑新创建的Crime记录,如代码清单16-7所示。

代码清单16-7 响应菜单项选择事件(CrimeListFragment.java)

注意,onOptionsItemSelected(MenuItem)方法返回的是布尔值。一旦完成菜单项事件处理,应返回true值以表明已完成菜单项选择需要处理的全部任务。另外,case表达式中,如果菜单项ID不存在,默认的超类版本方法会被调用。

运行CriminalIntent应用,尝试使用选项菜单,添加一些crime记录并对它们进行编辑。

16.2 实现层级式导航

目前为止,CriminalIntent应用主要依靠后退键在应用内导航。使用后退键的导航又称为临时性导航,只能返回到上一次的用户界面。而 Ancestral navigation,有时也称为层级式导航(hierarchical navigation),可逐级向上在应用内导航。

Android可轻松利用操作栏上的应用图标实现层级式导航。也可利用应用图标实现直接回退至主屏,即逐级向上直至应用的初始界面。实际上,操作栏上的应用图标最初是用作Home键的。不过,Android现在只推荐利用应用图标,实现向上回退一级至当前activity的父界面。这样一来,应用图标实际上就起到了向上按钮的作用。

本节中,针对显示在CrimePagerActivity操作栏上的应用图标,我们将编码使其具有向上按钮的功能。点击该图标,可回退至crime列表界面。

16.2.1 启用应用图标的导航功能

通常,应用图标一旦启用了向上导航按钮的功能,在应用图标的左边会显示一个如图16-9所示的向左指向图标。

16-9 带有向上导航按钮的操作栏

为启用应用图标向上导航按钮的功能,并在fragment视图上显示向左的图标,须调用以下方法设置fragment的DisplayHomeAsUpEnabled属性:

public abstract void setDisplayHomeAsUpEnabled(boolean showHomeAsUp)

该方法来自于API 11级,因此需进行系统版本判断保证应用向下兼容,并使用@TargetApi(11)注解阻止Android Lint报告兼容性问题。

CrimeFragment.onCreateView(...)中,调用setDisplayHomeAsUpEnabled(true)方法,如代码清单16-8所示。

代码清单16-8 启用向上导航按钮(CrimeFragment.java)

注意,调用setDisplayHomeAsUpEnabled(...)方法只是让应用图标转变为按钮,并显示一个向左的图标而已。因此我们必须进行编码,实现点击按钮可向上逐级回退的功能。对于那些以API 11-13级别为目标版本开发的应用,应用图标已默认启用为向上按钮的功能,但仍需调用setDisplayHomeAsUpEnabled(true)方法,以在应用图标左边显示向左的指向图标。

在代码清单16-8中,我们对onCreateView(...)方法使用了@TargetApi注解。实际上只注解setDisplayHomeAsUpEnabled(true)方法的调用即可。不过,onCreateView(...)方法很快就会有一些特定API级别的代码加入,因此,这里我们选择直接注解整个onCreateView(...)实施方法。

注:代码清单16-8,如果采用原书的代码会报错,因为 getactivity().getActioBar() returns null in fragment。网上的解决方法和代码如下:

I think I found a solution. First of all I didn‘t even see an action bar so I did some searching and found many people saying to extend the activities with ActionBarActivity instead of FragmentActivity...but ActionBarActvity is deprecated now, did some more searching, found people saying to use AppCompatActivity. So I extended CrimePagerActivity and SingleFragmentActivity with AppCompatActivity. Then I found people solving the NullPointerException by using getSupportActionBar() instead of getActionBar(). It couldn‘t find getSupportActionBar() though, until casting with AppCompatActivity. So finally my code looks like this:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}

改为

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
((AppCompatActivity)getActivity()).getSupportActionBar()
.setDisplayHomeAsUpEnabled(true);
}

16.2.2 响应向上按钮

如同响应选项菜单项那样,可通过覆盖onOptionsItemSelected(MenuItem)方法的方式,响应已启用向上按钮功能的应用图标。因此,首先应通知FragmentManager:CrimeFragment将代表其托管activity实现选项菜单相关的回调方法。如同前面对CrimeListFragment的处理一样,在CrimeFragment.onCreate(...)方法中,调用setHasOptionsMenu(true)方法,如代码清单16-9所示。

代码清单16-9 开启选项菜单处理(CrimeFragment.java)

无需在XML文件中定义或生成应用图标菜单项。它已具有现成的资源ID:android.R.id.home。在CrimeFragment.java中,覆盖onOptionsItemSelected(MenuItem)方法,响应用户对该菜单项的点击事件,如代码清单16-10所示。

代码清单16-10 响应应用图标(Home键)菜单项(CrimeFragment.java)

为实现用户点击向上按钮返回至crime列表界面,我们可能会想到去创建一个intent,然后启动CrimePagerActivity实例,如以下实现代码:

Intent intent = new Intent(getActivity(), CrimeListActivity.class);

intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

startActivity(intent);

finish();

FLAG_ACTIVITY_CLEAR_TOP指示Android在回退栈中寻找指定activity的存在实例,如图16-10所示。如存在,则弹出栈中的所有其他activity,让启动的activity出现在栈顶,从而显示在屏幕上。

16-10 工作中的FLAG_ACTIVITY_CLEAR_TOP

然而,Android有更好的办法实现层级式导航:配合使用NavUtils便利类与manifest配置文件中的元数据。

先来处理元数据。打开AndroidManifest.xml文件,在CrimePagerActivity声明中添加新的meta-data属性,指定CrimePagerActivity的父类为CrimeListActivity,如代码清单16-11所示。

代码清单16-11 添加父activity元数据属性(AndroidManifest.xml)

把元数据标签想象为张贴在activity上的一个便利贴。类似这样的便利贴信息都保存在系统的PackageManager中。只要知道便利贴的名字,任何人都可以获取它的内容。也可创建自己的名-值(name-value)对以便在需要的时候获取它们。这种特别的名-值对由NavUtils类定义,这样它就能知道谁是指定activity的父类,配合以下NavUtils类方法一起使用尤其有用:

public static void navigateUpFromSameTask(Activity sourceActivity)

CrimeFragment.onOptionsItemSelected(...)方法中,首先通过调用NavUtils.getParentActivityName(Activity)方法,检查元数据中是否指定了父activity。如指定有父activity,则调用navigateUpFromSameTask(Activity)方法,导航至父activity界面。如代码清单16-12所示。

代码清单16-12 使用NavUtils类(CrimeFragment.java)

如元数据中未指定父activity,则为避免误导用户,无需再显示向左的箭头图标。回到onCreateView(...)方法中,在调用setDisplayHomeAsUpEnabled(true)方法前,先检查父activity是否存在,如代码清单16-13所示。

代码清单16-13 控制导航图标的显示(CrimeFragment.java)

为什么使用NavUtils类要好于手动启动activity?首先,NavUtils类的实现代码既简洁又优雅。其次,使用NavUtils类也可实现在manifest配置文件中统一管理activity间的关系。如果activity间的关系发生改变,无需费力地去修改Java代码,我们只要简单修改配置文件中的一行代码即可。

除此之外,使用NavUtils类还可保持层级关系处理与fragment的代码相分离。这样,即使在各个具有不同父类的activity中使用同一CrimeFragmentCrimeFragment依然能正常工作。

运行CriminalIntent应用。创建新的crime记录,然后点击应用图标,返回至crime列表界面。实际上,CriminalIntent应用两个层级的关系并不是太容易区分。但navigateUpFromSameTask(Activity)方法实现了向上导航的功能,使得用户可以轻松地向上导航一级至CrimePagerActivity的父类界面。

16.3 可选菜单项

本小节,利用前面学过的有关菜单、应用兼容性以及可选资源的知识,我们添加一个菜单项实现显示或隐藏CrimeListActivity操作栏的子标题。

16.3.1 创建可选菜单XML文件

对于使用Honeycomb之前系统版本的用户,只适用于操作栏的菜单项对他们来说应该是不可见的。因此,首先应创建可供API 11级以后的系统版本使用的可选菜单资源。在项目的res目录下创建一个menu-v11目录,然后将fragment_crime_list.xml文件复制到该目录中。

打开res/menu-v11/fragment_crime_list.xml文件,参照代码清单16-14,新增一个显示为Show Subtitle的菜单项。如空间足够,它将显示在操作栏上。

代码清单16-14 添加Show Subtitle菜单项(res/menu/fragment_crime_list.xml)

onOptionsItemSelected(...)方法中,设置操作栏的子标题以响应菜单项的单击事件,如代码清单16-15所示。

代码清单16-15 响应Show Subtitle菜单项单击事件(CrimeListFragment.java)

注意,这里只是在代码中使用@TargetApi(11)注解阻止Android Lint报告兼容性问题。而操作栏的相关代码并没有置于版本条件判断之中。在早期版本的设备上,R.id.menu_item_show_subtitle不会出现,自然也就不会调用操作栏相关代码,所以这里没必要处理设备兼容性问题。

在新设备上运行CriminalIntent应用,使用新增菜单显示子标题。然后在Froyo或Gingerbread设备上(虚拟设备或实体设备皆可)运行应用。点按菜单键,确认Show Subtitle没有显示。最后,添加新的crime记录,确认应用运行如前。

16.3.2 切换菜单项标题

操作栏上的子标题显示后,菜单项标题依然显示为“显示子标题”。如果菜单项标题的切换与子标题的显示或隐藏能够联动,用户体验会更好。

onOptionsItemSelected(...)方法中,选中菜单项后,检查子标题的显示状态并采取相应操作,如代码清单16-16所示。

代码清单16-16 实现菜单项标题与子标题的联动显示(CrimeListFragment.java)

如果操作栏上没有显示子标题,则应设置显示子标题,同时切换菜单项标题,使其显示为Hide Subtitle。如果子标题已经显示,则应设置其为null值,同时将菜单项标题切换回Show Subtitle。

运行CriminalIntent应用,确认菜单项标题与子标题的显示能够联动。

16.3.3 还有个问题

这个问题就是经典的设备旋转问题。子标题显示后,旋转设备,这时因为用户界面的重新生成,显示的子标题会消失。为解决此问题,需要一个实例变量记录子标题的显示状态,并且设置 保留CrimeListFragment,使得变量值在设备旋转后依然可用。

在CrimeListFragment.java中,添加一个布尔类型的成员变量,在onCreate(...)方法中保留CrimeListFragment并对变量进行初始化,如代码清单16-17所示。

代码清单16-17 保留CrimeListFragment并初始化变量(CrimeListFragment.java)

然后在onOptionsItemSelected(...)方法中,根据菜单项的选择设置对应的变量值,如代码清单16-18所示。

代码清单16-18 根据菜单项的选择设置subtitleVisible (CrimeListFragment.java)

现在需要查看设备旋转后是否应该显示子标题。在CrimeListFragment.java中,覆盖onCreateView(...)方法,根据变量mSubtitleVisible的值确定是否要设置子标题,如代码清单16-19所示。

代码清单16-19 根据变量mSubtitleVisible的值设置子标题(CrimeListFragment.java)

同时需要在onCreateOptionsMenu(...)方法中查看子标题的状态,以保证菜单项标题与之匹配显示,如代码清单16-20所示。

代码清单16-20 基于mSubtitleVisible变量的值,正确显示菜单项标题(CrimeListFragment.java)

运行CriminalIntent应用。显示子标题并旋转设备,可以看到子标题在重新创建的视图中依然能正确显示。

时间: 2024-08-13 16:41:11

第 16 章 操作栏的相关文章

C++ Primer Plus 第六版 第16章 string类和标准模板库

1.string实际上是模板具体化basic_string<char> 的一个typedef,有默认参数,所以省略了初始化参数 2.size_type是一个依赖于实现的整形 string将string::npos定义为字符串的最大长度 3.string类的构造函数P656 4.对于c-风格字符串,3种输入方法:cin>>   cin.getline(),cin.get 对于string   ,2种输入方法:cin>>,getline(cin,string对象) 5.st

java JDK8 学习笔记——第16章 整合数据库

第十六章 整合数据库 16.1 JDBC入门 16.1.1 JDBC简介 1.JDBC是java联机数据库的标准规范.它定义了一组标准类与接口,标准API中的接口会有数据库厂商操作,称为JDBC驱动程序. 2.JDBC标准主要分为两个部分:JDBC应用程序开发者接口和JDBC驱动程序开发者接口.应用程序需要联机数据库,其相关API主要在java.sql和javax.sql两个包中. 3.应用程序使用JDBC联机数据库的通用语法: Connection conn = DriverManager.g

[Android系列—] 4. 添加操作栏(Action Bar)

前言 操作栏是最重要的设计元素之一,使用它来实现你的应用程序活动.通过提供多种用户界面功能, 使应用程序快速和其他的Andorid应用程序一致, 以便被用户熟悉和接受. 主要功能包括: 1. 标识你的应用程序,指示在应用程序的用户的位置. 2. 能很方便的操作重要的功能(像搜索功能) 3. 导航和视图切换功能(使用制表符或下拉列表) 类似的效果如下: 设置操作栏 在基本的使用状况是, 操作栏在左边显示活动的标题和应用的图标. 类似: 设置一个基本的操作栏需要你使用的应用活动主题支持操作栏, 这和

LPTHW 笨方法学习python 16章

根据16章的内容作了一些扩展. 比如,判断文件如果存在,就在文件后追加,如不存在则创建. 同时借鉴了shell命令中类似 cat <<EOF > test的方法,提示用户输入一个结尾符. 现在有一个小坑,怎么使用python去读取一个文件的行数,原来有os.system("wc -l filename")倒是可以,但是windows下如何操作呢?回头补填. #!/usr/bin/env python # -*- coding:utf-8 -*- from sys im

4.4日第9次作业,,16章变更,17章安全,高项,29-田哲琦.

4.4日第9次作业,,16章变更,17章安全,高项,29-田哲琦.16章.变更管理 1.变更管理的原则是首先?P419答:变更管理的原则是首先建立项目基准.变更流程和变更控制委员会. 2.国内较多的配置工具有哪些?(3个)P419答:有Rational C1earCase,  VisualSvurceSafe和ConcurrentVersions Systemp. 3.CCB是决策机构还是作业机构?P420答:CCB是决策机构 4.项目经理在变更中的作用是什么?P420  答:项目经理在变更中的

Lua_第16 章 Weak 表

Lua_第16 章 Weak 表 Lua自动进行内存的管理.程序只能创建对象(表,函数等),而没有执行删除对象 的函数.通过使用垃圾收集技术,Lua 会自动删除那些失效的对象.这可以使你从内存 管理的负担中解脱出来.更重要的,可以让你从那些由此引发的大部分 BUG中解脱出 来,比如指针挂起(dangling   pointers)和内存溢出. 和其他的不同,Lua 的垃圾收集器不存在循环的问题.在使用循环性的数据结构的 时候,你无须加入特殊的操作;他们会像其他数据一样被收集.当然,有些时候即使更

C++ primer plus读书笔记——第16章 string类和标准模板库

第16章 string类和标准模板库 1. string容易被忽略的构造函数: string(size_type n, char c)长度为n,每个字母都为c string(const string & str, size_type pos = 0, size_type n = pos)初始化为str中从pos开始到结尾的字符,或从pos开始的n个字符 string(const char *s, size_type n)初始化为s指向的前n个字符,即使超过了s的结尾: string(Iter b

delphi完美经典-第16章 Delphi数据库程序设计----使用BDE组件

第16章 Delphi数据库程序设计----使用BDE组件 Delphi访问数据库的方式有:ADO.BDE.dbExpress.InterBase Express. 一.TDataSet组件 虽然Delphi有多种方式访问数据库,但它们必须依赖TDataSet.它用来显示从数据库单一或多个数据表取得的所有记录. 1.TDataSet常用属性 Active:指定或取得DataSet是否为打开状态.为True时,相当于调用Open. Bof.Eof:Bof检测DataSet是否停在第一条记录.Eof

设计模式之第16章-代理模式(Java实现)

设计模式之第16章-代理模式(Java实现) “现在朋友圈真是太让人蛋疼了啊.”“怎么说?”“一堆代理,各种卖东西的,看着好烦人.”“哎,删了呗.”“都是朋友,哪里好意思删啊.”“这倒也是...哎,迫于生计,没办法咯.还好我不玩.”“对了,你不就是代理的鼻祖么,身为代理模式,你作何感想.”“以代理之道还治代理之身啊.” 代理模式之自我介绍 最近出场率超级高,哦不,一直以来出场率都挺高的说的大名鼎鼎的模式,就是我-代理模式是也.有关我的定义如下:Provide a surrogate or pla