Android学习笔记3-界面

用户界面

 

在一个Android应用程序里,用户接口是一系列的View和ViewGroup对象组合而成。Android有很多种View和ViewGroup对象,他们都继承自View基类。

View对象是Android平台用户接口的基本对象。这些view类作为与用户交互的widgets小部件的父类,像文本框和按钮。ViewGroup作为提供各种布局结构的layouts的父类,例如linear线性布局,表格布局和绝对布局。

一个view对象是一个数据结构,他存储布局参数和屏幕特定区矩形区域的内容。一个view会处理自己所在屏幕区域的测量、布局、绘制、焦点改变、滚动、和按键手势交互。作为用户交互对象,一个view可以作为用户与系统的交互工具,接收事件。

 

View 结构体系

 

在Android平台,你要用到View或ViewGroup的层、节点的方式来定义一个Android用户界面,就像下面的图表,这个层次结构树可以按你的需求变得简单或者复杂。你可以用Android系统已经定义好的小控件或者布局,或者自定义一些。

 

 

为了能让你的view层次结构在屏幕上渲染,你的activity需要调用setContentView()方法并且传递一个根节点对象的引用。Android系统接收这些配置,并使用他们来进行测量绘制这个树形的视图结构。这个视图结构的根节点要求他的孩子节点自我绘制,反过来说,每个viewGroup节点负责让他们的子节点自我绘制。子节点会在父节点哪里请求到尺寸和位置,但父对象会最终决定他们孩子有多大。Android按顺序解析你的布局上的所有元素,从顶端开始,实例化view并且把他们添加到父对象。因为他们都是按顺序被绘制的,如果某些view超出的显示范围,那么后来绘制的将会覆盖原来的。

关于view结构体系怎样绘制,在后面会有详细的讨论。

 

布局

 

通常最多的布局是通过xml来定义。xml提供一个容易阅读的结构,很像html。xml中每个元素都是一个view或者viewGroup对象(或他们的子类)。view在树结构中是叶子节点,ViewGroup对象在非叶子节点(树枝节点,参考上图)。

xml元素名代表着各个类。比如<TextView>元素会创建一个TextView控件,一个<LinearLayout>会创建一个LinearLayout的viewGroup,当你加载一个布局资源,Android系统会初始化运行时对象,即对应的布局元素。

例如,一个简单的垂直布局,里面包含一个textView和一个Button。

 

[xhtml]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.               android:layout_width="fill_parent"   
  4.               android:layout_height="fill_parent"  
  5.               android:orientation="vertical" >  
  6.     <TextView android:id="@+id/text"  
  7.               android:layout_width="wrap_content"  
  8.               android:layout_height="wrap_content"  
  9.               android:text="Hello, I am a TextView" />  
  10.     <Button android:id="@+id/button"  
  11.             android:layout_width="wrap_content"  
  12.             android:layout_height="wrap_content"  
  13.             android:text="Hello, I am a Button" />  
  14. </LinearLayout>  
 

 

注意,linearLayout元素包含着TextView和Button。你可以嵌套另外的LinearLayout,来增加view的长度,或者增加一个更复杂的布局。

 

更多关于UI布局的信息,请参考 Declaring Layout章节。

 

你可以在你的布局里用很多种布局方式,使用很多不同类型的viewGroup,你可以定义无限多的子view或者子viewGroup。android提供一些预先定义好的布局,包括:LinearLayout-线性布局, RelativeLayout-关系布局, TableLayout-表格布局, GridLayout 网格布局等。他们提供一些独特的参数用来定义view的位置和布局结构。

 

Widgets小部件

widget是一个服务于用户与界面交互的view对象。Android提供很多视图小部件,如按钮、寻则狂、文本输入框、到呢个,你可以很快的构建自己的布局。一些小部件很复杂,像日期选择器,一个时钟,缩放控制。但你不会被Android平台所提供的UI部件所限制,你可以自定义UI部件,可以继承或者结合已经存在的UI部件。

 

UI事件

一旦你向界面添加了view或者widget,你可能会想他们怎样与用户进行交互的,这样你就可以执行一些操作。添加UI事件,你需要做两件事情:

@ 定义事件监听器并注册给view

多半情况下,这就是你怎样监听事件:view 类包含了一些名如OnXXXListener的监听器,他们都有名为OnXXX()的回调方法。例如:View.onClickListener(这是处理点击事件的),View.onTouchListener(处理触摸事件),View

.onKeyListener(处理按键事件),所以如果你想让view响应点击事件,比如按钮被选中,你需要实现OnClickListener接口并且定义回调方法,并且用setOnClickListener()方法来注册View。

@ 覆盖一个已经存在的回调方法:

如果你失信了自己的View类并且想要监听一些特殊的事件,那你应该用这种方法。例如,你可以处理屏幕触摸事件,处理滚动球事件,按键等事件。它允许你定义默认的事件,即自定义的view的每个事件,确定这个时间是否会传递到一些字view。然后,他们调用view类的回调函数,所以当你自定义一个组件时才有机会用到这种方法。

 

菜单Menu

 

应用程序菜单是UI的另一个很重要的部分。Menu提供一些可靠的借口来展示程序的功能和一些设置。通常menu是通过按menu键才显示的。然而,你可以让用户按下或者按住某一项时显示menu菜单。

menu菜单也遵循view的层次结构,但不要自己定义。取而代之的是,你只需为你的activity定义onCreateOptionsMenu()和onCreateContextMenu()两个回调方法即可,在适当的时间,Android会自动的绘制必要的视图结构并且menu的所包含的子项的。

Menu会处理他自己的时间。所以不需要注册事件监听器,当menu中的某一项被选中,系统会调用onOptionsItemSelected()方法或者onContextItemSelected()方法。

和应用程序的布局很像,你可以用xml来配置你的menu项。

 

高级特征

一旦你了解了创建用户界面的基本原则。你可以浏览一些高级特征来创建更复杂的应用程序接口。

 

适配器Adapters

一些时候,你不想用“硬代码”来填充一些view的数据,相反,你想让view绑定额外的数据集。要这样的话,你要定义一个AdapterView,每个子View里的数据都会被适配器填充。

适配器视图对象 AdapterView是实现的ViewGroup接口,子类是由被给出的适配器对象决定的。适配器就像你的adapter视图和数据源之间的适配器。这里有几种适配器类的实现方式,对于特殊的任务,例如自定义的适配器从一个Cursor来读取数据库的数据,或者一耳光数组适配器从任意一个数组读取数据。

 

风格和主题 Styles、Themes

你或许不满意标准控件的外观,你可以定制他们的风格和主题来改变他们。

@ 一个style是一个格式化的属性集,你作为布局的其中一个单元来使用他们。例如,你可以定义某些文本的文字大小和颜色作为特殊的view元素。

@ 一个theme主题是应用程序中整个activity的个格式化的属性集。例如,你可以定义窗体的边框和面板的背景,并且设置menu的字体大小和颜色。它可以应用在整个程序里。

 

风格和主题属于资源。Android提供一些默认的风格和主题资源来让你使用。或者你也可以自己定制他们。

声明布局

 

在一个activity里,你的layout就是整个界面架构。它定义了显示给用户的元素。你可以用两种方式声明你的layout:

@ 在XML里定义

android 提供了一些非常直观的视图类及其子类,比如一些widget和layout。

@ 在运行时新建一个实例

你的应用程序可以通过代码建立view或者viewGroup,并且设置它们的属性。

 

Android框架给了你这两种灵活的方法来管理和声明你的应用程序UI。例如,你可以在xml里声明一个默认的布局,包括用户界面的元素及其属性。你可以在运行时用过代码修改界面元素的包括在xml里声明的。

 

在xml里定制UI的优点是能让界面与逻辑部分相互独立并且容易iguanli它们的时间。如果UI和代码是分离的,那意味着你可以随时修改界面而不用修改代码后在编译。例如,你可以为横竖屏分别建立布局,不同的界面大小不同的语言。另外,在xml声明布局文件更直观,所以你很容易找出其中的bug,所以,这篇文章会告诉你怎样在xml里声明你的布局。如果你对运行时实例化view和viewGroup感兴趣,那你可以参考这两个ViewGroup和View类。

 

通常,UI元素的名字和实际的功能非常接近。元素名对应着类名,属性名对应着方法名。实际上,这种对应关系让我们很容易的猜到xml属性所丢应的类方法。或者才出一个类对应着哪个xml元素。然而,不是所有的命名都是相同的。有些情况下,命名会有一些不同。例如,EditText元素有一个text属性,但却对应着EditText.setText()方法。

小贴士:学习更多的布局类型可以参考Common Layout Objects章节,在Hello Views里有大量的创建布局的例子。

 

 


 

编写xml

 

使用Android xml的词汇表骂你可以很快的设计出UI布局和他们的位置。如同html那样,有一系列的嵌套元素。

每个layout布局文件必须包含一个根元素,这个根元素必须是view 或者 viewGroup。一旦你定义了根元素,你可以添加它的子元素,逐渐形成一个层次的布局。例如,下面是一个LinearLayout包含了一个TextView和Button。

 

[xhtml]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.               android:layout_width="fill_parent"   
  4.               android:layout_height="fill_parent"   
  5.               android:orientation="vertical" >  
  6.     <TextView android:id="@+id/text"  
  7.               android:layout_width="wrap_content"  
  8.               android:layout_height="wrap_content"  
  9.               android:text="Hello, I am a TextView" />  
  10.     <Button android:id="@+id/button"  
  11.             android:layout_width="wrap_content"  
  12.             android:layout_height="wrap_content"  
  13.             android:text="Hello, I am a Button" />  
  14. </LinearLayout>  
 

 

在xml声明好layout之后,保存为以xml为后缀名的文件,放到项目的res/layout/目录下,然后他就会被正确的编译。

待会我们再来讨论其中的具体元素的含义。

 

 


 

加载xml资源

 

当你编译完程序后,每个xml都编程一个view资源。你可以从代码中加载这些布局资源,在你的activity。onCreate()方法里。当你调用setContextView()之后,会把资源的引用通过R.layout_file_name的方式传递过去。例如你有了main_layout

.xml配置文件,你可以在activity中这样加载:

 

[java]  view plain copy
  1. public void onCreate(Bundle savedInstanceState) {  
  2.     super.onCreate(savedInstanceState);  
  3.     setContentView.(R.layout.main_layout);  
  4. }  
 

 

当activity运行时,在OnCreate()回调方法会被Android框架所调用。

 

 


 

属性Attributes

 

每个View和Viewgroup对象都支持xml属性,其中一些是特殊的,例如,textView支持textSize属性,但这些属性也可以继承人和view对象来扩展着各类。一些属性是公共的,因为他们继承自根类,例如ID。其他的属性例如layout parameters 为称作布局参数,这些属性用来描述view的布局,被他们的父view,即viewGroup定义的。

 

ID

每个View对象都有一个int型Id属性,作为在视图结构中的唯一标识。当一个程序编译完成,id便成为一个int型的引用,但是通常在xml中的id属性中id是一个字符串。这是所有view对象所共有的基本属性,你会经常用到。xml中的书写语法如下:android:id="@+id/my_button"

字符串开始的@符,说明xml解析器会解析@符后面剩余的字符串,并会定义他为一个id资源。“+”符号意味着必须在R.java文件中增加这个资源。android框架会提供大量的id资源。当我们引用一个android资源id时,你不需要“+”符号,但是必须添加android包的命名空间,像这样:android:id="@android:id/empty"

当使用了android包的命名空间,我们便可以使用android.r资源类了。

 

为了建立view 并且在程序中使用,通常的模式是:

1.在xml中定义一个viewm,并且分配一个id

 

[xhtml]  view plain copy
  1. <Button android:id="@+id/my_button"  
  2.         android:layout_width="wrap_content"  
  3.         android:layout_height="wrap_content"  
  4.         android:text="@string/my_button_text"/>  
 

 

2.(通常在onCreate()方法里)在代码中新建一个view对象的引用

 

[java]  view plain copy
  1. Button myButton = (Button) findViewById(R.id.my_button);  
 

 

在RelativeLayout相对布局时,定义view对象的id是非常重要的,这种布局下,兄弟view可以通过之间的位置关系来定布局,就是通过这个唯一标识id。

在整个布局结构中,id并不一定是唯一的,保证在当前布局结构中唯一即可,但我们有时会使用到整个布局,所以做好全局唯一。

 

布局参数

 

名为layout_something的xml属性定义了在viewGroup里的view的布局参数。

 

每个viewGroup类实现了一个继承自ViewGroup.LayoutParams类的嵌套类。这个子类包含了一些控制他的子View的大小位置等参数。

正如下面你所看到的,父View定义了子View的布局:

 

 

注意每个布局参数子类有自己的设置值的格式语法,每个子元素必须定义适合父view的布局参数,尽管他为自己的子view也定义了不同的布局参数。

所有的viewGroup都包含了宽高属性,而且必须定义它们。很多布局参数也包含了可选的间隙参数和边界参数。

你可以用精确的值来定义宽高,尽管你并不希望经常这样做。更多的时候,你会这样来定义:

@ wrap_content 只占用所需要的尺寸

@ fill_parent 占用父viewGroup可能的最大尺寸。(在API Level 8 里更名为 match_parent)

通常, 不建议使用像素值来定义宽高值,我们经常用相对的单位,如与密度无关的像素单元(dp),或者warp_content或者fill_parent来代替,这样能确保你的程序能运行在大量不同尺寸的设备上。公认的测量类型在 Available Respurces 文档里被定义。

 

 


 

布局位置 Layout Position 

 

view是一个矩形,每个view都有一个位置,包含x,y起始坐标和宽高来确定这个矩形的位置。位置和尺寸的单位是像素pixel。通过调用getLeft()和getTop()方法可以获得view 的位置,两个方法返回矩形的左上角的坐标xy。这些方法返回的是相对于父view的方位。比如,getLeft()返回20,那么他的右边距离父view左边有20个像素。此外,有很多方便的方法,都是为了减少不必要的计算,像getRight()和 getBottom()。(getRight()=getLeft()+getWidth())

 

 


 

大小、填充、边距 Size, Padding and Margins

 

一个view的大小即他的宽高。一个view 实际上有两套高度宽度值。

第一对值是我们都知道的measured width和measured 高度即测量宽度和测量高度。这组值定义了他们想在父view中有多大,通过 getMeasuredWidth() 和 getMeasuredHeight() 可以获得他们。第二组值为width和height,或者成为 drawing width 和drawing height。这组值定义了view再被绘制到屏幕后,在屏幕中的实际大小。这些值有可能会和第一组值大小不同。通过getWidth()和getHeight()方法可以获得。

为了得到view 的实际尺寸,必须考虑到他的填充。padding属性表示view的左上右下的像素间隙。通过设置一些像素值,padding属性被用来填充视图内容周围。例如,把left padding 设置为2,则view左边 与其父view的左边会有2个像素的间隙。通过setPadding(int,int,int,int)方法和getPaddingLeft()/getPaddingRight()/getPaddingTop()/getPaddingBotton()方法设置和获得。

尽管一个view可以定义padding,但他不支持margins属性,但是viewGroup支持。参考 ViewGroup和ViewGroup.marginlayoutParams类来获得更多信息。

创建菜单

 

菜单是应用程序重要的组成成分。他提供相似的借口来提供功能和设置。android为开发者提供一个简单的编程借口,针对不同的情况提供标准的程序菜单。

android 提供三种基本的menu类型:

Options Menu

这是一个菜单的基本元素。通过按menu的屏幕键才显示。有两种类型的menu菜单:

Icon Menu

通过按下menu键会出现这些可见按钮的集合。最大可以支持6个选项。只能显示为图标,并且菜单项只能为按钮,不能是选择框。

Expanded Menu

Icon menu有一个more选项,有一个选项集。只有当 Icon Menu 菜单被重写,并且有超过六个的选项时才会被显示。

Context Menu

这个菜单会在你长时间按view时显示。

Submenu

这是一个子菜单,可以添加到 Options Menu 或 Context Menu 菜单的选项中,子菜单不支持嵌套使用。


Options Menu

 

选项菜单通过按下menu键来显示。当菜单打开时,只显示前六个选项,如果超过六个,多的部分会显示在more里。超过的选项被添加到more菜单里是系统自动添加的。

选项菜单可以包含一些基本的程序功能,或者必要的导航。比如从主界面转到设置界面。你可以添加Submenus 来配置你的显示项,让它包含更多的功能。

当menu第一次被创建时,android系统会调用activity的onCreateOptionsMenu()回调方法。要添加自己的menu只需要重写它即可。你可以在弹出的menu中包含着在xml中定义的资源。或者通过调用add()方法添加每一个选项。这个方法会添加一个MenuItem,并且返回你最新创建的对象。你可以使用它们来获得菜单项,然后添加一些额外的属性,比如设置icon、快捷键等。

有很多种的add()方法,通常用的是能接收一个itemid参数的那个,这个id可以让你识别回调方法。

当一个菜单项从菜单中被选中,你会接收一个回调方法onOptionItemSelected()。这个回调方法会传递你所点击的菜单项,可以通过传来的id来判断是哪个菜单项。一旦你定义了菜单项,就可以添加适当的处理时间处理方法。

这里有一断activity中的代码,你可以这样定义并且处理他们的选择事件:

[java]  view plain copy
  1. /* Creates the menu items */  
  2. public boolean onCreateOptionsMenu(Menu menu) {  
  3.     menu.add(0, MENU_NEW_GAME, 0"New Game");  
  4.     menu.add(0, MENU_QUIT, 0"Quit");  
  5.     return true;  
  6. }  
  7. /* Handles item selections */  
  8. public boolean onOptionsItemSelected(MenuItem item) {  
  9.     switch (item.getItemId()) {  
  10.     case MENU_NEW_GAME:  
  11.         newGame();  
  12.         return true;  
  13.     case MENU_QUIT:  
  14.         quit();  
  15.         return true;  
  16.     }  
  17.     return false;  
  18. }  
 

在这个例子中使用的add()方法有四个参数:groupId、itemId、 order 和 title。groupId允许你将要添加的条目添加到groupId躲在项里。这个例子里,我们没有设置。itemid 是我们给MenuItem的唯一的整型值,可以用在在回调方法中识别选中项。order允许我们自定义item显示的顺序,默认情况下按照添加的顺序显示。title,就是显示的标签名,你可以用String resource字符串资源的方式来添加,这有有助于本地化的修改。

如果你想把几个菜单项合成一组,你可以用Submenu来实现。

 

添加图标 Adding icons

Icons可以通过setIcon()方法来添加。例如
menu.add(0, MENU_QUIT, 0, "Quit").setIcon(R.drawable.menu_quit_icon);

 

修改菜单 Modifying the menu

如果你想在菜单被添加之后修改它,可以重写onPrepareOptionsMenu()方法,他在菜单每次被打开时调用。他会传递你的menu对象,就像onCreateOptionsMenu()方法那样。如果你想根据程序或者游戏现在的状态来确定是否修改menu 的话,这个方法将会非常有用。

注意:当改变menu里的菜单项时,不提倡直接操作当前选中项。请记住,当在触摸模式下,是没有当前选中项的,相反,你应该使用Context Menu 来做这样的操作:在UI的特定项目里提供这样的功能。

 


Context Menu

android的 Context Menu 也是类似的,在PC上,即为右键菜单。当一个view注册了Context Menu 菜单后,长按这个view便能显示菜单,提供一些与此view相关的功能。Context menus可以使用任何的view,但通常被注册成listView,这种view会使选中的项目非常的明显。(待会会有一个例子)

注意:Context Menu条目不支持图标和快捷键。

要创建一个Context Menu,你必须重写activity的 Context Menu 回调方法:onCreateContextMenu()和 onContextItemSelected()方法。在onCreateContextMenu()回调方法中,你可以通过add()方法添加菜单项,或者使用xml配置文件添加,然后yon registerForContextMenu()方法注册成为ContextMenu。

例如,下面是在笔记本应用程序中添加contextMenu的例子:

[java]  view plain copy
  1. public void onCreateContextMenu(ContextMenu menu, View v,  
  2.                                 ContextMenuInfo menuInfo) {  
  3.   super.onCreateContextMenu(menu, v, menuInfo);  
  4.   menu.add(0, EDIT_ID, 0"Edit");  
  5.   menu.add(0, DELETE_ID, 0,  "Delete");  
  6. }  
  7. public boolean onContextItemSelected(MenuItem item) {  
  8.   AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();  
  9.   switch (item.getItemId()) {  
  10.   case EDIT_ID:  
  11.     editNote(info.id);  
  12.     return true;  
  13.   case DELETE_ID:  
  14.     deleteNote(info.id);  
  15.     return true;  
  16.   default:  
  17.     return super.onContextItemSelected(item);  
  18.   }  
  19. }  
 

在onCreateContextmenu()方法中,我们不仅仅只能给contextMenu添加菜单项,而且还可以给选中的view或者是ContextMenuInfo对象添加。ContextMenuInfo对象提供了选中对象的一些相关信息。例如,在OnCreateContextMenu()方法中添加两个很普通的item条目。在onContextItemSelected()方法中,我们需要得到菜单项的AdapterContextmenuInfo,来获取关于当前选中项的某些信息。我们只需要选中条目的id即可,所以无论添加或者删除,我们只需要知道AdapterContextMenuInfo.info 这个属性即可。然后这个id会被传递到deitNote()方法或者deleteNote方法来做相应的处理。

现在,为ListView中所有的项添加contextMenu方法,我们把整个ListView传递给registerForContextMenu(View)方法。

registerForContextMenu(getListView());

记住,你可以传递任何view对象来注册 contextMenu菜单,getListView()方法会返回在ListActivity类中使用到的ListView,因此,list中的每一项都会注册一个contextMenu。

 


子菜单Submenus

一个子菜单可以被添加到任何菜单,但不不能添加到子菜单中。当你的应用程序有很多功能需要被显示的时候非常有用。你可以用add()方法给menu添加额外的条目,例如:

[java]  view plain copy
  1. SubMenu fileMenu = menu.addSubMenu("File");  
  2.   SubMenu editMenu = menu.addSubMenu("Edit");  
  3.   fileMenu.add("new");  
  4.   fileMenu.add("open");  
  5.   fileMenu.add("save");  
  6.   editMenu.add("undo");  
  7.   editMenu.add("redo");  
  8.   return result;  
  9. }  
 

子菜单中的回调方法会传递给父菜单的回调方法。如上所示:子菜单的选择结果会传递到父菜单的onOptionsItemSelected();

 


在xml文件中定义Menus

就像UI布局一样,你可以在xml文件中定义菜单。然后再onCreate()方法中实例化他们。这样会让你的程序代码更加简洁,并且让代码和视图分离,更加可视化。

首先,在res文件夹下建立一个menu文件夹。你可以在这里定义菜单。

在一个菜单的xml布局中,有三个元素:<menu><group><item>。

Item和group元素必须是menu的子元素,但item还可以是group的子元素,menu元素必须有一个子元素,当然了,最外层的根元素必须是menu。

作为一个例子,我们建一个和上面一样的操作菜单,先在res/menu/文件夹下建一个option_menu.xml文件

[xhtml]  view plain copy
  1. <menu xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <item android:id="@+id/new_game"  
  3.           android:title="New Game" />  
  4.     <item android:id="@+id/quit"  
  5.           android:title="Quit" />  
  6. </menu>  
 

然后,在onCreateOptionsMenu()方法中,我们用MenuInflater.inflate()方法加载这个资源:

[java]  view plain copy
  1. public boolean onCreateOptionsMenu(Menu menu) {  
  2.     MenuInflater inflater = getMenuInflater();  
  3.     inflater.inflate(R.menu.options_menu, menu);  
  4.     return true;  
  5. }  
 

getMenuInflater()方法会返回activity的菜单解析器,我们可以调用inflate()方法来传递先前定义好的menu资源的指针,在回调方法中可以得到菜单对象。

虽然这种方式看似更加麻烦,但当你处理大量items时会为你省很多事,并且让你的代码看起来更加整洁。

你可以定义menu group,在group元素里包装item来定义menu group,并且在item里创建别的menu,每个元素都支持一些基本属性如快捷键、选择框、图标等。学习更多的属性可以参考XML syntax.

 

菜单特性

这里有一些大多数菜单项都有的特性

菜单组

当我们添加新的条目到一个菜单中,你可以让所有条目包含在一个组中,菜单组是菜单条目的集合,里面的菜单那项可以共享某些特性,比如是否可见、可用、可选。

一个菜单组被定义为整形(或者在xml中配置一个资源id),往menu中使用add()方法添加菜单项时,如果参数中包含了菜单组的id,那么这个条目会添加到相应的菜单组中。比如add(int,int,int,int)。使用setGroupVisible()方法可以设置整个组是否隐藏。setGroupEnabled()发那个发设置整个组是否可用,setGroupCheckable()设置整个组是否可选。

可选菜单项 

任何菜单项都有是否可选的接口。可以是一个独立的选择框、单选框、单选按钮复选框,参考上面截图。

注意:在Icon菜单中,菜单项不能显示为但则狂或者单选按钮。如果你想在icon菜单中让条目可选,必须自己在状态改变时改变条目的现实。

想让一个条目可选,可以使用setCheckable()方法,像这样。

[java]  view plain copy
  1. menu.add(0, VIBRATE_SETTING_ID, 0"Vibrate")  
  2.     .setCheckable(true);  
 

这样会显示一个选择框(前提是他不是一个icon菜单),当条目被选中时,onOptionsItemSelected()回调方法会被调用。这里你必须设置选择框的状态,你可以使用isChecked()查询当前状态或者使用setChecked()方法设置状态。类似于onOptionsItemsSelected()方法。

[java]  view plain copy
  1. switch (item.getItemId()) {  
  2. case VIBRATE_SETTING_ID:  
  3.   if (item.isChecked()) item.setChecked(false);  
  4.   else item.setChecked(true);  
  5.   return true;  
  6. ...  
  7. }  
 

如果想让几个radio选择按钮成为一个单选按钮组,可以设置相同的groupId,然后调用setGroupCheckable()方法即可。这种情况下,不用每个item都调用setCheckable()方法,下面是一个子菜单中的两个单选按钮组:

[java]  view plain copy
  1. SubMenu subMenu = menu.addSubMenu("Color");  
  2. subMenu.add(COLOR_MENU_GROUP, COLOR_RED_ID, 0"Red");  
  3. subMenu.add(COLOR_MENU_GROUP, COLOR_BLUE_ID, 0"Blue");  
  4. subMenu.setGroupCheckable(COLOR_MENU_GROUP, truetrue);  
 

在setGroupCheckable()方法中,第一个参数为要设置的groupId,第二个为菜单项是否可选,最后一个参数是是否为单选(设为false的则所有item选择情况相互独立,即可以多选),当group设为单选时,每一次有条目被选中时,其他选项自动设置为未选状态(即单选)。

注意:可选菜单按钮是基于会话的,其状态、结果并不会保存在设备上。如截图所示:在地图程序中的设置并不会保存。如果你想把设置的记过保存,可以使用Preferences累,并且使用PreferencesActivity类来管理他们。

Shortcut keys

使用setAlphabeticShortcut(char)方法,可以给菜单项中添加快捷键,使用setNumericShortcut(int)可以设置数字快捷键,或者使用setShortCut(char,int)。大小写不敏感,例如:

[java]  view plain copy
  1. menu.add(0, MENU_QUIT, 0"Quit")  
  2.     .setAlphabeticShortcut('q');  
 

现在,当菜单打开时,或者按下menu键时,按下q键将会选择这个条目。

这个快捷键会当成菜单项的一个小提示来显示,在菜单项标签的下面(icon菜单除外)。

注意:快捷键不能加载Context菜单中。

菜单项的intents

如果你阅读了以前的章节,你会对Android Intents有所熟悉,它允许应用程序绑定到其他程序上,共享信息,各个任务间通信。就像你的程序可以启动一个web浏览器、或者email客户端或者其他的activity,你可以在一个菜单中运行它们。有两种方式:为每个菜单项定义一个intent;或者定义intent然后允许Android自动搜索activity,并为每个符合条件的activity添加菜单项。

关于建立intents和为程序提供服务的详细信息,可以参考Intents and Intent Filter 章节

为单独的菜单项设置intent

如果你想让一个菜单项运行一个新的activity,那么你需要调用菜单项的setIntent()方发来定义intent。

例如,在onCreateOptionMenu()里,你可以这样定义:

[java]  view plain copy
  1. MenuItem menuItem = menu.add(0, PHOTO_PICKER_ID, 0"Select Photo");  
  2. menuItem.setIntent(new Intent(this, PhotoPicker.class));  
 

当点击菜单项时,android 会自动运行设置好的activity。

注意:这种运行方式不会返回数据,如果你想得到返回数据,那么不能使用setIntent()方法。你应该在onOptionsMenuItemSelected()方法或onContextMenuItemSelected()方法中调用startActivityForResult()方法。

动态添加intents

当前程序或者当前选项有可能会关联很多的activity,所以程序便可以动态的添加菜单项来执行那些操作。

在创建menu菜单时,根据当前选定项的MIME类型,来使用Intent.ALTERNATIVE_CATEGORY或者Intent.SELECTED_ALTERNATIVE这两个参数来设置intent的分类,或者其他的参数来让intent filter (intent过滤器)启动一个新的acticity。然后调用addIntentOptions()来让android搜索到符合要求的服务并且添加到menu菜单中。如果没有合适的相应程序则菜单不添加条目。

注意:SELECTED_ALTERNATIVE 处理当前屏幕里选中项,所以,之有当在onCreateContextMenu()方法和onPrepareOptionsMenu()方法里建立菜单时时才能使用。

下面的例子,展示了程序怎样找到额外的服务来显示到菜单中。

[java]  view plain copy
  1. public boolean onCreateOptionsMenu(Menu menu){  
  2.     super.onCreateOptionsMenu(menu);  
  3.     // Create an Intent that describes the requirements to fulfill, to be included  
  4.     // in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE.   
  5.     Intent intent = new Intent(null, getIntent().getData());  
  6.     intent.addCategory(Intent.CATEGORY_ALTERNATIVE);  
  7.           
  8.     // Search for, and populate the menu with, acceptable offering applications.  
  9.     menu.addIntentOptions(  
  10.          thisClass.INTENT_OPTIONS,  // Menu group   
  11.          0,      // Unique item ID (none)  
  12.          0,      // Order for the items (none)  
  13.          this.getComponentName(),   // The current Activity name  
  14.          null,   // Specific items to place first (none)  
  15.          intent, // Intent created above that describes our requirements  
  16.          0,      // Additional flags to control items (none)  
  17.          null);  // Array of MenuItems that correlate to specific items (none)  
  18.     return true;  
  19. }  
 

当activity发现有合适的intent过滤器时,便会添加一个相应的menu菜单项,菜单项会显示lable标签的内容。addIntentOptions()方法会返回添加的菜单项的个数。

必须注意:当addIntentOptions()方法被调用时,他将会覆盖第一个参数所指定菜单里的所有项。

如果你想为其他菜单提供服务,你只需要定义一个intent filter,只要再<category>标签里包含 ALTERNATIVE 或者 SELECTED_ALTERNATIVE 即可,例如:

[java]  view plain copy
  1. <intent-filter label="Resize Image">  
  2.     ...  
  3.     <category android:name="android.intent.category.ALTERNATIVE" />  
  4.     <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />  
  5.     ...  
  6. </intent-filter>  

建立对话框Dialog

Dialog是一个常见的显示在当前activity之上的小窗口。下面的activity会失去焦点,而dialog回接受用户输入。dialog常用在与程序直接相关联的通知和短小的activity中。

Android API支持以下几种dialog:

AlertDialog:

它可以包含0、1、2、3个按钮,或者一个列表或者多选单选按钮等,它是一个功能最强大的dialog接口,详细信息可参考下面的章节。

ProgressDialog:

它会显示一个进度条或者进度环,因为他是AlertDialog的子类,所有野支持按钮。

DatePickerDialog:

用来选择日期的对话框。

TimerPickerDialog:

用来选择时间的。

如果你想要定制自己的dialog,你可以继承Dialog对象,或者它的任何一个子类,并且定义一个新的布局。


显示一个Dialog

Dialog 总是被当做activity的一部分来创建和显示。你可以在activity的onCreateDialog(int)方法中创建一个dialog。当你使用这个方法,android系统会自动的管理每个dialog的状态并且关联到所在的activity中,让这个activity成为dialog的管理者。每个dialog都会继承activity的某些特性。例如,当dialog打开时,按下menu弹出的是所在activity的菜单,调节的是所在activity的音量。

注意:如果你决定在onCreateDialog()方法之外建立dialog,他将不会连接到activity中,此时,你可以使用setOwnerActivity(Activity)方法来绑定activity。

当你显示dialog时,调用showDialog(int)来传递一个dialog的id句柄。

当一个dialog首次显示时,android会在实例化dialog的activity中调用onCreateDialog(int)方法。回调方法会传递相同的id给showDialog(int)。当 创建完一个dialog后,会再方法的最后返回这个对象。

在dialog显示前,android回调用可选的方法 :onPrepareDialog(int,Dialog)。如果你想在每次调用dialog时改变一些配置的话,你可以定义这个方法。OnPrepareDialog(int,Dialog)方法会在每次调用dialog时调用,而onCreateDialog(int)方法只会调用一次。如果你不定义onPrepareDialog()方法,那么打开的dialog会保持上一次的状态。这个方法也会传递dialog的id句柄。

定义这两个onXXX()方法最好使用一个switch结构来检测Id参数,每一个case项都应该创建自己的dialog。例如。想象一个游戏使用两个不同的dialog,一个暂停一个结束游戏:

[java]  view plain copy
  1. static final int DIALOG_PAUSED_ID = 0;  
  2. static final int DIALOG_GAMEOVER_ID = 1;  
 

然后,再onCreateDialog(int)里根据id创建dialog:

[java]  view plain copy
  1. protected Dialog onCreateDialog(int id) {  
  2.     Dialog dialog;  
  3.     switch(id) {  
  4.     case DIALOG_PAUSED_ID:  
  5.         // do the work to define the pause Dialog  
  6.         break;  
  7.     case DIALOG_GAMEOVER_ID:  
  8.         // do the work to define the game over Dialog  
  9.         break;  
  10.     default:  
  11.         dialog = null;  
  12.     }  
  13.     return dialog;  
  14. }  
 

注意:在例子中没有详写,因为定义dialog属于另外的章节。

现在可以调用showDilaog(int)来显示一个dialog了:

[java]  view plain copy
  1. showDialog(DIALOG_PAUSED_ID);  
 


取消Dialog的显示

调用dialog的dismiss()方法可以隐藏正在显示的dialog,如果必要的话,可以调用activity的dismissDialog(int)方法,他俩效果是一样的。如果使用的onCreateDialog(int)方法来管理dialog的状态,那么每次当你的dialog消失时,对话框的状态都会被activity保存着。如果不太需要这个对话框或者不希望activity保留dialog的状态,可以调用removeDialog(int)方法。它会删除任何关于dialog的引用,如果dialog正在显示,此方法会让dialog隐藏。

隐藏dialog监听器的使用

如果你想让activity在dialog隐藏时执行某些动作,那么你可以建立一个监听器。

首先定义DialogInterface.OnDismissListerner 接口,这个接口只有一个方法,onDismiss(DialogInterface),当dialog隐藏时被调用,然后传递OnDismissListener 对象给setOnDismissLister()方法。

然而,注意dialog也可以是取消,用户让这个dialog取消也是一种特殊的情况。当用户按下back键时,或者调用cancel()方法时会发生这种情况。当一个dialog被取消时,OnDismissLister监听器仍然会收到通知,但如果你喜欢的到明确的取消消息,可以注册DialogInterface.OnCancelLister监听器。


AlertDialog的创建

AlertDialog时Dialog的子类,Dilaog绝大多数是这个强大类型,你可以在以下情况下使用:

@ 一个标题

@ 一个文本信息

@ 一个两个或者三个按钮

@ 一个单选或者多选列表

建立AlertDialog,使用AlertDialog.Builder子类。使用AlertDialog.Builder(Context)方法来获得一个Builder,并且使用它的公共方法来定义AlertDialog所有的属性。最后,调用create()方法来显示。

下面显示了如何定义AlertDialog.Builder类的一些属性,如果在onCreateDialog()方法中使用了例子中的代码,你可以返回结果对话框来显示这个dialog。

添加按钮

创建一个上图所示包含按钮的AlertDialog,可以使用setXXXButton()方法:

[java]  view plain copy
  1. AlertDialog.Builder builder = new AlertDialog.Builder(this);  
  2. builder.setMessage("Are you sure you want to exit?")  
  3.        .setCancelable(false)  
  4.        .setPositiveButton("Yes"new DialogInterface.OnClickListener() {  
  5.            public void onClick(DialogInterface dialog, int id) {  
  6.                 MyActivity.this.finish();  
  7.            }  
  8.        })  
  9.        .setNegativeButton("No"new DialogInterface.OnClickListener() {  
  10.            public void onClick(DialogInterface dialog, int id) {  
  11.                 dialog.cancel();  
  12.            }  
  13.        });  
  14. AlertDialog alert = builder.create();  
 

首先,通过setMessage(CharSequence)为dialog添加一个message,然后通过setCancelable(boolean)方法让此dialog无法通过按back键来取消。每个按钮都需要调用setXXXButton()方法,例如setPositiveButton()方法,DialogInterface.OnClickListener()类会定义按下按钮所要做的处理。

注意:每种类型的按钮只能加一个,这就是说,你不能添加多于一个的positive按钮。最多能添加三个按钮,positive, neutral, 和 negative.他们名字所显示的功能并未实现,但能帮你记住要实现的功能。

添加一个列表

如上图所示,使用setItems()方法添加可选列表:

[java]  view plain copy
  1. final CharSequence[] items = {"Red""Green""Blue"};  
  2. AlertDialog.Builder builder = new AlertDialog.Builder(this);  
  3. builder.setTitle("Pick a color");  
  4. builder.setItems(items, new DialogInterface.OnClickListener() {  
  5.     public void onClick(DialogInterface dialog, int item) {  
  6.         Toast.makeText(getApplicationContext(), items[item], Toast.LENGTH_SHORT).show();  
  7.     }  
  8. });  
  9. AlertDialog alert = builder.create();  
 

首先,使用setTitle(CharSequence)方法设置标题,然后使用setItem()方法添加可选列表,这个列表会接收一个item数组来显示,DialogInterFace.OnClickListener类会定义他们的点击事件。

添加选择框和单选按钮

通过setMultiChoiceItems()方法或 setSingleChoiceItems()方法来分别建立一个多选按钮列表或者单选列表,如果再onCreateDialog()方法中建立了其中一种列表,android会为你管理这个list。当activity处于活动状态时,dialog会记住当才选中项,如果退出了程序,选择结果便会丢失。

注意:当用户离开或者暂停activity时,如果你想保存选择状态,你必须在整个activity的生命周期中保存这个设置。永久的保存所选项,甚至当前进程完全被关闭,你需要使用数据存储方式来保存。建立一个如上图所示的列表dialog,代码和上面的例子相同,只需要把setItems()方法改为setSingleChoiceItems()方法即可。

[java]  view plain copy
  1. final CharSequence[] items = {"Red""Green""Blue"};  
  2. AlertDialog.Builder builder = new AlertDialog.Builder(this);  
  3. builder.setTitle("Pick a color");  
  4. builder.setSingleChoiceItems(items, -1new DialogInterface.OnClickListener() {  
  5.     public void onClick(DialogInterface dialog, int item) {  
  6.         Toast.makeText(getApplicationContext(), items[item], Toast.LENGTH_SHORT).show();  
  7.     }  
  8. });  
  9. AlertDialog alert = builder.create();  
 

setSingleChoiseItems()方法的第二个参数是checkedItem的id值,从0开始对应着位置,如果返回”-1“表明没有选中任何项。

 


进度对话框 ProgressDialog 的建立

ProgressDialog时AlertDialog的子类,它会显示一个表示进度的圆形动画,来表示一个进度或者任务正在运行,也可以时一个进度条,能清晰的表示出进度。他也能添加按钮,比如取消一个下载进程。

调用ProgressDialog.show()方法可以显示进程对话框,例如,上图的对话框可通过如下代码生成:

[java]  view plain copy
  1. ProgressDialog dialog = ProgressDialog.show(MyActivity.this"",   
  2.                         "Loading. Please wait..."true);  
 

第一个参数是程序的Context引用,四二个为标题,第三个为显示的信息,最后一个为类型,(当创建进度条时才会用到,下节讨论)。

默认的进度条为圆形的样式,如果你想生成一个通过具体数值来显示任务的加载情况的进度条,下一节会讨论。

进度条的显示

显示一个进度条要经过以下几个步骤:

1-使用ProgressDialog(Context)方法初始化

2-使用setProgressStyle(int)方法设置类型。

3-调用show()方法显示,或者在onCreateDialog(int)方法里返回一个ProgressDialog。

4-你可以调用setProgress(int)方法,根据整体的任务完成度来设置一个具体进度值,或者使用incrementPressBy(int)来设置一个增长值。

例如:

[java]  view plain copy
  1. ProgressDialog progressDialog;  
  2. progressDialog = new ProgressDialog(mContext);  
  3. progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);  
  4. progressDialog.setMessage("Loading...");  
  5. progressDialog.setCancelable(false);  
 

设置代码非常简单,大部分代码是在dialog参与进程并且更新的功能里。你会发现,另起一个线程来做这个工作是很有必要的,要把消息传递给activity的UI线程里需要用到 Handler 消息机制。如果你并不熟悉使用额外的线程,那么看这个例子:

这个例子使用了第二个线程来跟踪任务的进度(实际上只是在数值上加到100),线程通过 Handler 发了一个Message 给主activity,然后主activity更新ProgressDialog。

[java]  view plain copy
  1. package com.example.progressdialog;  
  2. import android.app.Activity;  
  3. import android.app.Dialog;  
  4. import android.app.ProgressDialog;  
  5. import android.os.Bundle;  
  6. import android.os.Handler;  
  7. import android.os.Message;  
  8. import android.view.View;  
  9. import android.view.View.OnClickListener;  
  10. import android.widget.Button;  
  11. public class NotificationTest extends Activity {  
  12.     static final int PROGRESS_DIALOG = 0;  
  13.     Button button;  
  14.     ProgressThread progressThread;  
  15.     ProgressDialog progressDialog;  
  16.      
  17.     /** Called when the activity is first created. */  
  18.     public void onCreate(Bundle savedInstanceState) {  
  19.         super.onCreate(savedInstanceState);  
  20.         setContentView(R.layout.main);  
  21.         // Setup the button that starts the progress dialog  
  22.         button = (Button) findViewById(R.id.progressDialog);  
  23.         button.setOnClickListener(new OnClickListener(){  
  24.             public void onClick(View v) {  
  25.                 showDialog(PROGRESS_DIALOG);  
  26.             }  
  27.         });   
  28.     }  
  29.      
  30.     protected Dialog onCreateDialog(int id) {  
  31.         switch(id) {  
  32.         case PROGRESS_DIALOG:  
  33.             progressDialog = new ProgressDialog(NotificationTest.this);  
  34.             progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);  
  35.             progressDialog.setMessage("Loading...");  
  36.             progressThread = new ProgressThread(handler);  
  37.             progressThread.start();  
  38.             return progressDialog;  
  39.         default:  
  40.             return null;  
  41.         }  
  42.     }  
  43.     // Define the Handler that receives messages from the thread and update the progress  
  44.     final Handler handler = new Handler() {  
  45.         public void handleMessage(Message msg) {  
  46.             int total = msg.getData().getInt("total");  
  47.             progressDialog.setProgress(total);  
  48.             if (total >= 100){  
  49.                 dismissDialog(PROGRESS_DIALOG);  
  50.                 progressThread.setState(ProgressThread.STATE_DONE);  
  51.             }  
  52.         }  
  53.     };  
  54.     /** Nested class that performs progress calculations (counting) */  
  55.     private class ProgressThread extends Thread {  
  56.         Handler mHandler;  
  57.         final static int STATE_DONE = 0;  
  58.         final static int STATE_RUNNING = 1;  
  59.         int mState;  
  60.         int total;  
  61.          
  62.         ProgressThread(Handler h) {  
  63.             mHandler = h;  
  64.         }  
  65.          
  66.         public void run() {  
  67.             mState = STATE_RUNNING;     
  68.             total = 0;  
  69.             while (mState == STATE_RUNNING) {  
  70.                 try {  
  71.                     Thread.sleep(100);  
  72.                 } catch (InterruptedException e) {  
  73.                     Log.e("ERROR""Thread Interrupted");  
  74.                 }  
  75.                 Message msg = mHandler.obtainMessage();  
  76.                 Bundle b = new Bundle();  
  77.                 b.putInt("total", total);  
  78.                 msg.setData(b);  
  79.                 mHandler.sendMessage(msg);  
  80.                 total++;  
  81.             }  
  82.         }  
  83.           
  84.         /* sets the current state for the thread, 
  85.          * used to stop the thread */  
  86.         public void setState(int state) {  
  87.             mState = state;  
  88.         }  
  89.     }  
  90. }  
 

 


自定义dialog的建立

如果你想自定义dialog的布局,你可以自己创建一个dialog布局。定义好之后,传递根View对象或者资源ID到setContextView(View)方法。

例如,如上图的dialog:

1-建立一个xml布局文件custom_dialog.xml;

[java]  view plain copy
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.               android:id="@+id/layout_root"  
  3.               android:orientation="horizontal"  
  4.               android:layout_width="fill_parent"  
  5.               android:layout_height="fill_parent"  
  6.               android:padding="10dp"  
  7.               >  
  8.     <ImageView android:id="@+id/image"  
  9.                android:layout_width="wrap_content"  
  10.                android:layout_height="fill_parent"  
  11.                android:layout_marginRight="10dp"  
  12.                />  
  13.     <TextView android:id="@+id/text"  
  14.               android:layout_width="wrap_content"  
  15.               android:layout_height="fill_parent"  
  16.               android:textColor="#FFF"  
  17.               />  
  18. </LinearLayout>  
 

这个xml在LinearLayout里定义了一个ImageView和TextView。

2-设置上面的布局为dialog的context view ,并且定义ImageView和TextView两个元素。

[java]  view plain copy
  1. Context mContext = getApplicationContext();  
  2. Dialog dialog = new Dialog(mContext);  
  3. dialog.setContentView(R.layout.custom_dialog);  
  4. dialog.setTitle("Custom Dialog");  
  5. TextView text = (TextView) dialog.findViewById(R.id.text);  
  6. text.setText("Hello, this is a custom dialog!");  
  7. ImageView image = (ImageView) dialog.findViewById(R.id.image);  
  8. image.setImageResource(R.drawable.android);  
 

实例化dialog后,使用setContextView(int)方法设置自定义的布局。现在dialog便有了一个自定义的布局,你可以使用findViewById(int)方法来获得或者修改布局。

3-完成了,现在你可以显示自定义的dialog了。

一个dialog必须有一个title,如果你没有调用setTitile()方法,那么会标题处会显示空,但dialog仍然可见,如果你不想显示标题,只有写一个自己的dialog类了。然而,因为一个AlertDialog使用AlertDialog.builder类创建起来非常简单,你不必使用setContextView(int)方法。但必须使用setView(view)方法代替。这个方法会接受一个view参数,你需要从xml中得到根view元素。

得到xml布局,通过LayoutInflater类的getLayoutflater()方法(或者getSystemService()方法),然后调用inflate(int,ViewGroup)方法,第一个参数是xml文件id,第二个参数是根view的id,在这点上,你可以使用inflated 布局来获得xml中的view对象并且定义ImageView和TextView对象,然后实例化AlertDialog.Builder类并且使用setView(View)方法来设置布局。

这有一个自定义dialog布局文件的例子:

[java]  view plain copy
  1. AlertDialog.Builder builder;  
  2. AlertDialog alertDialog;  
  3. Context mContext = getApplicationContext();  
  4. LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(LAYOUT_INFLATER_SERVICE);  
  5. View layout = inflater.inflate(R.layout.custom_dialog,  
  6.                                (ViewGroup) findViewById(R.id.layout_root));  
  7. TextView text = (TextView) layout.findViewById(R.id.text);  
  8. text.setText("Hello, this is a custom dialog!");  
  9. ImageView image = (ImageView) layout.findViewById(R.id.image);  
  10. image.setImageResource(R.drawable.android);  
  11. builder = new AlertDialog.Builder(mContext);  
  12. builder.setView(layout);  
  13. alertDialog = builder.create();  
 

使用自定义布局这种方式来生成dialog,可以让你使用更高级的特性,比如管理按钮、列表、标题、图标等。

 

UI事件的处理

在用户的交互中,有不止一种的方法来截获事件。考虑到在用户界面中的事件时,从特殊的与用户交互的view对象中捕获事件,View类提供了一些特殊的方法。

再各种的视图类中,你要编写自己的布局,你可以声明一些公共的回调方法来响应UI事件。当相应的动作发生时,这些方法会被android框架自动调用。例如,当一个view被触摸时,onTouchEvent()方法被调用。然而,为了截获这些方法,我们需要重写这些方法。当然了,重写每一个这样的方法显然时不实际的,这就是为什么为何view类会有一些必须的接口这样你可以更加容易的使用。这些接口,被称作事件监听器 event listeners,这就是捕捉到界面上用户操作的关键。

当你经常在用户交互上使用事件监听器时,有时需要自己写一个继承自view的类,来自定义一个部件。如果你想继承一个Button类来实现更多的功能,这种情况下,你可以借助event handlers 类来定义默认的事件处理类。


事件监听器

事件监听器是包含简单回调方法的view类的一个接口,当用户触发了一些注册过的事件时,android系统框架会自动调用相应的方法。

事件监听器包含以下几个方法:

onCLick()

来自View.onClickListener。 当view被触摸,或者焦点在view上时,用户按了确认键或导航键。

onLongClick()

来自 View.onLongClickListener。当view被触摸超过1秒,或者焦点在view上,用户按了确认或导航键超过1秒时,方法被调用。

onFocysChange()

来自 View.onFocusChangeListener。当焦点离开view时被调用。

onKey()

来自 View.onKeylistener。按下或者弹起某个键时方法被调用。

onTouch()

来自 View.OnTouchListener。当用户操作屏幕时被调用,包括按下释放或者其他的操作。

onCreateContextMenu()

来自 View.onCreateContextMenuListener。当一个Context Menu菜单被建立时调用。

 

这些方法都有自己的接口方法。如果想定义这些方法来处理自己的事件,只要在activity里面使用匿名类即可。然后使用View.setXXXListener()方法,将匿名类传递给view。

下面的例子演示了怎样给一个Button注册一个监听器:

[java]  view plain copy
  1. // Create an anonymous implementation of OnClickListener  
  2. private OnClickListener mCorkyListener = new OnClickListener() {  
  3.     public void onClick(View v) {  
  4.       // do something when the button is clicked  
  5.     }  
  6. };  
  7. protected void onCreate(Bundle savedValues) {  
  8.     ...  
  9.     // Capture our button from layout  
  10.     Button button = (Button)findViewById(R.id.corky);  
  11.     // Register the onClick listener with the implementation above  
  12.     button.setOnClickListener(mCorkyListener);  
  13.     ...  
  14. }  
 

你会发现让activity实现监听器接口是非常方便的,他可以让你的程序避免多余的对象分配。

[java]  view plain copy
  1. public class ExampleActivity extends Activity implements OnClickListener {  
  2.     protected void onCreate(Bundle savedValues) {  
  3.         ...  
  4.         Button button = (Button)findViewById(R.id.corky);  
  5.         button.setOnClickListener(this);  
  6.     }  
  7.     // Implement the OnClickListener callback  
  8.     public void onClick(View v) {  
  9.       // do something when the button is clicked  
  10.     }  
  11.     ...  
  12. }  
 

注意,上面例子中的onClick()回调方法没有返回值,但其他的时间监听器必须返回一个boolean类型的返回值,这是由监听器的类型决定的,例如:

onLongClick()

这个方法会返回一个布尔值,来说明这个事件正在进行,也就是说,返回true表明此事件正在被处理不会被继续传递了。

onKey()

也会返回一个布尔值,返回true的话说明此按键事件将会被处理并不会被继续响应。如果你没有处理或者希望事件继续被其他监听器响应,那么返回false。

onTouch()

返回同上,也是返回true就不再被传递。重点是有很多类型的事件与这个事件有关,例如,当接收到键被按下的事件时,如果你此时返回false,那么表明你将不会处理这个按键事件。因此,你不会处理这个事件,例如手势或者其他后续事件。

记住这些按键事件总会让当前veiw成为当前焦点,他们从view层次结构的顶端开始被一级级传递,直到到达应该被响应的地方。如果你的view或者子view当前拥有焦点,那么你可以看到事件怎样传递到dispatchKeyEvent()方法。作为一种捕捉按键事件的替代方法,你可以接收activity里的所有的onKeyDown()和onKeyUp()事件。

注意:Android会首先调用事件处理程序,然后再调用自定义的事件处理类。例如:返回true将会阻止事件被继续传递给下一层,也会阻止系统做一些默认的处理,所以如果你确定要终止这个事件就返回true。

 


事件处理器

如果你创建了一个自定义的view组件,那么你要定义几种回调方法来。在Building Custom Components文档中,你会学习到以下几个回调事件:

onKeyDown(int,ketEvent);键被按下

onKeyUp(int,keyEvent);键弹起

onTrackballEvent(MotionEvent);轨迹球事件

onTouchEvent(MotionEvent);触摸屏幕事件

onFocusChanged(boolean,int,Rect);焦点改变事件

这里有一些你需要知道的方法,他们不是view类的一部分,但是会直接影响到你对事件的处理。所以遇到十分复杂的布局时,可以考虑以下的几个方法:

Activity.dispatchTouchEvent(MotionEvent)

它允许你的activity拦截所有的触摸事件

ViewGroup.onInterceptTouchEvent(MotionEvent)

允许VeiwGroup来观察子view的事件

ViewParent.requestDisallowInterceptTouchEvent(boolean)

告诉父View不应该使用相关方法来拦截事件。

 


触摸模式

当用户正在使用导航键或者轨迹球来与手机交互时,有必要给当前互动的item例如button一个焦点,来让用户看到接收输入、或者说当前的控件。如果一个设备支持触摸,那么操作时就不需要用上面的高亮显示的方式来告诉用户。因此,便有了这种触摸模式。对于有触摸功能的设备来说,一旦用户触摸了屏幕,设备便会进入触摸模式。只有那些isFocusableInTouchMode()返回true的控件才可以获得焦点,例如文本编辑框。别的view是touchable的,例如按钮,但被点击时不会显示焦点。当被按下时,他们的事件监听器将会无效。

任何时候用户点击了导航键或者轨迹球,设备都会退出touch模式,并且找一个view来显示焦点。现在,用户可以不用触摸屏幕来与设备进行交互了。

Touch模式存在于整个系统中。包括窗口和activity。可以用isinTouchMode()方法来查询当前模式。

 


 

焦点的处理

框架会处理常规的焦点改变事件来响应用户的输入。包括一些veiw的显示和隐藏。view通过isFocusable()方法来设定是否可以得到焦点。调用setFocusable()方法可以改变focusable状态。当view在触摸模式,你可以通过调用isFocusableInTouchMode()方法查询到是否支持,通过setFocusableInTouchMode()来设置。

焦点的运动是根据给定方向最近的一个view来确定的。在极少情况下,默认的这种算法或许不符合开发者预期的要求。这种情况下,可以通过xml来明确确定焦点的转移情况。例如:

[java]  view plain copy
  1. <LinearLayout  
  2.     android:orientation="vertical"  
  3.     ... >  
  4.   <Button android:id="@+id/top"  
  5.           android:nextFocusUp="@+id/bottom"  
  6.           ... />  
  7.   <Button android:id="@+id/bottom"  
  8.           android:nextFocusDown="@+id/top"  
  9.           ... />  
  10. </LinearLayout>  
 

一般的,在垂直的布局里,往下或者往上移动轨迹球或是导航键多不能移动焦点,现在可以通过xml的定义来达到移动焦点的目的。

如果你想要声明可以获得焦点的view,可以在xml里添加android:focusable这个属性,设置为true,你也可以在Touch模式下添加android:focusableInTouchMode。

使用requestFocus()方法可以使一个特定的师徒取的焦点。

使用onFocusChange()方法来监听焦点事件。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值