第5章chapter5
Android用户界面Android用户界面是应用程序开发的重要组成部分,决定了应用程序是否美观、易用。通过本章的学习可以让读者熟悉Android用户界面的基本开发方法,了解在Android界面开发过程中常见的界面控件、界面布局、菜单和界面事件的使用方法,充分理解手机应用程序与桌面应用程序在用户界面开发上的不同与相同之处。
本章学习目标:
了解各种界面控件的使用方法;
掌握各种界面布局的特点和使用方法;
掌握选项菜单、子菜单和快捷菜单的使用方法;
掌握操作栏和Fragment的使用方法;
掌握按键事件和触摸事件的处理方法。
5.1用户界面基础
用户界面(User Interface,UI)是系统和用户之间进行信息交换的媒介,实现信息的内部形式与人类可以接收形式之间的转换。最古老的用户界面是各种形式的文字、图形、旗帜和手势等,这些抽象符合作为信息传递的介质,使人类可以理解这些信息所包含的意义或指代的实体。
算盘是由柱子和珠子组成的最早的人机交互界面。在计算机出现的早期,批处理界面(1945—1968年)和命令行界面(1969—1983年)得到广泛的使用。目前,流行的用户界面是图形用户界面(Graphical User Interface,GUI),采用图像的方式与用户进行交互。与早期的交互界面相比,图形用户界面对于用户来说更加简便易用,用户从此不再需要记住大量的命令,取而代之的是通过窗口、菜单和按钮等方式来进行操作。未来的用户界面将更多地运用虚拟现实技术,使用户能够摆脱键盘与鼠标的交互方式,而通过动作、语言,甚至脑电波来控制计算机。当然,对用户界面的深入探讨远超出了本书的涉及范围,感兴趣的读者可以阅读相关书籍。
在手机上进行用户界面设计是一项具有挑战性的工作。首先,手机的界面设计者和程序开发者是独立且并行工作的,这就需要界面设计与程序逻辑完全分离,不仅有利于并行工作,而且在后期修改界面时也可以避免修改程序的逻辑代码;其次,不同型号手机◆Android应用程序开发(第4版)第◆5章Android用户界面的屏幕解析度、尺寸和长宽比各不相同,程序界面需要能够根据屏幕信息,自动调整界面控件的位置和尺寸,避免因为屏幕解析度、尺寸或纵横比的变化而出现显示错误;最后,手机屏幕尺寸较小,设计者必须能够合理利用有限的显示空间,构造出符合人机交互规则的用户界面,避免出现凌乱、拥挤的用户界面。
Android系统已经为使用者解决了界面设计前两个问题。在界面设计与程序逻辑分离方面,Android系统将用户界面和资源从逻辑代码中分离出来,使用XML文件对用户界面进行描述,资源文件独立保存在资源文件夹中。Android系统的用户界面描述非常灵活,允许模糊定义界面元素的位置和尺寸,通过声明界面元素的相对位置和粗略尺寸,从而使界面元素能够根据屏幕尺寸和屏幕摆放方式动态调整显示方式。
Android用户界面框架(Android UI Framework)采用MVC(ModelViewController)模型,为用户界面提供了处理用户输入的控制器(Controller)和显示图像的视图(View),模型(Model)是应用程序的核心,数据和代码被保存在模型中。控制器、视图和模型的关系如图5.1所示。
MVC模型中的视图将应用程序的信息反馈给用户,可能的反馈方法包括视觉、听觉或触觉等,但最常用的就是通过屏幕显示反馈信息。Android系统的界面元素以一种树形结构组织在一起,这种树形结构称为视图树(View Tree),如图5.2所示。Android系统在屏幕上绘制界面元素时,会依据视图树的结构从上至下绘制每一个界面元素。每个元素负责对自身的绘制,如果元素包含子元素,该元素会通知其下所有子元素进行绘制。Android系统在用户界面绘制上还有一些提高效率的办法,例如,如果父元素能够确定某个区域一定会被其子元素绘制,则父元素会停止绘制该区域,以提高屏幕绘制的效率,缩短绘制时间。
图5.1MVC模型
图5.2视图树
视图树由View和ViewGroup构成。View是界面中最基本的可视单元,存储了屏幕上特定矩形区域内所显示内容的数据结构,并能够实现所占据区域的界面绘制、焦点变化、用户输入和界面事件处理等功能。View也是一个重要的基类,所有在界面上的可见元素都是View的子类。ViewGroup是一种能够承载多个View的显示单元,一般有两个用途,一个是承载界面布局,另一个是承载具有原子特性的重构模块。
MVC模型中的控制器能够接收并响应程序的外部动作,如按键动作或触摸屏幕动作等。控制器使用队列处理外部动作,每个外部动作作为一个独立的事件被加入队列中,然后Android用户界面框架按照“先进先出”的规则从队列中获取事件,并将这个事件分配给所对应的事件处理函数。
Android用户界面框架中另一个重要的概念就是单线程用户界面(Singlethreaded UI)。在单线程用户界面中,控制器从队列中获取事件,视图在屏幕上绘制用户界面,使用的都是同一个线程。单线程用户界面能够降低应用程序的复杂程度,同时也能降低开发的难度。首先,用户不需要在控制器和视图之间进行同步。其次,所有事件处理完全按照其加入队列的顺序进行,也就是说,在事件处理函数返回前不会处理其他事件,因此,用户界面的事件处理函数具有原子性。但单线程用户界面也有其缺点,如果事件处理函数过于复杂,可能会导致用户界面失去响应。因此,应尽可能在事件处理函数中使用简短的代码,或将复杂的工作交给后台线程处理。
5.2界 面 控 件
Android系统的界面控件分为定制控件和系统控件。定制控件是用户独立开发的控件,或通过继承并修改系统控件后所产生的新控件,能够提供特殊的功能和显示需求。系统控件是Android系统中已经封装的界面控件,是应用程序开发过程中最常见的功能控件。系统控件更有利于进行快速开发,同时能够使Android应用程序的界面保持一定的一致性。
常见的系统控件包括TextView、EditText、Button、ImageButton、Checkbox、RadioButton、Spinner、ListView和TabHost。
5.2.1TextView和EditText
TextView是一种用于显示字符的控件,EditText则是用来输入和编辑字符的控件,因为EditText继承于TextView,所以EditText是一个具有编辑功能的TextView控件。
TextViewDemo示例如图5.3所示,从上至下分别是TextView01和EditText01。在XML文件(/res/layout/main.xml)中的代码如下。1
5
6
10图5.3TextViewDemo示例
第1行代码中的android:id属性声明了TextView的ID,这个ID主要用于在代码中引用TextView对象。@+id/TextView01表示所设置的ID值,其中@表示后面的字符串是ID资源;加号(+)表示需要建立新资源名称,并添加到R.java文件中;斜杠后面的字符串(TextView01)表示新资源的名称。如果不是新添加的资源,或属于Android框架的资源,则不需要使用加号,但必须添加Android包的命名空间,例如android:id="@android:id/empty"。
第2行代码中的android:layout_width属性用来设置TextView的宽度,wrap_content表示TextView的宽度只要能够包含所显示的字符串即可。第3行代码中的android:layout_height属性用来设置TextView的高度。第4行代码表示TextView所显示的字符串,在后面将通过代码更改TextView的显示内容。第7行中代码的match_parent表示EditText的宽度将等于父控件的宽度。
TextViewDemo.java文件中,引用XML文件中建立的TextView和EditText,并更改其显示内容。为了能够使程序正常运行,需要在代码中引入android.widget.EditText和android.widget.TextView。1TextView textView=(TextView)findViewById(R.id.TextView01);
2EditText editText=(EditText)findViewById(R.id.EditText01);
3textView.setText("用户名:");
4editText.setText("Rajan");第1行的findViewById()函数能够通过ID引用界面上的任何控件,只要该控件在XML文件中定义过ID即可。第3行的setText()函数用来设置TextView所显示的内容。
5.2.2Button和ImageButton
Button是按钮控件,用户点击该控件,能够引发相应的事件处理函数。如果需要在按钮上显示图像,可以使用Android系统提供的ImageButton控件,图5.4是ButtonDemo示例,上方是Button控件,下方是ImageButton控件。
图5.4ButtonDemo示例
ButtonDemo示例从上至下分别是TextView01、Button01和ImageButton01。在XML文件(/res/layout/main.xml)中的代码如下。 1
6
9XML文件中定义了两个按钮的宽度和高度,并定义了Button控件所显示的内容,但没有定义ImageButton所显示的图像,显示图像内容在后面的代码中进行定义。
Android系统支持多种图形格式,如png、ico等,本例ImageButton所使用的是png格式。首先在/res目录下建立drawable目录,然后将download.png文件复制到/res/drawable文件夹下。
ButtonDemo.java文件中,引用XML文件建立的Button控件和ImageButton控件,更改Button显示字符内容和ImageButton图像内容。为了程序正常运行,需要在代码中引入android.widget.Button和android.widget.ImageButton。1Button button=(Button)findViewById(R.id.Button01);
2ImageButton imageButton=(ImageButton)findViewById(R.id.ImageButton01);
3button.setText("Button按钮");
4imageButton.setImageResource(R.drawable.download);第1行和第2行代码用于引用在XML文件中定义的Button控件和ImageButton控件。第3行代码将Button的显示内容更改为“Button按钮”。第4行代码利用setImageResource()函数,将新加入的png文件R.drawable.download传递给ImageButton。
为了能够使按钮响应点击事件,在onCreate()函数中为Button控件和ImageButton控件添加点击事件的监听器,代码如下。1final TextView textView=(TextView)findViewById(R.id.TextView01);
2button.setOnClickListener(new View.OnClickListener(){
3public void onClick(View view){
4textView.setText("Button按钮");
5}
6});
7imageButton.setOnClickListener(new View.OnClickListener(){
8public void onClick(View view){
9textView.setText("ImageButton按钮");
10}
11});在第2行代码中,button对象通过调用setOnClickListener()函数,注册一个点击(Click)事件的监听器View.OnClickListener()。第3行代码是点击事件的回调函数。第4行代码将TextView的显示内容更改为“Button按钮”。
View.OnClickListener()是View定义的点击事件的监听器接口,并在接口中仅定义了onClick()函数。当Button从Android界面框架中接收到事件后,首先检查这个事件是否是点击事件,如果是点击事件,同时Button又注册了监听器,则会调用该监听器中的onClick()函数。
每个View仅可以注册一个点击事件的监听器,如果使用setOnClickListener()函数注册第二个点击事件的监听器,之前注册的监听器将被自动注销。为每个按钮注册一个点击事件监听器,每个按钮的事件处理程序都有各自的onClick()函数,这样能够使代码更加清晰、易读,且易于维护。当然,也可以将多个按钮注册到同一个点击事件的监听器上,示例代码如下。1Button.OnClickListener buttonListener=new Button.OnClickListener(){
2@Override
3public void onClick(View v){
4switch(v.getId()){
5case R.id.Button01:
6textView.setText("Button按钮");
7return;
8case R.id.ImageButton01:
9textView.setText("ImageButton按钮");
10return;
11}
12}};
13button.setOnClickListener(buttonListener);
14imageButton.setOnClickListener(buttonListener);第1~12行代码定义了一个名为buttonListener的点击事件监听器,第13行和第14行代码分别将该监听器注册到Button和ImageButton上。
5.2.3CheckBox和RadioButton
CheckBox是同时可以选择多个选项的控件,而RadioButton则是仅可以选择一个选项的控件。RadioGroup是RadioButton的承载体,程序运行时不可见,应用程序中可能包含一个或多个RadioGroup。RadioGroup包含多个RadioButton,在一个RadioGroup中,用户仅能够选择其中的一个RadioButton。在图5.5中,RadioGroup中包含两个RadioButton,当选择RadioButton01后,RadioButton02自动变为非选择状态。
图5.5CheckboxRadiobuttonDemo示例
CheckboxRadiobuttonDemo示例如图5.5所示,从上至下分别是TextView01、CheckBox01、CheckBox02、RadioButton01和RadioButton02。在XML文件(/res/layout/main.xml)中的代码如下。1
5
9
10
14
15
18
22
23
27
28第15行代码中的标签声明了一个RadioGroup,第18行和第23行代码分别声明了两个RadioButton,这两个RadioButton是RadioGroup的子元素。
在代码中引用CheckBox和RadioButton的方法可以参考下面的代码。1CheckBox checkBox1=(CheckBox)findViewById(R.id.CheckBox01);
2RadioButton radioButton1=(RadioButton)findViewById(R.id.RadioButton01);CheckBox设置点击事件监听器的方法,与Button中介绍的方法相似,唯一不同在于将Button.OnClickListener换成了CheckBox.OnClickListener,下面给出简要代码。1CheckBox.OnClickListener checkboxListener=new CheckBox.OnClickListener(){
2@Override
3public void onClick(View v){
4//过程代码
5}};
6checkBox1.setOnClickListener(checkboxListener);
7checkBox2.setOnClickListener(checkboxListener);RadioButton设置点击事件监听器的方法。1RadioButton.OnClickListener radioButtonListener=new RadioButton
.OnClickListener(){
2@Override
3public void onClick(View v){
4//过程代码
5}};
6radioButton1.setOnClickListener(radioButtonListener);
7radioButton2.setOnClickListener(radioButtonListener);5.2.4Spinner
Spinner是从多个选项中选择一个选项的控件,类似于桌面程序的组合框(ComboBox),但没有组合框的下拉菜单,而是使用浮动菜单为用户提供选择。
SpinnerDemo示例如图5.6所示,从上至下分别是TextView01和Spinner01。在XML文件(/res/layout/main.xml)中的代码如下。 1
5
8图5.6SpinnerDemo示例
第5行代码使用标签声明了一个Spinner控件,并在第6行代码中指定该控件的宽度为300dip。
在SpinnerDemo.java文件中,定义一个ArrayAdapter适配器,在ArrayAdapter中添加在Spinner中可以选择的内容。为了使程序能够正常运行,需要在代码中引入android.widget.ArrayAdapter和android.widget.Spinner。1Spinner spinner=(Spinner)findViewById(R.id.Spinner01);
2List list =new ArrayList();
3list .add("Spinner子项1");
4list .add("Spinner子项2");
5list .add("Spinner子项3");
6ArrayAdapter adapter=new ArrayAdapter(this, android.R.layout.simple_spinner_item, list);
7adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
8spinner.setAdapter(adapter);第2行代码建立了一个字符串数组列表(ArrayList),这种数组列表可以根据需要进行增减,表示数组列表中保存的是字符串类型的数据。在代码的第3~5行中,使用add()函数分别向数组列表中添加3个字符串。第6行代码建立了一个ArrayAdapter的数组适配器,数组适配器能够将界面控件和底层数据绑定在一起。在这里,ArrayAdapter将Spinner和ArrayList绑定在一起,所有ArrayList中的数据将显示在Spinner的浮动菜单中,绑定过程由第8行代码实现。第7行代码设定了Spinner浮动菜单的显示方式,其中,android.R.layout.simple_spinner_dropdown_item是Android系统内置的一种浮动菜单,如图5.6所示。如果将其改为android.R.layout.simple_spinner_item,则显示效果如图5.7所示。
图5.7Spinner的item菜单
为了保证用户界面显示的内容与底层数据一致,应用程序需要监视底层数据的变化,如果底层数据更改了,则用户界面也需要修改显示内容。在使用适配器绑定界面控件和底层数据后,应用程序就不需要再监视底层数据的变化,从而极大地简化了代码的复杂性。
5.2.5ListView
ListView是用于垂直显示的列表控件,如果显示内容过多,则会出现垂直滚动条。ListView是在界面设计中经常使用的界面控件,其原因是ListView能够通过适配器将数据和显示控件绑定,在有限的屏幕上提供大量内容供用户选择;而且它支持点击事件,可以用少量的代码实现复杂的选择功能。
ListViewDemo示例如图5.8所示,从上至下分别是TextView01和ListView01。在XML文件(/res/layout/main.xml)中的核心代码如下。1
5
8图5.8ListViewDemo示例