标签:
请参考教材,全面理解和完成本章节内容... ...
复制工程ch12,将工程目录改名为ch16.
在Honeycomb版本系统中,Android引入了全新的操作栏。操作栏不仅取代了用来显示标题和应用图标的传统标题栏(title bar),还带来了更多其他功能,例如,安置菜单选项、配置应用图标作为导航按钮,等等。
本章,我们将为CriminalIntent应用创建一个菜单,并在其中提供可供用户新增crime记录的菜单项,然后让应用的图标支持向上的导航操作,如图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级别,各设备呈现选项菜单的方式会稍有不同。本章后续学习过程中,我们会再来看看可能会涉及到的兼容性、选项菜单以及操作栏问题。
菜单是一种类似于布局的资源。创建一个定义菜单的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)中。该属性当前设置为ifRoom
和withText
的一个组合值。因此,只要空间足够,菜单项图标及其文字描述都会显示在操作栏上。如空间仅够显示菜单项图标,则不会显示文字描述。如空间大小不够任何一项显示,则菜单项会被转移隐藏到溢出菜单中。(复制图标文件ic_menu_add.png后,在项目里粘贴到drawable目录里)
如何访问溢出菜单取决于具体设备。如设备具有物理菜单键,则必须单击该键查看溢出菜单。目前,大多数较新设备都已取消物理菜单键,因此可通过操作栏最右端带有三个点的图标来访问溢出菜单,如图16-4所示。
图16-4 操作栏中的溢出菜单
属性showAsAction
还有另外两个可选值:always
和never
。不推荐使用always
,应尽量使用更为方便的ifRoom
属性值,让操作系统决定如何显示菜单项。对于那些很少用到的菜单项,使用never
是个不错的选择。总的来说,为避免看到混乱的用户界面,只应将用户经常使用的菜单项放置在操作栏上。
在代码中,Activity
类提供了管理选项菜单的回调函数。在需要选项菜单时,Android会调用Activity
的onCreateOptionsMenu(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()
方法什么也没做。
Fragment
的onCreateOptionsMenu(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 同时显示在操作栏上的菜单图标和菜单标题
为响应用户点击[新手记]菜单项,需实现新方法以添加新的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记录并对它们进行编辑。
目前为止,CriminalIntent应用主要依靠后退键在应用内导航。使用后退键的导航又称为临时性导航,只能返回到上一次的用户界面。而 Ancestral navigation,有时也称为层级式导航(hierarchical navigation),可逐级向上在应用内导航。
Android可轻松利用操作栏上的应用图标实现层级式导航。也可利用应用图标实现直接回退至主屏,即逐级向上直至应用的初始界面。实际上,操作栏上的应用图标最初是用作Home键的。不过,Android现在只推荐利用应用图标,实现向上回退一级至当前activity的父界面。这样一来,应用图标实际上就起到了向上按钮的作用。
本节中,针对显示在CrimePagerActivity
操作栏上的应用图标,我们将编码使其具有向上按钮的功能。点击该图标,可回退至crime列表界面。
通常,应用图标一旦启用了向上导航按钮的功能,在应用图标的左边会显示一个如图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);
}
如同响应选项菜单项那样,可通过覆盖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中使用同一CrimeFragment
,CrimeFragment
依然能正常工作。
运行CriminalIntent应用。创建新的crime记录,然后点击应用图标,返回至crime列表界面。实际上,CriminalIntent应用两个层级的关系并不是太容易区分。但navigateUpFromSameTask(Activity)
方法实现了向上导航的功能,使得用户可以轻松地向上导航一级至CrimePagerActivity
的父类界面。
本小节,利用前面学过的有关菜单、应用兼容性以及可选资源的知识,我们添加一个菜单项实现显示或隐藏CrimeListActivity
操作栏的子标题。
对于使用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记录,确认应用运行如前。
操作栏上的子标题显示后,菜单项标题依然显示为“显示子标题
”。如果菜单项标题的切换与子标题的显示或隐藏能够联动,用户体验会更好。
在onOptionsItemSelected(...)
方法中,选中菜单项后,检查子标题的显示状态并采取相应操作,如代码清单16-16所示。
代码清单16-16 实现菜单项标题与子标题的联动显示(CrimeListFragment.java)
如果操作栏上没有显示子标题,则应设置显示子标题,同时切换菜单项标题,使其显示为Hide Subtitle。如果子标题已经显示,则应设置其为null
值,同时将菜单项标题切换回Show Subtitle。
运行CriminalIntent应用,确认菜单项标题与子标题的显示能够联动。
这个问题就是经典的设备旋转问题。子标题显示后,旋转设备,这时因为用户界面的重新生成,显示的子标题会消失。为解决此问题,需要一个实例变量记录子标题的显示状态,并且设置 保留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应用。显示子标题并旋转设备,可以看到子标题在重新创建的视图中依然能正确显示。
标签:
原文地址:http://www.cnblogs.com/jlxuqiang/p/4758610.html