第3章〓UI编程基础
本章思维导图



本章目标

 了解Android中的UI元素; 

 能够使用布局管理器对界面进行管理; 

 掌握界面交互事件处理机制及实现步骤; 

 能够熟练使用常用的Widget简单组件; 

 掌握Dialog对话框的使用。

3.1Android UI元素

UI(User Interface,用户界面)设计是指对软件的人机交互、操作逻辑、界面美观的整体设计。良好的UI设计不仅是让软件变得更加人性化,还让软件的操作变得舒适、简单、自由、充分体现软件的定位和特点。Android借鉴了Java中的UI设计思想,包括事件响应机制和布局管理,提供了丰富的可视化用户界面组件,例如,菜单、对话框、按钮和文本框等。

Android中界面元素主要由以下几个部分构成。

 视图(View): 视图是所有可视界面元素(通常称为控件或小组件)的基类,所有UI控件都是由View类派生而来的。

 视图容器(ViewGroup): 视图容器是视图类的扩展,其中包含多个子视图。通过扩展ViewGroup类,可以创建由多个相互连接的子视图所组成的复合控件,还可以创建布局管理器从而实现Activity中的布局。

 布局管理(Layout): 布局管理器是由ViewGroup派生而来,用于管理组件的布局格式,组织界面中组件的呈现方式。

 Activity: 用于为用户呈现窗口或屏幕,当程序需要显示一个UI界面时,需要为Activity分配一个视图(通常是一个布局或Fragment)。

 Fragment: Fragment是Android 3.0引入的新API,代表了Activity的子模块,即Activity片段(Fragment本身就是片段的意思)。Fragment可用于UI的各个部分,特别适合针对不同屏幕尺寸时,优化UI布局以及创建可重用的UI元素。每个Fragment都包含自己的UI布局,并接受相应的输入事件,但使用时必须与Activity紧密绑定在一起(Fragment必须嵌入到Activity中)。

因此,一个复杂的Android界面设计往往需要不同的组件组合才能实现,有时需要对这些标准视图进行扩展或者修改从而提供更好的用户体验。





3.1.1视图

View视图组件是用户界面的基础元素,View对象是Android屏幕上一个特定的矩形区域的布局和内容属性的数据载体,通过View对象可实现布局、绘图、焦点变换、滚动条、屏幕区域的按键、用户交互等功能。Android应用的绝大部分UI组件都放在android.widget包及其子包中,所有这些UI组件都继承了View类。View的常见子类及功能如表31所示。


表31View类的主要子类



类名功 能 描 述类名功 能 描 述


TextView文本视图DigitalClock数字时钟
EditText编辑文本框AnalogClock模拟时钟
Button按钮ProgessBar进度条
Checkbox复选框RatingBar评分条
RadioGroup单选按钮组SeekBar搜索条
Spinner下拉列表GridView网格视图
AutoCompleteTextView自动完成文本框ListView列表视图
DataPicker日期选择器ScrollView滚动视图
TimePicker时间选择器




注意本章后续各节将对上述View组件进行重点讲解。


3.1.2视图容器

View类还有一个非常重要的ViewGroup子类,该类通常作为其他组件的容器使用。View组件可以添加到ViewGroup中,也可以将一个ViewGroup添加到另一个ViewGroup中。Android中的所有UI组件都是建立在View、ViewGroup基础之上,Android采用了“组合器”模式来设计View和ViewGroup; 其中,ViewGroup是View的子类,因此ViewGroup可以当成View来使用。对于一个Android应用的图形UI而言,ViewGroup又可以作为容器来盛装其他组件; ViewGroup不仅可以包含普通的View组件,还可以包含其他ViewGroup组件。Android图形UI的组件层次如图31所示。




图31UI组件层次





注意图31来自Android开发文档,对于每个Android程序员而言,Android提供的官方文档都需要仔细阅读。


ViewGroup类提供的主要方法如表32所示。


表32ViewGroup类的方法功能



方法功 能 描 述


ViewGroup()构造方法
void addView(View child)用于添加子视图,以View作为参数,将该View增加到当前视图组中
removeView(View view)将指定的View从视图组中移除
updateViewLayout(View view, 
ViewGroup.LayoutParams params)用于更新某个View的布局
void bringChildToFront(View child)将参数所指定的视图移动到所有视图之前显示
boolean clearChildFocus(View child)清除参数所指定的视图的焦点
boolean dispatchKeyEvent(KeyEvent event)将参数所指定的键盘事件分发给当前焦点路径的视图。当分发事件时,按照焦点路径来查找合适的视图。若本视图为焦点,则将键盘事件发送给自己; 否则发送给焦点视图

boolean dispatchPopulateAccessibilityEvent
(AccessibilityEvent event)将参数所指定的事件分发给当前焦点路径的视图
boolean dispatchSetSelected(boolean selected)为所有的子视图调用setSelected()方法




注意ViewGroup继承了View类,虽然可以当成普通的View来使用,但习惯上将ViewGroup当容器来使用。由于ViewGroup是一个抽象类,在实际应用中通常使用ViewGroup的子类作为容器,例如,各种布局管理器。


1.ViewGroup继承结构

ViewGroup的继承者大部分位于android.widget包中,其直接子类包括AdapterView、AbsoluteLayout、FrameLayout、LinearLayout和RelativeLayout等。以上直接子类又分别具有子类,ViewGroup继承者的体系结构如图32所示。




图32ViewGroup继承者的体系结构


如图32所示,ViewGroup直接子类均可作为容器来使用,这些类为子类提供不同的布局方法,用于设置子类之间的位置和尺寸关系。ViewGroup类的间接子类中,有些不能作为容器来使用,仅能当作普通的组件来使用。

2. 布局参数类

在Android布局文件中,每个组件所能使用的XML属性有以下三类。

 组件本身的XML属性。

 组件祖先类的XML属性。

 组件所属容器的布局参数。

其中,布局参数是包含该组件的容器(如ViewGroup子类)所提供的参数。在Android中,ViewGroup子类都有一个相应的{XXX}.LayoutParams静态子类,用于设置子类所使用的布局方式。这些子类继承关系和ViewGroup子类的继承关系具有相似性。

ViewGroup容器使用ViewGroup.LayoutParams和ViewGroup.MarginLayoutParams两个内部类来控制子组件在其中的分布位置,这两个内部类中都提供了一些XML属性,ViewGroup容器中的子组件通过指定XML属性来控制组件的位置,如表33所示。


表33ViewGroup子元素支持的属性



XML属性功 能 描 述


android:layout_width设定该组件的子组件布局的宽度
android:layout_height设定该组件的子组件布局的高度


android:layout_height和android:layout_width属性都支持以下三个属性值。

 fill_parent属性用于指定子组件的高度、宽度与父容器的高度、宽度相同。

 match_parent与fill_parent的功能完全相同,从Android 2.2开始推荐使用该属性值来代替fill_parent。

 wrap_content属性用于指定子组件的大小恰好能包裹其内容即可。



注意在实际应用中,除了为组件指定高度、宽度,还需要设置布局的高度、宽度,这是由Android的布局机制决定的。Android组件的大小不仅由实际的宽度、高度控制,还由布局的高度、宽度控制。例如,一个组件的宽度为30px,如果将其布局宽度设置为match_parent,那么该组件的宽度将会被“拉宽”并占满其所在的父容器; 如果将其布局宽度设为wrap_cotent,那么该组件的宽度才会是30px。


ViewGroup.MarginLayoutParams用于控制子组件周围的页边距(即组件四周的留白),所支持的XML属性如表34所示。


表34MarginLayoutParams支持的属性



XML属性功 能 描 述


android:layout_marginTop指定该子组件上面的页边距
android:layout_marginRight 指定该子组件右面的页边距
android:layout_marginBottom指定该子组件下面的页边距
android:layout_marginLeft指定该子组件左面的页边距




注意由于LayoutParams也具有继承关系,因此LinearLayout的子类除了可以使用LinearLayout.LayoutParams所提供的XML属性外,还可以使用其祖先类ViewGroup.LayoutParams的XML属性。


3.1.3布局管理

针对不同的手机屏幕(如手机屏幕的分辨率不同或屏幕尺寸不同等情况),当在程序中手动控制每个组件的大小和位置时,将会给编程带来巨大的困难。为了解决这个问题,Android提供了布局管理器,使得Android各类组件(如按钮、文本等组件)能够适应屏幕的变化。布局管理器可以根据运行平台来调整组件的大小,开发者只需为容器选择合适的布局管理器即可。

Android的布局管理器本身是一种UI组件,所有的布局管理器都是ViewGroup的子类,Android布局管理器类之间的关系如图33所示。




图33Android布局管理器类之间的关系


所有布局都可以作为容器来使用,通过调用addView()方法向布局管理器中添加组件。此外,布局管理器还继承了View类,在实际编程过程中可以把布局管理器作为普通的UI组件嵌套到其他布局管理器中。

Android提供了多种布局,常用的布局有以下几种。

 LinearLayout(线性布局): 该布局中子元素之间成线性排列,即在水平或垂直方向上的顺序排列。

 RelativeLayout(相对布局): 该布局是一种根据相对位置排列元素的布局方式,允许子元素指定相对于其他元素或父元素的位置(一般通过ID指定)。在线性布局中排列子元素时,不需要特殊指定参照物,而相对布局中的子元素必须指定其参照物,只有指定参照物之后,才能定义该元素的相对位置。

 TableLayout(表格布局): 该布局将子元素的位置分配到表格的行或列中,即按照表格形式的顺序排列。一个表格布局中有多个“表格行”,而表格行中又包含多个“表格单元”。表格布局并不是真正意义上的表格,只是按照表格的方式组织元素的布局,元素之间并没有实际表格中的分界线。

 AbsoluteLayout绝对布局: 按照绝对坐标对元素进行布局。与相对布局不同,绝对布局不需要指定参照物,而是使用整个手机界面作为坐标系,通过坐标系的水平偏移量和垂直偏移量来确定其唯一位置。

3.1.4Fragment

Fragment允许将Activity拆分成多个完全独立的可重用的组件,每个组件具有自己的生命周期和UI布局。Fragment最大的优点就是灵活地为不同大小屏幕的设备创建UI界面,例如,小屏幕的智能手机和大屏幕的平板电脑。

每个Fragment都是一个独立的模块,并与所绑定的Activity紧密地联系在一起。一个Fragment可以被多个Activity所共用,一个界面有可以有多个UI模块,对于像平板电脑的设备,Fragment展现了很好的适应性和动态创建UI的能力,在一个Activity中可以添加、删除、更换Fragment。Fragment为不同型号、尺寸、分辨率的设备提供了统一的UI优化方案。



注意有关Fragment的生命周期及详细使用方法参见第5章。


3.2界面布局

Android中提供了以下两种创建布局的方式。

 在XML布局文件中声明: 首先将需要显示的组件在布局文件中进行声明,然后在程序中通过setContentView(R.layout.XXX)方法将布局呈现在Activity中。推荐使用此种方式,前面的程序也一直使用此种方式。

 在程序中直接实例化布局及其组件: 此种方式并不提倡使用,除非界面中的组件及布局需要动态改变才使用。

常见的Android布局包括LinearLayout、RelativeLayout、TableLayout和AbsoluteLayout等多种布局。






3.2.1线性布局

LinearLayout是一种线性排列的布局,布局中的组件按照垂直或者水平方向进行排列,排列方向是由android:orientation属性进行控制,其属性值包括垂直(vertical)和水平(horizontal)两种。LinearLayout对应的类为android.widget.LinearLayout。

LinearLayout常用的XML属性及相关方法的说明如表35所示。


表35LinearLayout常用的XML属性及对应方法



XML属性对 应 方 法功 能 描 述


android:dividersetDividerDrawable()设置垂直布局时两个按钮之间的分隔条
android:gravitysetGravity()设置布局管理器内组件的对齐方式。该属性支持top、bottom、left、right、center_vertical、fill_vertical、center_horizontal、fill_horizontal、center、fill、clip_vertical、clip_horizontal、start、end几个属性值。也可以指定多种对齐方式的组合,例如,left|center_vertical代表出现在屏幕左边,且垂直居中
android:orientationsetOrientation()设置布局管理器内组件的排列方式,参数可以为horizontal(水平排列)或vertical(垂直排列,默认值)


此外,LinearLayout中包含的所有子元素的位置都受LinearLayout.LayoutParams控制,LinearLayout包含的子元素可以额外指定属性,如表36所示。


表36LinearLayout子元素常用XML属性及说明



XML属性功 能 描 述


android:layout_gravity指定子元素在LinearLayout中的对齐方式
android:layout_weight指定子元素在LinearLayout中所占的比重




注意线性布局不会换行,当组件顺序排列到屏幕边缘时,剩余的组件不会被显示出来。


在项目的res\\layout目录下创建一个线性布局文件linearlayout.xml。如图34所示,打开res文件目录,右击layout文件夹,选择New→XML→Layout XML File命令,创建一个新的XML布局文件。




图34创建新的XML布局文件


LinearLayout布局代码如下。

【案例31】linearlayout.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical"

android:gravity="center_horizontal">

<TextView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="txtView1"

android:textColor="#000000"

android:textSize="20sp"/>

<TextView  android:text="txtView2" .../>

<TextView  android:text="txtView3" .../>

<TextView  android:text="txtView4" .../>

<TextView  android:text=" txtView5" .../>

</LinearLayout>





上述代码中,页面布局相对比较简单,仅定义了一个线性布局,并在布局中定义了5个TextView; 在定义线性布局时默认采用垂直排列方式,且所有组件在容器的顶部居中对齐。如图35所示,在Android Studio中编写、设计页面布局可以采用三种模式: Code只显示左侧代码窗口; Split以切分方式左侧显示代码,右侧显示设计窗口; Design只显示右侧设计窗口。




图35页面布局窗口三种模式


在LayoutActivity中使用linearlayout.xml布局,代码如下。

【案例32】LayoutActivity.java



public class LayoutActivity extends AppCompatActivity {

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.linearlayout);

}

}





上述代码中,调用setContentView()方法将布局设置到屏幕中,运行结果如图36(a)所示。在上述布局文件中,将LinearLayout属性修改为android:gravity="center",即垂直方向居中,再次运行LayoutActivity结果如图36(b)所示。



图36线性布局

3.2.2表格布局

TableLayout类似表格形式,以行和列的方式来布局子组件。TableLayout继承了LinearLayout,因此其本质上依然是线性布局。TableLayout并不需要明确地声明所包含的行数和列数,而是通过TableRow及其子元素来控制表格的行数和列数。






通常情况下,TableLayout的行数由开发人员直接指定,即TableRow对象(或View控件)的个数; TableLayout的列数等于含有最多子元素的TableRow所包含的元素个数,例如,第一个TableRow中含2个子元素,第二个TableRow中含3个,第三个TableRow中含4个,则该TableLayout的列数为4。

在TableLayout布局中,某列的宽度是由该列中最宽的那个单元格决定,整个表格布局的宽度则取决于父容器的宽度(默认总是占满父容器本身)。

在表格布局器中,可以通过以下3种方式对单元格进行设置。

 Shrinkable: 如果某个列被设置为Shrinkable,那么该列中所有单元格的宽度都可以被收缩,以保证表格能适应父容器的宽度。

 Stretchable: 如果某个列被设置为Stretchable,那么该列中所有单元格的宽度都可以被拉伸,以保证组件能够完全填满表格的空余空间。

 Collapsed: 如果某个列被设置为Collapsed,那么该列中所有单元格都会被隐藏。

TableLayout可设置的属性包括全局属性和单元格属性,全局属性也被称为列属性。TableLayout常用的全局XML属性及相关方法如表37所示。


表37TableLayout常用XML属性及对应方法



XML属性对 应 方 法功 能 描 述


android:shrinkColumnssetShrinkAllColumns(boolean)设置可收缩的列。当该列子控件的内容太多,已经挤满所在行时,子控件的内容将往列方向显示,多个列之间用逗号隔开
android:stretchColumnssetStretchAllColumns(boolean)设置可伸展的列。该列可以横向伸展,最多可占据一整行,多个列之间用逗号隔开
android:collapseColumnssetColumnCollapsed(int,boolean)设置要隐藏的列,多个列之间用逗号隔开


下述代码演示了表格的全局属性的使用。

【示例】全局属性的设置



<?xml version="1.0" encoding="utf-8"?>

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:stretchColumns="0"

android:collapseColumns="*"

android:shrinkColumns="1,2" >

</TableLayout>





其中: 

 android:stretchColumns="0"表示第0列可伸展。

 android:shrinkColumns="1,2"表示第1、2列皆可收缩。

 android:collapseColumns="*"表示隐藏所有行。



注意列可以同时具备stretchColumns和shrinkColumns属性; 当该列的内容较多时,将以“多行”方式显示其内容。(此处所指的“多行”不是真正的多行,而是系统根据需要自动调节该行的layout_height。)


TableRow.LayoutParams常用的单元格XML属性及方法如表38所示,通常对TableRow的子元素进行修饰。


表38TableRow.LayoutParams的单元格XML属性及对应方法



XML属性功 能 描 述


android:layout_column指定该单元格在第几列显示
android:layout_span指定该单元格占据的列数(未指定时,默认为1)


下述代码演示了表格属性的设置。

【示例】对表格属性进行设置



<?xml version="1.0" encoding="utf-8"?>

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:stretchColumns="0"

android:collapseColumns="*"

android:shrinkColumns="1,2" >

<TableRow>

<Button  android:layout_span="2"/>

<Button  android:layout_column="1"/>

</TableRow>

</TableLayout>





其中: 

 android:layout_span="2"表示该控件占据2列。

 android:layout_column="1"表示该控件显示在第1列。



注意由于TableLayout继承了LinearLayout,因此完全支持LinearLayout所支持的全部XML属性。


下述代码用于演示TableLayout的基本使用。在res\\layout目录下创建一个表格布局文件tablelayout.xml。

【案例33】tablelayout.xml



<?xml version="1.0" encoding="utf-8"?>

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/MorePageTableLayout_Favorite"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:collapseColumns="2"

android:shrinkColumns="0"







android:stretchColumns="0" >

<TableRow

android:id="@+id/more_page_row1"

android:layout_width="fill_parent"

android:layout_marginLeft="2.0dip"

android:layout_marginRight="2.0dip"

android:paddingBottom="16.0dip"

android:paddingTop="8.0dip" >

<TextView

android:layout_width="wrap_content"

android:layout_height="fill_parent"

android:drawablePadding="10.0dip"

android:gravity="center_vertical"

android:includeFontPadding="false"

android:paddingLeft="17.0dip"

android:text="账号管理"

android:textColor="#ff333333"

android:textSize="16.0sp" />



<ImageView

android:layout_width="wrap_content"

android:layout_height="fill_parent"

android:layout_gravity="right"

android:gravity="center_vertical"

android:paddingRight="20.0dip"

android:src="@drawable/item_arrow" />

</TableRow>

<TableRow  android:id="@+id/more_page_row0" ...>

<TextView  android:text="搜索商品" .../>

<ImageView  android:src="@drawable/item_arrow" .../>

</TableRow>

<TableRow  android:id="@+id/more_page_row2" ...>

<TextView   android:text="浏览记录" .../>

<ImageView  android:src="@drawable/item_arrow" .../>

</TableRow>

</TableLayout>






上述代码中: 

 使用<TableLayout>元素定义了表格布局,该元素的android:collapseColumns属性用于指明表格的列数,此处设置表格的列数为2。android:stretchColumns属性用于指明表格的伸展列,将指定列进行拉伸以填满剩余的空间; 注意列号从0开始,此处0表示第1列为伸展列。

 使用<TableRow>元素定义了表格中的行,其他组件都放在该元素内。

在LayoutActivity中,使用tablelayout.xml布局,相关代码如下。



setContentView(R.layout.tablelayout);





运行结果如图37所示。将<TableLayout>元素中的android:stretchColumns="0"删除,即不指定伸展列时,运行结果如图38所示。




图37第一列为延伸列




图38普通的表格布局






注意Android的表格布局跟HTML中的表格布局非常类似,TableRow相当于HTML表格的<tr>标记。








3.2.3相对布局

RelativeLayout是一组相对排列的布局方式,在相对布局容器中子组件的位置总是相对于兄弟组件或父容器,例如,一个组件在另一个组件的左边、右边、上边或下边等位置。在相对布局容器中,当A组件的位置是由B组件来决定时,Android要求先定义B组件,再定义A组件。

RelataiveLayout位于android.widget包中,其常用XML属性及方法如表39所示。


表39RelativeLayout常用XML属性及方法



XML属性对 应 方 法功 能 描 述


android:gravitysetGravity()设置布局管理器内组件的对齐方式。该属性支持包括top、bottom、left、right、center_vertical、fill_vertical、center_horizontal、fill_horizontal、center、fill、clip_vertical、clip_horizontal、start和end。也可以同时指定多种对齐方式的组合,例如,left|center_vertical代表出现在屏幕左边且垂直居中
android:ignoreGravitysetIgnoreGravity()设置特定的组件不受gravity属性的影响


为了控制该布局容器中各个子组件的布局分布,RelativeLayout提供了一个内部类: RelativeLayout.LayoutParams,该类提供了大量的XML属性来控制RelativeLayout布局中子组件的位置分布,如表310所示,表中所列属性取值只能为true或false。


表310RelativeLayout.LayoutParams的XML属性及说明(一)



XML属性功 能 描 述


android:layout_alignParentLeft指定该组件是否与布局容器左对齐
android:layout_alignParentTop指定该组件是否与布局容器顶端对齐
android:layout_alignParentRight指定该组件是否与布局容器右对齐
android:layout_alignParentBottom指定该组件是否与布局容器底端对齐
android:layout_centerInParent指定该组件是否位于布局容器的中央位置
android:layout_centerHorizontal指定该组件是否在布局容器中水平居中
android:layout_centerVertical指定该组件是否在布局容器中垂直居中


RelativeLayout.LayoutParams中另外一部分的属性值可以是其他UI组件的ID值,表示当前组件与指定ID组件的相对位置,如表311所示。


表311RelativeLayout.LayoutParams的XML属性及说明(二)



XML属性功 能 描 述


android:layout_toLeftOf控制该组件位于指定ID组件的左侧
android:layout_toRightOf控制该组件位于指定ID组件的右侧
android:layout_above控制该组件位于指定ID组件的上方
android:layout_below控制该组件位于指定ID组件的下方
android:layout_alignLeft控制该组件与指定ID组件的左边界进行对齐
android:layout_alignTop控制该组件与指定ID组件的上边界进行对齐
android:layout_alignRight控制该组件与指定ID组件的右边界进行对齐
android:layout_alignBottom控制该组件与指定ID组件的下边界进行对齐


此外,RelativeLayout.LayoutParams还继承了android.view.ViewGroup.MarginLayoutParams类,该类用于定义组件边缘的空白,具有android:layout_marginTop、android:layout_marginLeft、android:layout_marginBottom、android:layout_marginRight四个XML属性,分别表示上、左、下、右四个方向的边缘空白。

下面通过对“东西南北中”的布局,来演示RelativeLayout的使用。

【案例34】relativelayout.xml



<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent" >

<TextView

android:id="@+id/middle"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerInParent="true"

android:text="中" />

<TextView android:id="@+id/west" android:layout_toLeftOf="@id/middle"...

android:text="西" />

<TextView android:id="@+id/east" android:layout_toRightOf="@id/middle"...

android:text="东" />

<TextView android:id="@+id/north" android:layout_above="@id/middle"...

android:text="北" />

<TextView android:id="@+id/south" android:layout_below="@id/middle"...

     android:text="南" />

</RelativeLayout>






上述代码使用<RelativeLayout>元素定义了一个相对布局,该布局中含有5个文本,分别位于“东、西、南、北、中”。由于相对布局中的组件总是由其他组件来决定分布的位置,在设计过程中首先把“中”元素放到布局容器的中间,然后以该组件为中心,依次将“东、西、南、北”四个组件分布到四周,这样就形成了“上北下南左西右东”的布局效果。文本的摆放位置具体如下。

 “西”位于文本“中”的左边,即通过layout_toLeftOf属性进行设置。

 “东”位于文本“中”的右边,即通过layout_toRightOf属性进行设置。

 “北”位于文本“中”的上边,即通过layout_above属性进行设置。

 “南”位于文本“中”的下边,即通过layout_below属性进行设置。

在LayoutActivity中,使用relativelayout.xml布局,相关代码如下。



setContentView(R.layout.relativelayout);







图39相对布局

运行结果如图39所示。

在图39中,以“中”为中心,所显示的“上北下南左西右东”偏离了预想的位置; 如果希望“东西南北”四个元素以“中”为中心,分别位于正东、正西、正南、正北”四个方位,需要在布局文件中通过android:layout_alignXXX属性来指定具体的对齐方式。

 “中”的“正西”: 由于两个文字的高度相同,可以通过android:layout_alignTop属性进行设置。

 “中”的“正东”: 通过android:layout_alignTop属性进行设置。

 “中”的“正南”: 由于两个文字的长度相同,可以通过android:layout_alignLeft属性进行设置。

 “中”的“正北”: 通过android:layout_alignLeft属性进行设置。

对relativelayout.xml布局文件进行改进,改进后的代码如下。

【案例35】relativelayout.xml



<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent" >

<TextView android:id="@+id/middle" ... android:text="中" />

<TextView android:id="@+id/west" ...

android:layout_alignTop="@id/middle"

android:layout_toLeftOf="@id/middle"

android:text="西" />

...

</RelativeLayout>








注意如果五个方位的字符串长度不同,则需要选择居中对齐的方式,例如,使用android:layout_centerHorizontal="true"来实现。


运行上述代码结果如图310所示。

图310显示了传统意义上的“上北、下南、左西、右东”各个方位,但五个方位之间过于紧凑,需要调整一下元素之间的间距,因此可以使用android:layout_margin等属性来设置元素的边缘空白,例如,将边缘空白设置为10dp。

对relativelayout.xml布局文件进一步改进,改进后的代码如下。

【案例36】relativelayout.xml



<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent" >

<TextView

android:id="@+id/middle"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerInParent="true"

android:layout_margin="10dp"

android:text="中" />

...

</RelativeLayout>





运行上述代码结果如图311所示。




图310相对布局(对齐)




图311相对布局(页边距)






注意上述效果除了通过设置除了“中”之外,还可以通过设置其他四个方位对象的android:layout_marginXX来实现上述效果,此处不再赘述,请读者验证之。








3.2.4绝对布局

AbsoluteLayout通过指定组件的确切X、Y坐标来确定组件的位置。下述代码用于演示AbsoluteLayout的使用。

【案例37】absolutelayout.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical" android:layout_width="match_parent"

android:layout_height="match_parent">

<AbsoluteLayout android:id="@+id/AbsoluteLayout01"

android:layout_width="wrap_content" 

android:layout_height="wrap_content">

<Button android:text="A" android:id="@+id/Button01"

android:layout_width="wrap_content" 

android:layout_height="wrap_content"

android:layout_x="10dp" android:layout_y="20dp"></Button>

<Button android:text="B" android:id="@+id/Button02" ...

android:layout_x="100dp" android:layout_y="20dp"></Button>

<Button android:text="C" android:id="@+id/Button03" ...

android:layout_x="10dp" android:layout_y="80dp"></Button>

<Button android:text="D" android:id="@+id/Button04" ...

android:layout_x="100dp" android:layout_y="80dp"></Button>

</AbsoluteLayout>

</LinearLayout>







图312绝对布局


上述代码使用<AbsoluteLayout>元素来定义绝对布局,该布局中有四个按钮,每个按钮的位置都是通过X、Y轴坐标进行指定,其中,layout_x属性用于指定元素的X轴坐标,layout_y属性用于指定元素的Y轴坐标。

在LayoutActivity中,使用absolutelayout.xml布局,相关代码如下。



setContentView(R.layout.absolutelayout);





运行结果如图312所示。


3.3事件处理


当用户在程序界面上执行各种操作时,应用程序必须为用户提供响应动作,通过响应动作来完成事件处理。在图形界面(UI)的开发中,有两个非常重要的内容: 一个是控件的布局,另一个就是控件的事件处理,其中控件的布局已经在3.2节中进简要介绍,本节主要对事件处理进行介绍。

Android提供了两种方式的事件处理: 基于回调的事件处理和基于监听的事件处理。Android系统充分利用这两种事件处理方式的优点,允许开发人员采用自己熟悉的事件处理方式为用户的操作提供响应动作,从而可以开发出界面友好、人机交互效果好的Android应用程序。






3.3.1基于监听的事件处理

基于监听的事件处理方式和Java Swing/AWT的事件处理方式几乎完全相同,如果开发者具有Java Swing方面的编程经验,则更容易上手。

Android系统中引用了Java事件处理机制,包括事件、事件源和事件监听器三个事件模型。

 事件(Event): 是一个描述事件源状态改变的对象,事件对象不是通过new运算符创建的,而是在用户触发事件时由系统生成的对象。事件包括键盘事件、触摸事件等,一般作为事件处理方法的参数,以便从中获取事件的相关信息。

 事件源(Event Source): 触发事件的对象,事件源通常是UI组件,例如,单击按钮时按钮就是事件源。

 事件监听器(Event Listenrer): 当触发事件时,事件监听器用于对该事件进行响应和处理。监听器需要实现监听接口中所定义的事件处理方法。

当用户单击一个按钮或单击某个菜单选项时,这些操作就会触发一个响应事件,该事件就会调用在事件源上注册的事件监听器,事件监听器调用相应的事件处理程序并完成相应的事件处理。基于监听的事件处理流程如图313所示。




图313基于监听的事件处理流程


Android的事件处理机制是一种委派式事件处理机制,该处理方式类似于人类社会的分工协作,例如,某个企业(事件源)进行货物采购(事件)时,企业通常不会自己运输物品,而是找特定的物流公司来运输; 如果发生了火灾(事件),则会委派给消防局(事件监听器)来处理; 而消防局或物流公司也会同时监听多个企业的火灾事件或货物运输事件。委派式的处理方式将事件源和事件监听器分离,从而提供更好的程序模型,有利于提高程序的可维护性和代码的健壮性。

在Android应用程序中,所有的组件都可以针对特定的事件指定一个事件监听器,每个事件监听器可以监听一个或多个事件源。同一个事件源上也可能发生多个事件,例如,在按钮上可能发生单击、获取焦点等事件,委派式事件处理将事件源上的所有可能发生的事件分别委派给不同的事件监听器来处理; 同时也可以让一类事件都使用同一个事件监听器来处理。

Android中常用的事件监听器如表312所示,这些事件监听器以内部接口的形式定义在android.view.View中。


表312Android中的事件监听器



事件监听器接口事件功 能 描 述


OnClickListener单击事件当用户单击某个组件或者按方向键时触发该事件

OnFocusChangeListener焦点事件当组件获得或者失去焦点时触发该事件
OnKeyListener按键事件当用户按下或者释放设备上的某个按键触发该事件
OnTouchListener触摸事件如果设备具有触摸屏功能,当触碰屏幕时触发该事件
OnCreateContextMenuListener创建上下文菜单事件当创建上下文菜单时触发该事件
OnCheckedChangeListener选项改变事件当选择改变时触发该事件


由此可知,事件监听器本质上是一个实现了特定接口的Java对象。在程序中实现事件监听器,通常有以下几种形式。

 Activity本身作为事件监听器: 通过Activity实现监听器接口,并实现事件处理方法。

 匿名内部类形式: 使用匿名内部类创建事件监听器对象。

 内部类或外部类形式: 将事件监听类定义为当前类的内部类或普通的外部类。

 绑定标签: 在布局文件中为指定标签绑定事件处理方法。

通常实现基于监听的事件处理步骤如下。

(1) 创建事件监听器。

(2) 在事件处理方法中编写事件处理代码。

(3) 在相应的组件上注册监听器。

1. Activity本身作为事件监听器

通过Activity实现监听器接口,并实现该接口中对应的事件处理方法。下述代码演示了在Button按钮上绑定单击事件,当单击按钮时改变文字的内容,布局代码如下。

【案例38】event_btn.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:gravity="center_horizontal"

android:orientation="vertical" >

<EditText android:id="@+id/showTxt" android:layout_width="match_parent"

android:layout_height="wrap_content" android:editable="false" />

<Button android:id="@+id/clickBtn" android:layout_width="wrap_content"

android:layout_height="wrap_content" android:text="单击我" />

</LinearLayout>





上述代码定义了Button和EditText两个组件,主要用于实现Button的单击事件。实现监听和事件处理的Activity代码如下。

【案例39】EventBtnActivity.java



//1.实现事件监听器接口

public class EventBtnActivity extends AppCompatActivity

implements OnClickListener{

//单击Button

private Button clickBtn;

//文字显示

private TextView showTxt;

@Override

protected void onCreate(Bundle savedInstanceState) {







super.onCreate(savedInstanceState);

setContentView(R.layout.event_btn);

//初始化组件

showTxt = (TextView) findViewById(R.id.showTxt);

clickBtn = (Button)findViewById(R.id.clickBtn);

//3.直接使用Activity作为事件监听器

clickBtn.setOnClickListener(this);

}

//2. 在事件处理方法中编写事件处理代码

@Override

public void onClick(View v) {

//实现事件处理方法

showTxt.setText("btn按钮被单击了!");

}

}







图314按钮单击效果

运行上述代码,当单击“单击我”按钮时,TextView文本内容将发生改变,效果如图314所示。

上述代码中,定义的EventBtnActivity继承了Activity,并实现了OnClickListener接口,此时Activity对象允许作为事件监听器进行使用。代码“clickBtn.setOnClickListener(this); ”用于为clickBtn按钮注册事件监听器。当单击Button按钮时,触发鼠标单击事件并调用onClick()事件处理方法,TextView文本内容变成“btn按钮被单击了!”。从上面程序中可以看出,基于监听的事件的处理模型的编程步骤如下。

(1) 获取所要触发事件的事件源控件,例如本例中的clickBtn对象。

(2) 实现事件监听器类,本例中的监听器类是Activity对象本身(实现了OnClickListener接口)。

(3) 调用事件源的setXxxListener()方法,将事件监听器注册给事件源对象; 当事件源上发生指定事件时,Android会触发事件监听器,由事件监听器调用相应的方法来处理事件。

2.   匿名内部类形式

Activity的主要职责是完成界面的初始化工作,而案例39中使用Activity本身作为监听器类,并在Activity类中定义事件处理方法,易造成程序结构混乱。大部分情况下事件监听器只是临时使用一次,所以匿名内部类形式的事件监听器更合适。将案例39改为匿名内部类形式,代码如下。

【案例310】AnonymousBtnActivity.java



//实现事件监听器接口

public class AnonymousBtnActivity extends AppCompatActivity{

//单击Button

private Button clickBtn;

//文字显示

private TextView showTxt;







@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.event_btn);

//初始化组件

showTxt = (TextView) findViewById(R.id.showTxt);

clickBtn = (Button)findViewById(R.id.clickBtn);

//使用匿名内部类创建一个监听器

clickBtn.setOnClickListener(new OnClickListener() {



@Override

public void onClick(View v) {

//实现事件处理方法

showTxt.setText("btn按钮被单击了!");

}

});

}

}






上述代码中粗体部分使用匿名内部类创建了一个事件监听器对象,界面效果与图314一致,此处不再演示。

3. 内部类、外部类形式

“内部类”形式是指将事件监听器定义成当前类的内部类。下述代码演示使用内部类的方式实现事件监听。

【案例311】InnerClassBtnActivity.java



//实现事件监听器接口

public class InnerClassBtnActivity extends AppCompatActivity{

//单击Button

private Button clickBtn;

//文字显示

private TextView showTxt;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.event_btn);

//初始化组件

showTxt = (TextView)findViewById(R.id.showTxt);

clickBtn = (Button)findViewById(R.id.clickBtn);

//直接使用Activity作为事件监听器

clickBtn.setOnClickListener(new ClickListener());

}

//内部类方式定义一个事件监听器

class ClickListener implements OnClickListener{

@Override

public void onClick(View v) {

//实现事件处理方法

showTxt.setText("btn按钮被单击了!");

}

}

}





使用内部类有以下优点。

 可以在当前类中复用内部监听器类。

 由于监听器是当前类的内部类,所以可以访问当前类的所有界面组件。



注意外部监听器的定义方式和内部类的定义方式相似,由于使用外部类事件监听器的形式比较少见,此处不再赘述。


4. 绑定标签

Android还有一种更简单的绑定事件的方式,在界面布局文件中直接为指定标签绑定事件处理方法。对于大多数Android界面的组件标签而言,基本都支持onClick事件属性,相应的属性值就是一个类似xxxMethod形式的方法名称。

【案例312】event_tag.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:gravity="center_horizontal"

android:orientation="vertical" >

<EditText android:id="@+id/showTxt" android:layout_width="match_parent"

android:layout_height="wrap_content" android:editable="false" />

<Button android:id="@+id/clickBtn" android:layout_width="wrap_content"

android:layout_height="wrap_content" android:text="单击我" 

android:onClick="clickMe"/>

</LinearLayout>





上述代码中,粗体部分用于为clickBtn按钮绑定一个事件处理方法clickMe,此时需要开发者在相应的Activity中定义一个名为clickMe的方法,该方法用于负责处理按钮的单击事件,代码如下。

【案例313】BindTagActivity.java



//实现事件监听器接口

public class BindTagActivity extends AppCompatActivity{

//单击Button

private Button clickBtn;

//文字显示

private TextView showTxt;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.event_btn);

//初始化组件

showTxt = (TextView) findViewById(R.id.showTxt);

clickBtn = (Button)findViewById(R.id.clickBtn);

}

public void clickMe(View v){

//实现事件处理方法

showTxt.setText("btn按钮被单击了!");

}

}






上述代码中,粗体部分定义了clickMe()方法,其中有一个View类型的参数,方法的返回类型为void。运行上述代码,界面效果与图314一致。

3.3.2基于回调机制的事件处理

在Android平台中,每个View都拥有事件处理的回调方法,开发人员通过重写这些回调方法来实现所需





要响应的事件。当某个事件没有被任何一个View控件处理时,便会调用Activity中相应的回调方法进行处理。从代码实现的角度来看,基于回调的事件处理模型要比基于监听的事件处理模型更为简单。事件监听机制是一种委托式的事件处理,而回调机制则恰好与之相反; 对于基于回调的事件处理模型而言,事件源和事件监听器是统一的,当用户在GUI组件上触发某个事件时,组件自身的方法将会负责处理该事件。

为了实现回调机制的事件处理,Android为所有的GUI组件都提供了事件处理的回调方法,例如,View中提供了onKeyDown()、onKeyUp()、onTouchEvent()、onTrackBallEvent()和onFocusChanged()等事件回调方法。

1. onKeyDown()

onKeyDown()方法是KeyEvent.Callback接口中的抽象方法,所有的View都实现了该接口并重写了onKeyDown()方法,onKeyDown()方法用来捕捉手机键盘按键被按下的事件,方法的签名如下。

【语法】



public boolean onKeyDown (int keyCode, KeyEvent event)





其中: 

 参数keyCode表示被按下的键值(即键盘码),手机键盘中每个按键都有一个单独的键盘码,在应用程序中可通过键盘码的值来判断用户按下的是哪个键。在KeyEvent类中定义了许多常量来表示不同的keyCode,如表313所示。

 参数event用于封装按键事件的对象,其中包含触发事件的详细信息,例如,事件的状态、事件的类型以及事件触发的时间等。当用户按下某个键时,系统自动将事件封装成KeyEvent对象供应用程序使用。

 onKeyDown()方法的返回值为boolean类型,当方法返回true时,表示已经完整地处理了该事件,并不希望其他的回调方法再次进行处理; 当方法返回false时,表示没有完全处理完该事件,其他回调方法可以继续对该事件进行处理,例如,Activity中的回调方法。


表313keyCode的部分值



常量名功 能 描 述


KEYCODE_CALL拨号键
KEYCODE_ENDCALL挂机键
KEYCODE_HOME按键Home
KEYCODE_MENU菜单键
KEYCODE_BACK返回键
KEYCODE_SEARCH搜索键
KEYCODE_CAMERA拍照键
KEYCODE_FOCUS拍照对焦键
KEYCODE_POWER电源键
KEYCODE_NOTIFICATION通知键
KEYCODE_MUTE话筒静音键
KEYCODE_VOLUME_MUTE扬声器静音键
KEYCODE_VOLUME_UP音量增加键
KEYCODE_VOLUME_DOWN音量减小键
KEYCODE_CALL拨号键
KEYCODE_ENDCALL挂机键




注意表313中是常见的手机键盘中的keyCode值; 此外,keyCode还包括控制键、组合键、基本键、符号键、小键盘键和功能键等,读者可以参见KeyEvent代码。


下述代码通过一个简单例子来演示onKeyDown()方法的使用。通过用户的按键,来捕获手机键盘事件,并根据按键情况来显示相关信息。

【案例314】keydown_btn.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:gravity="center_horizontal"

android:orientation="vertical" >

<EditText

android:id="@+id/showTxt"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:editable="false" />

</LinearLayout>






上述代码定义了一个EditText文本框,用于显示用户按下按键时不同的文本。在相应的Activity中实现onKeyDown事件监听,代码如下。

【案例315】KeyDownActivity.java



public class KeyDownActivity extends AppCompatActivity {



//自定义的Button

EditText showText;

public void onCreate(Bundle savedInstanceState) { //重写的onCreate()方法

super.onCreate(savedInstanceState);

setContentView(R.layout.keydown_btn);

showText = (EditText) findViewById(R.id.showTxt);

}

public boolean onKeyDown(int keyCode, KeyEvent event) { 

//重写的键盘按下监听







switch (keyCode) {

case KeyEvent.KEYCODE_BACK:

showText.setText("按下了【回退键】");

break;

case KeyEvent.KEYCODE_0:

showText.setText("0键");

break;

case KeyEvent.KEYCODE_A:

showText.setText("A键");

break;

case KeyEvent.KEYCODE_VOLUME_DOWN:

showText.setText("音量-");

break;

case KeyEvent.KEYCODE_VOLUME_UP:

showText.setText("音量+");

break;

default:

break;

}

return super.onKeyDown(keyCode, event);

}

}





上述代码中,粗体部分为重写的Activity中的onKeyDown()方法,在该方法中实现键盘按下的事件处理,方法返回之前调用父类的同名方法并返回处理结果。当按下手机键盘上的回退键、0键、A键、音量-或音量+键时,在showText文本框中显示对应的文本信息,例如,按下键盘上的音量+键或
时,显示结果如图315所示。

如果将onKeyDown()方法中的返回代码“return super.onKeyDown(keyCode, event); ”改为“return true; ”,然后按下回退键时,回退键的按下事件会被捕获并进行处理,但界面仍然在当前页面,并没有产生回退效果,如图316所示。




图315按下音量+键效果




图316按下回退键效果






注意在实际应用中,有时需要对Home等特殊键进行屏蔽处理,可以采用上述方式对这些键进行捕获并处理。


2. onKeyUp()

onKeyUp()方法也是接口KeyEvent.Callback中的一个抽象方法,并且所有的View都实现了该接口并重写了onKeyUp()方法,onKeyUp()方法用来捕捉手机键盘按键抬起的事件,方法的签名如下。

【语法】



public boolean onKeyUp (int keyCode, KeyEvent event)





其中: 

 参数keyCode表示触发事件的按键码,需要注意的是,同一个按键在不同型号的手机中的按键码可能不同。

 参数event是一个事件封装类的对象,其含义与onKeyDown()方法中的完全相同,此处不再赘述。

 onKeyUp()方法返回值的含义与onKeyDown()方法相同,同样通知系统是否希望其他回调方法再次对该事件进行处理。

onKeyUp()的使用方式与onKeyDown()基本相同,只是onKeyUp()方法在按键抬起时触发调用。如果用户需要对按键被抬起时进行事件处理,可以通过重写该方法来实现。

3. onTouchEvent()

onKeyDown()和onKeyUp()方法主要针对手机键盘事件的处理,onTouchEvent()方法主要针对手机屏幕事件的处理。onTouchEvent()方法在View类中定义,并且所有的View都重写了该方法,应用程序可以通过onTouchEvent()方法来处理手机屏幕的触摸事件。onTouchEvent()方法的签名如下。

【语法】



public boolean onTouchEvent(MotionEvent event)





其中: 

 参数event是一个手机屏幕触摸事件封装类的对象,用于封装事件的相关信息,例如,触摸的位置、触摸的类型以及触摸的时间等。在用户触摸手机屏幕时由系统创建event对象。

 onTouchEvent()方法的返回机制与键盘响应事件的相同,当已经完整地处理了该事件且不希望其他回调方法再次处理时返回true,否则返回false。

与onKeyDown()、onKeyUp()方法不同的是,onTouchEvent()方法可以处理多种事件; 一般情况下,屏幕中的按下、抬起和拖动事件均可由onTouchEvent()方法进行处理,只是每种情况中的动作值有所不同。

 屏幕被按下: 当触摸屏幕时,会自动调用onTouchEvent()方法来处理事件,此时MotionEvent.getAction()的值为MotionEvent.ACTION_DOWN,如果在应用程序中需要处理屏幕被按下的事件,只需重写该回调方法,并在方法中进行动作的判断即可。

 触摸动作抬起: 离开屏幕时所触发的事件,该事件需要onTouchEvent()方法来捕捉,并在该方法中进行动作判断。当MotionEvent.getAction()的值为MotionEvent.ACTION_UP时,表示触发的是触摸屏幕动作抬起的事件。

 在屏幕中拖动: onTouchEvent()方法还用于处理在屏幕上滑动的事件,根据MotionEvent.getAction()方法的返回值来判断动作值是否为MotionEvent.ACTION_MOVE,然后进行相应的处理。

下述代码通过一个简单例子来演示onTouchEvent()方法的使用。在用户单击的位置绘制一个矩形,然后监测用户触摸的状态,当用户在屏幕上移动手指时,使矩形随之移动,而当用户手指离开手机屏幕时,停止绘制矩形。代码如下。

【案例316】KeyTouchActivity.java



public class KeyTouchActivity extends AppCompatActivity {

TouchView touchView;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

//初始化自定义的View ①

touchView = new TouchView(this);

//设置当前显示的用户界面 ②

setContentView(touchView);

}

//重写的onTouchEvent回调方法

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN://手指按下 ③

//改变x坐标 

touchView.x = (int) event.getX();

//改变y坐标 

touchView.y = (int) event.getY() - 52;

touchView.postInvalidate();

//重绘

break;

case MotionEvent.ACTION_MOVE: //手指移动 ④

//改变x坐标

touchView.x = (int) event.getX();

//改变y坐标

touchView.y = (int) event.getY() - 52;

touchView.postInvalidate();

//重绘

break;

case MotionEvent.ACTION_UP://手指抬起⑤ 

//改变x坐标

touchView.x = -100;

//改变y坐标

touchView.y = -100;

//重绘

touchView.postInvalidate();

break;

}







return super.onTouchEvent(event);

}

//定义View的子类⑥

class TouchView extends View {

//画笔

Paint paint;

//x坐标

int x =300;

//y坐标 

int y = 300;

//矩形的宽度 

int width = 100;

public TouchView(Context context) {

super(context);

//初始化画笔⑦

paint = new Paint();



}

@Override

protected void onDraw(Canvas canvas) {

//绘制方法⑧ 

canvas.drawColor(Color.WHITE);

//绘制背景色⑨

canvas.drawRect(x, y, x + width, y + width, paint);

//绘制矩形⑩

super.onDraw(canvas);

}

}

}





代码解释如下。

 标号①
处创建了一个自定义的TouchView对象,标号②
处将该View的对象设置为当前显示的用户界面。

 标号③
用于判断当前事件是否为屏幕被按下的事件,通过调用MotionEvent的getX()和getY()方法得到事件发生的x和y坐标,并赋给TouchView对象的x与y成员变量。

 标号④
用于判断是否为屏幕的滑动事件,同样将得到事件发生的位置并赋给TouchView对象的x、y成员变量。需要注意的是,因为此时手机屏幕并不是全屏模式,所以需要对坐标进行调整。

 标号⑤
用于判断是否为触摸屏幕动作抬起的事件,此时将TouchView对象的x、y成员变量设成-100,表示并不需要在屏幕中绘制矩形。

 标号⑥
处自定义了TouchView类,并重写了View类的onDraw()绘制方法。在⑦
处的构造方法中初始化绘制时需要的画笔,然后在第⑧
~⑩
行的方法中根据成员变量x、y的值来绘制矩形。



注意自定义的View并不会自动刷新,所以每次改变数据模型时都需要手动调用postInvalidate()方法进行屏幕的刷新操作。关于自定义View的使用方法,将会在后面的章节中进行详细介绍,此处只是简单地使用。




图317矩形绘制

运行上述代码,效果如图317所示。


当单击屏幕时,会在所单击的位置绘制一个矩形,当手指在屏幕中滑动时,该矩形会随之移动,而当手指离开屏幕时,取消所绘制的矩形。



注意由于无法在图书中展示动态效果,需要读者自行验证。


4. onTrackBallEvent()

onTrackBallEvent()方法是手机中轨迹球的处理方法。所有的View同样全部实现了该方法,该方法的签名如下。

【语法】



public Boolean onTrackballEvent (MotionEvent event)





其中: 

 参数event为手机轨迹球事件封装类的对象,用于封装触发事件的相关信息,包括事件的类型、触发时间等; 一般情况下,该对象会在用户操控轨迹球时由系统创建。

 onTrackBallEvent()方法的返回机制与前面介绍的各个回调方法完全相同,此处不再赘述。

轨迹球与手机键盘有一定的区别,具体如下。

 某些型号的手机设计出的轨迹球会比只有手机键盘时更美观,可增添用户对手机的整体印象。

 轨迹球使用更为简单,例如,在某些游戏中使用轨迹球控制会更为合理。

 使用轨迹球会比键盘更为细化,即滚动轨迹球时,后台表示状态的数值会变得更细微、更精准。

 onTrackBallEvent()方法的使用与前面介绍过的各个回调方法基本相同,可以在Activity中重写该方法,也可以在View的子类中重写。



注意在模拟器运行状态下,可以通过F6键打开模拟器的轨迹球,然后通过鼠标的移动来模拟轨迹球事件。


5. onFocusChanged()

前面介绍的各个方法都可以在View及Activity中重写,接下来介绍的onFocusChanged()方法只能在View中重写。该方法是焦点改变的回调方法,在某个控件重写了该方法后,当焦点发生变化时,会自动调用该方法来处理焦点改变的事件,该方法的签名如下。

【语法】



protected void onFocusChanged (

Boolean gainFocus, int direction, Rect previouslyFocusedRect)





其中: 

 参数gainFocus表示触发该事件的View是否获得了焦点,当该控件获得焦点时gainFocus为true,否则为false。

 参数direction表示焦点移动的方向,使用数值表示,有兴趣的读者可以重写View中的该方法并打印该参数进行观察。

 参数previouslyFocusedRect表示在触发事件的View的坐标系中,前一个获得焦点的矩形区域,即表示焦点是从哪里来的,如果不可用则为null。

下述代码通过一个简单例子来演示onFocusChanged()方法的使用。通过移动上下按键来观察屏幕中4个按钮在获得或失去焦点后,按钮上文字的变化情况,代码如下。

【案例317】FocusEventActivity.java



public class FocusEventActivity extends AppCompatActivity {

//定义4个button ①

FocusButton focusButton1;

FocusButton focusButton2;

FocusButton focusButton3;

FocusButton focusButton4;

//声明myButton04的引用

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

//创建4个FocusButton对象 ②

focusButton1 = new FocusButton(this);

focusButton2 = new FocusButton(this);

focusButton3 = new FocusButton(this);

focusButton4 = new FocusButton(this);

//设置focusButton1上的文字 ③

focusButton1.setText("focusButton1");

//设置focusButton2上的文字

focusButton2.setText("focusButton2");

//设置focusButton3上的文字

focusButton3.setText("focusButton3");

//设置focusButton4上的文字

focusButton4.setText("focusButton4");

//创建一个线性布局 ④

LinearLayout linearLayout = new LinearLayout(this); 

//设置其布局方式为垂直

linearLayout.setOrientation(LinearLayout.VERTICAL); 

//将focusButton1添加到布局中 ⑤

linearLayout.addView(focusButton1);

//将focusButton2添加到布局中

linearLayout.addView(focusButton2);

//将focusButton3添加到布局中

linearLayout.addView(focusButton3);

//将focusButton4添加到布局中

linearLayout.addView(focusButton4);

//设置当前的用户界面 ⑥

setContentView(linearLayout);

}

//自定义Button ⑦

class FocusButton extends Button {







//自定义Button

public FocusButton(Context context) {

//构造器

super(context);

}

@Override

protected void onFocusChanged(boolean focused, int direction,

Rect previouslyFocusedRect) {

String suffix = "(选中)";

String text = getText().toString();

//重写的焦点变化方法

if(focused){

//获取焦点时,添加"(选中)"文字

if(!text.contains(suffix)){

this.setText(text+suffix);

}

}else{

//去掉"(选中)"文字

if(text.contains(suffix)){

text = text.substring(0,text.length()-suffix.length());

this.setText(text);

}

}

super.onFocusChanged(focused, direction, previouslyFocusedRect);

}

}

}








图318焦点获取


上述代码解释如下。

 标号①
处声明了4个自定义的按钮变量。

 标号②
处初始化标号①
所声明的4个自定义的按钮控件,然后在标号③
处分别设置了各个按钮上的文字。

 标号④
处创建一个线性布局,并设置其布局方式为垂直。

 标号⑤
处用于将4个按钮控件依次添加到线性布局中,然后在⑥
处将该线性布局设置成当前显示的用户界面。

 标号⑦
处为自定义的FocusButton类,在该类中重写了onFocusChanged()方法,并在该方法内判断是否获取焦点,如果按钮获取焦点则为该按钮添加“(选中)”文字,否则取消“(选中)”文字。

运行上述代码,效果如图318所示。

读者可以通过向上向下键来控制各个按钮的焦点切换,并观察界面的变化情况。



注意每按下一次按键,会调用两次onFocusChanged()方法,第一次是某个按钮失去焦点时调用,第二次是另一个按钮获得焦点时调用。同时方向direction的值会根据情况的不同而有所不同。此外,默认情况下Android 5.0.1模拟器的方向键可能不起作用,需要读者自己重新设置,将C: \\Users\\xxuser\\.android\\avd\\android_720p.avd\\文件夹下的config.ini中的hw.dPad属性值改为yes; 其中,xxuser表示当前系统用户,android_720p表示自定义的模拟器名称。


在介绍onFocusChanged()方法时,提到了焦点的概念。焦点描述了按键事件(或者是屏幕事件等)的接受者,每次按键事件都发生在拥有焦点的View上。在应用程序中,开发人员可以对焦点进行控制,例如,将焦点从一个View移动到另一个View上。下面列出一些与焦点有关的方法,如表314所示,读者可以进一步进行学习。


表314常见的焦点相关方法



方法功 能 描 述


setFocusable()用于设置View是否可以拥有焦点
isFocusable()用于判断View是否可以拥有焦点
setNextFocusDownId()用于设置View的焦点向下移动后获得焦点View的ID
hasFocus()用于判断View的父控件是否获得了焦点
requestFocus()用于尝试让此View获得焦点
isFocusableTouchMode()用于设置View是否可以在触摸模式下获得焦点,默认情况下不可用



3.4Widget简单组件

本节将要介绍的是Android的基本组件。一个易操作、美观的UI界面,都是从界面布局开始,然后不断地向布局容器中添加界面组件。掌握这些最基本的用户界面组件是学好Android编程的基础。Android几乎所有的用户界面组件都定义在android.widget包中,如Button、TextView、EditText、CheckBox、RadioGroup和Spinner等。

3.4.1Widget组件通用属性

对Widget组件进行UI设计时,既可以采用XML布局方式,也可以采用编写代码的方式。其中,XML布局文件方式由于简单易用,被广泛使用。Widget组件几乎都属于View类,因此大部分属性在这些组件之间是通用的,如表315所示。


表315Widget组件通用属性



属 性 名 称功 能 描 述


android:id设置控件的索引,Java程序可通过R.id.<索引>形式来引用该控件
android:layout_height设置布局高度,使用以下三种方式来指定高度: fill_parent(和父元素相同)、wrap_content(随组件本身的内容调整)、通过指定px值来设置高度
android:layout_width设置布局宽度,也可以采用三种方式: fill_parent、wrap_content、指定px值
android:autoLink设置是否当文本为URL链接时,文本显示为可单击的链接。可选值为none、web、email、phone、map和all
android:autoText如果设置,将自动执行输入值的拼写纠正
android:bufferType指定getText()方式取得的文本类别
android:capitalize设置英文字母大写类型。需要弹出输入法才能看得到
android:cursorVisible设定光标为显示/隐藏,默认为显示
android:digits设置允许输入哪些字符,如1234567890.+-*/%\\n()
android:drawableBottom在text的下方输出一个drawable
android:drawableLeft在text的左边输出一个drawable
android:drawablePadding设置text与drawable(图片)的间隔,与drawableLeft、drawableRight、drawableTop、drawableBottom一起使用,可设置为负数,单独使用没有效果
android:drawableRight在text的右边输出一个drawable对象
android:inputType设置文本的类型,用于帮助输入法显示合适的键盘类型
android:cropToPadding是否截取指定区域用空白代替; 单独设置无效果,需要与scrollY一起使用
android:maxHeight设置View的最大高度








3.4.2TextView文本框

TextView文本框直接继承了View类,位于android.widget包中。TextView定义了操作文本框的基本方法,是一个不可编辑的文本框,多用于在屏幕中显示静态字符串。从功能上来看,TextView实际上是一个文本编辑器,只是Android关闭了其文字编辑功能,如果开发者想要定义一个可以编辑内容的文本框,可以使用其子类EditText来实现,EditText允许用户编辑文本框的内容。此外,TextView还是Button的父类,TextView类及其子类的继承关系如图319所示。




图319TextView及其子类


TextView提供了大量的XML属性,这些属性不仅可以适用于TextView,还可以适用于其子类; TextView所支持的XML属性及说明如表316所示。


表316TextView类的XML属性及描述



XML属性功 能 描 述


android:autoLink设置是否当文本为URL链接(例如email、电话号码、map)时,文本显示为可单击的链接。可选值有none、web、email、phone、map和all
android:autoText如果设置,将自动执行输入值的拼写纠正。此处无效果,在显示输入法并输入的时候起作用
android:digits设置允许输入哪些字符。如1234567890.+-*/%\\n()
android:drawableLeft在text的左边输出一个drawable,如图片
android:drawablePadding设置text与drawable(图片)的间隔,与drawableLeft、drawableRight、drawableTop、drawableBottom一起使用,可设置为负数,单独使用没有效果
android:drawableRight在text的右边输出一个drawable,如图片
android:drawableTop在text的正上方输出一个drawable,如图片
android:ellipsize设置当文字过长时如何显示该控件,取值情况如下。

start: 省略号显示在开头。

end: 省略号显示在结尾。

middle: 省略号显示在中间。

marquee: 以跑马灯的方式显示(动画横向移动)
android:gravity设置文本位置,例如,center表示文本将居中显示
android:hint文本为空时显示的提示信息,可通过textColorHint设置提示信息的颜色。此属性在TextView和EditView中使用
android:ems设置TextView的宽度为N个字符的宽度
android:maxEms设置TextView的宽度为最长为N个字符的宽度,与ems同时使用时将覆盖ems选项
android:minEms设置TextView的宽度为最短为N个字符的宽度,与ems同时使用时将覆盖ems选项
android:maxLength限制显示的文本长度,超出部分不显示
android:lines设置文本的行数,设置两行就显示两行,即使第二行没有数据
android:maxLines设置文本的最大显示行数,与width或者layout_width结合使用,超出部分自动换行,超出行数将不显示
android:minLines设置文本的最小行数,与lines类似
android:linksClickable设置链接是否可以单击,即使设置了autoLink
android:lineSpacingExtra设置行间距
android:lineSpacingMultiplier设置行间距的倍数,例如1.2
android:numeric如果被设置,该控件将有一个数字输入法。

此处无用,设置后唯一效果是TextView有单击效果,此属性将在EdtiView中详细说明
android:password以“.”显示文本
android:phoneNumber设置为电话号码的输入方式
android:scrollHorizontally设置文本超出TextView的宽度的情况下,是否出现横向滚动条
android:selectAllOnFocus如果文本是可选的,使其获取焦点,而不是将光标移动到文本的开始位置或者末尾位置。TextView中设置后无效果
android:shadowColor指定文本阴影的颜色,需要与shadowRadius一起使用
android:shadowDx设置阴影横向坐标开始位置
android:shadowDy设置阴影纵向坐标开始位置
android:shadowRadius设置阴影的半径。设置为0.1就变成字体的颜色,一般设置为3.0的效果较好
android:singleLine设置单行显示。如果和layout_width一起使用,当文本不能全部显示时,后面用“...”来表示,例如,android:text="test_singleLine" android:singleLine="true" android:layout_width="20dp"则显示的文本为“t...”,而不是test_singleLine。

如果不设置singleLine或者设置为false,文本将自动换行
android:text设置显示文本
android:textAppearance设置文字外观。如“?android: attr/textAppearanceLargeInverse”

此处引用的是系统自带的一个外观,“?”表示系统是否有这种外观,否则使用默认的外观。取值情况如下: textAppearanceButton、textAppearanceInverse、textAppearanceLarge、textAppearanceLargeInverse、textAppearanceMedium、textAppearanceMediumInverse、textAppearanceSmall、textAppearanceSmallInverse
android:textColor设置文本颜色
android:textColorHighlight被选中文字的底色,默认为蓝色
android:textColorHint设置提示信息文字的颜色,默认为灰色。与hint一起使用
android:textColorLink文字链接的颜色
android:textScaleX设置文字缩放,默认为1.0f。分别设置0.5f/1.0f/1.5f/2.0f
android:textSize设置文字大小,推荐度量单位“sp”,如“15sp”
android:textStyle设置字形[bold(粗体) 0,italic(斜体) 1,bolditalic(又粗又斜) 2]。可以设置一个或多个,用“|”隔开

android:height设置文本区域的高度,支持度量单位: px(像素)、dp、sp、in、mm(毫米)
android:maxHeight设置文本区域的最大高度
android:minHeight设置文本区域的最小高度
android:width设置文本区域的宽度,支持度量单位: px(像素)、dp、sp、in、mm(毫米)




注意表316中介绍了TextView最常用的属性,其他不常用的属性此处没有介绍,读者可在实际使用时再具体查询。


TextView提供了大量的XML属性,通过这些属性开发人员可以控制TextView中文本的行为,下面通过简单示例讲解TextView的基本用法,代码如下。

【案例318】textview_demo.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:orientation="vertical" >

<!-- 设置字号为20sp -->

<TextView android:layout_width="match_parent"

android:layout_height="wrap_content" android:text="TextView演示"

android:textSize="20sp" />

<!-- 设置中间省略,所有字母大写,字号为20sp,内容一行 -->

<TextView android:layout_width="match_parent"

android:layout_height="wrap_content"

android:singleLine="true" android:ellipsize="middle"

android:text="TextView演示TextView演示TextView演示TextView

演示TextView演示TextView演示TextView演示"







android:textAllCaps="true" android:textSize="20sp" />

<!-- 邮件、电话添加链接 -->

<TextView android:layout_width="match_parent"

android:layout_height="wrap_content" android:singleLine="true"

android:text="邮件: zkl@163.com 电话: 053212345678"

android:autoLink="email|phone" android:textSize="20sp" />

<!-- 测试密码框 -->

<TextView android:layout_width="match_parent"

android:layout_height="wrap_content" android:text="TextView演示"

android:password="true" android:textSize="20sp" />

</LinearLayout>





代码解释如下。



图320TextView效果演示

 第一个TextView通过设置android:textSize="20sp"指定了字号为20sp; 其中,sp(scaled pixels,比例像素)主要用于处理字体的大小,可以根据用户的字体大小首选项进行缩放。

 第二个TextView设置了android:ellipsize="middle"属性,当文本内容大于文本框宽度时,从中间省略文本。通过设定android:textAllCaps="true"属性,将该文本框中的所有字母都以大写形式进行显示。

 第三个TextView设置了android:autoLink="email|phone"属性,该文本框会自动为文本框内的email、电话号码添加超链接。

 第四个TextView设置了android:password="true"属性,指定了该文本框会用“.”来显示所有字符。

通过运行TextViewDemoActivity,效果如图320所示。







3.4.3EditText编辑框


EditText是TextView的子类,继承了TextView的XML属性和方法,EditText和TextView的最大区别是: EditText可以接受用户的输入。EditText作为用户与系统之间的文本输入接口,用于接收用户输入的数据并传给系统,从而使系统获取所需要的数据。EditText组件最重要的是inputType属性,该属性用于指定在EditText输入值时所启动的虚拟键盘风格,例如,经常需要虚拟键盘只提供字符或数字。在开发过程中,常用的inputType属性值如表317所示。


表317inputType属性值



属性值功 能 描 述属性值功 能 描 述


text普通文本,默认textEmailAddress电子邮件地址
textCapCharacters字母大写textEmailSubject邮件主题
textCapWords每个单词的首字母大写textShortMessage短信
textLongMessage长信息numberSigned带符号数字格式
textPassword密码numberDecimal带小数点的浮点格式
number数字phone拨号键盘
textAutoCorrect自动完成datetime时间日期
textMultiLine多行输入date日期键盘
textNoSuggestions不提示time时间键盘
textUri网址


下面通过简单示例演示inputType属性的使用,代码如下。

【案例319】edittext_demo.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:orientation="vertical" >

<!-- 多行效果-->

<EditText android:layout_width="match_parent"

android:layout_height="wrap_content" android:inputType="textMultiLine"

android:hint="多行效果" android:textSize="20sp" />

<!-- 拨号键盘-->

<EditText android:layout_marginTop="15dp"...

android:inputType="phone" android:textSize="20sp" />

<!-- 密码类型-->

<EditText android:layout_marginTop="15dp"...

android:inputType="textPassword"

android:hint="输入密码" android:textSize="20sp" />

</LinearLayout>







图321EditText效果演示



代码解释如下。



 上述代码中,三个EditText都通过android:hint属性指定了文本框的提示信息; 当用户输入内容之前,文本框内默认显示指定的提示信息,当用户输入信息时,提示信息被清除。

 第一个EditText通过设置android:inputType="textMultiLine"属性来指定该文本框允许多行输入。

 第二个EditText通过设置android:inputType="phone"属性来指定该文本框只能接受数值的输入。

 第三个EditText通过设置android:inputType="textPassword"属性来指定该文本框是一个密码框。

通过Activity运行上述代码,效果如图321所示。






3.4.4Button按钮


Button继承了TextView,主要用于在UI界面上生成一个按钮,当用户单击按钮时,会触发一个OnClick事件。按钮的使用相对比较容易,通过android:backgroud属性为按钮指定背景颜色或背景图片,使用各式各样的背景图片可以实现各种不规则形状的按钮。

Button类通过继承父类的方法来实现对按钮组件的操作,表318列举了Button类的常用方法。


表318Button类的方法



方法功 能 描 述


onKeyDown()当用户按键时,该方法被调用
onKeyUp()当用户按键弹起后,该方法被调用
onKeyLongPress()当用户保持按键时,该方法被调用
onKeyMultiple()当用户多次按键时,该方法被调用
invalidateDrawable()用于刷新Drawable对象
onPreDraw()用于设置视图显示,例如,在视图显示之前调整滚动轴的边界
setOnKeyListener()用于设置按键监听器
setOnClickListener()用于设置单击监听器


下述代码以setOnClickListener()为例,通过一个模拟登录操作来演示Button、TextView和EditView的使用。界面布局的代码如下。

【案例320】login.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:orientation="vertical" >

<!-- 标题  ①-->

<TextView android:layout_width="wrap_content"

android:layout_height="wrap_content" android:layout_gravity="center"

android:text="用户登录" android:textSize="35sp" />

<!-- 用户名 ②-->

<LinearLayout android:layout_width="match_parent"

android:layout_height="wrap_content" android:layout_marginTop="10dp" >

<TextView android:layout_width="wrap_content"

android:layout_height="wrap_content" android:text="用户名: " />

<EditText android:id="@+id/userNameTxt" .../>

</LinearLayout>

<!-- 密码 ③-->

<LinearLayout android:layout_width="match_parent"...>

<TextView android:text="密   码: " .../>

<EditText android:id="@+id/passwordTxt" 

android:inputType="textPassword".../>

</LinearLayout>

<!-- "登录"按钮 ④-->

<Button android:id="@+id/loginBtn" android:text="登录" .../>

<!-- 成功或失败提示 ⑤-->

<TextView android:id="@+id/tipsTxt"

android:text="显示成功或失败" android:visibility="gone" .../>

</LinearLayout>





代码解释如下。

 上述代码中,标号①
处用于显示的是当前界面的标题,并将TextView的字号设为35sp,相对比较醒目。

 标号②
处通过LinearLayout将TextView和EditText组合起来,用于接收用户名的输入。

 标号③
处通过LinearLayout将TextView和EditText组合起来,用于接收密码的输入,其中,EditText的inputType设置为textPassword,表示该文本框当作密码框使用。

 标号④
定义了一个“登录”按钮,在Activity中用于实现登录的业务逻辑处理。

 标号⑤
定义了一个TextView,用于显示用户登录成功或失败后的提示,例如,用户名不存在、密码错误等。通过设置android:visibility="gone",默认隐藏该提示。

接下来在相应的Activity中实现登录的业务逻辑,此处仅实现一个简单的登录验证,并不做真正的登录跳转,代码如下。

【案例321】LoginActivity.java



public class LoginActivity extends AppCompatActivity {

//用户名 ①

EditText userNameTxt;

//密码

EditText passwordTxt;

//"登录"按钮

Button loginBtn;

//提示

TextView tipsTv;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.login);

//初始化各个组件 ②

userNameTxt =(EditText)findViewById(R.id.userNameTxt);

passwordTxt = (EditText) findViewById(R.id.passwordTxt);

tipsTv = (TextView) findViewById(R.id.tipsTxt);

loginBtn = (Button)findViewById(R.id.loginBtn);

//实现单击Button的业务逻辑 ③

loginBtn.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

//获取用户名

String userName = userNameTxt.getText().toString();

//获取密码

String password = passwordTxt.getText().toString();

//判断

//判断用户名

if(!"admin".equals(userName)) {

tipsTv.setText("用户名不存在!");

tipsTv.setVisibility(View.VISIBLE);

return;

}

if(!"1".equals(password)) {

tipsTv.setText("密码不正确!");

tipsTv.setVisibility(View.VISIBLE);

return;

}

if("admin".equals(userName)&&"1".equals(password)) {

tipsTv.setText("登录成功!");







tipsTv.setVisibility(View.VISIBLE);

}

}

});

}

}






代码解释如下。

 上述代码中,标号①
处定义了一个EditText类型的属性变量userNameTxt,用于获取界面传递过来的对象,其他属性的定义功能类似,此处不再赘述。

 标号②
处对标号①
处所定义的各个属性变量进行初始化,通过对属性变量赋值,使其能够进行后续的业务逻辑操作。

 标号③
处实现了“登录”按钮loginBtn的业务逻辑。逻辑相对比较简单: 如果用户名无效,则显示“用户名不存在”; 如果密码不对,则显示“密码不正确!”; 如果用户输入的用户名和密码都正确,则显示“登录成功!”。



注意本案例仅演示Button、TextView和EditText的使用,未涉及更复杂的应用逻辑。在实际开发中,用户登录时需要查询后台数据库来验证用户名和密码是否正确。


运行上述代码,当用户输入错误时进行相应的错误提示,例如,用户名输入“admi”,密码任意输入“1”,显示的界面如图322所示。当用户输入正确的用户名和密码时,显示的界面的如图323所示。

读者可以进一步完善该案例,例如,可以增加用户名和密码的空校验等功能。




图322用户输入错误时的情况




图323用户输入正确时的情况




3.4.5单选按钮和单选按钮组

在一组按钮中有且仅有一个按钮能够被选中,当选择按钮组中某个按钮时会取消其他按钮的选中状态。上述效果需要RadioButton和RadioGroup配合使用才能实现。RadioGroup是单选按钮组,是一个允许容纳多个RadioButton的容器。在没有RadioGroup的情况下,RadioButton可





以分别被选中; 当多个RadioButton同在一个RadioGroup按钮组中,RadioButton只允许选择其中之一。RadioButton和RadioGroup的关系如下。

 RadioButton表示单个圆形单选框,而RadioGroup是一个可以容纳多个RadioButton的容器。

 同一个RadioGroup中,只能有一个RadioButton被选中。

 不同的RadioGroup中,RadioButton互不影响,即如果组A中有一个被选中,组B中依然可以有一个被选中。

 通常情况下,一个RadioGroup中至少有两个RadioButton。

 大部分应用场景下,建议一个RadioGroup中的RadioButton默认会有一个被选中,并将其放在RadioGroup中的起始位置。

RadioGroup类是LinearLayout的子类,其常用的设置和控制单选按钮组的方法如表319所示。


表319RadioButton相关方法



方法功 能 描 述


getCheckedRadioButtonId()获取被选中按钮的ID
clearCheck()清除选中状态
check (int id)通过参数ID来设置该选项为选中状态; 如果传入-1则清除单选按钮组的选中状态,相当于调用clearCheck()操作
setOnCheckedChangeListener(RadioGroup.OnCheckedChangeListener listener)在一个单选按钮组中,当该单选按钮选中状态发生改变时所要调用的回调函数。当RadioButton的checked属性为true时,check(id)方法不会触发onCheckedChanged事件
addView(View child,int index, 

ViewGroup.LayoutParams params)使用指定的布局参数添加一个子视图。

 child: 所要添加的子视图。

 index: 将要添加子视图的位置。

 params: 所要添加的子视图的布局参数
getText()用于获取单选框的值


此外,通过OnCheckedChangeListener监听器对单选按钮的状态切换进行监听并处理。

下面通过一个简单的实例演示RadioButton和RadioGroup的使用,界面布局的代码如下。

【案例322】radiobutton_demo.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:layout_marginRight="5dp"

android:orientation="vertical" >

<!-- 显示选择的内容 ① -->

<TextView android:id="@+id/chooseTxt" ...







android:text="我选择的是...?" android:textSize="30sp" />

<!-- 单选按钮组 ②-->

<RadioGroup android:id="@+id/radioGroup" ...>

<RadioButton android:id="@+id/radioButton1" android:text="按钮1" .../>

<RadioButton android:id="@+id/radioButton2" android:text="按钮2" .../>

</RadioGroup>

<!-- 清除所有选中状态 ③-->

<Button android:id="@+id/radio_clearBtn" android:text="清除选中" .../>

<!-- 往按钮组中添加新的单选按钮 ④-->

<Button android:id="@+id/radio_addBtn" android:text="添加子项" .../>

</LinearLayout>





代码解释如下。

 上述代码中,标号①
处用于显示当前选中按钮的标题。

 标号②
处定义了一个单选按钮组,并为该按钮组添加了两个单选按钮。

 标号③
处定义了一个“清除选中”按钮,用于清除按钮组中所有单选按钮的选中状态。

 标号④
处定义了一个“添加子项”按钮,用于向按钮组中添加新的互斥的单选按钮。

接下来在对应的Activity中演示按钮组的使用,实现清除按钮组中所有按钮的选中状态,以及向按钮组中添加新的单选按钮的功能,代码如下。

【案例323】RadioButtonActivity.java



public class RadioButtonActivity extends AppCompatActivity {

//显示选择的单选按钮文本 ①

private  TextView  chooseTxt;

//按钮组

private  RadioGroup  radioGroup;

//多个单选按钮

private  RadioButton  radioButton1;

private  RadioButton  radioButton2;

//清除按钮

private  Button  radioClearBtn;

//新增按钮

private Button  radioAddBtn;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.radiobutton_demo);

//初始化按钮 ②

chooseTxt = (TextView)findViewById(R.id.chooseTxt); 

radioGroup=(RadioGroup)findViewById(R.id.radioGroup);

radioButton1=(RadioButton)findViewById(R.id.radioButton1);

radioButton2=(RadioButton)findViewById(R.id.radioButton2);

radioGroup.setOnCheckedChangeListener(onCheckedChangeListener);

//清除选中状态

radioClearBtn=(Button)findViewById(R.id.radio_clearBtn);

radioClearBtn.setOnClickListener(onClickListener);

//增加子选项

radioAddBtn=(Button)findViewById(R.id.radio_addBtn);

radioAddBtn.setOnClickListener(onClickListener);







}

/**

* 定义按钮组的监听事件 ③

*/

private OnCheckedChangeListener  onCheckedChangeListener

=new OnCheckedChangeListener() {

@Override

public void onCheckedChanged(RadioGroup group, int checkedId) {

int id= group.getCheckedRadioButtonId();

switch (group.getCheckedRadioButtonId()) {//获取当前选中的按钮的ID

case R.id.radioButton1:

chooseTxt.setText("我选择的是:"+radioButton1.getText());

break;

case R.id.radioButton2:

chooseTxt.setText("我选择的是:"+radioButton2.getText());

break;

default:

chooseTxt.setText("我选择的是:新增");

break;

}

}

};

//定义清除状态按钮和新增按钮的单击事件 ④

private OnClickListener onClickListener=new OnClickListener() {

@Override

public void onClick(View view) {

switch (view.getId()) {

case R.id.radio_clearBtn:

radioGroup.check(-1); //清除选项

chooseTxt.setText("我选择的是...?");

break;

case R.id.radio_addBtn:

//新增子选项

RadioButton  newRadio =new RadioButton(RadioButtonActivity.this);

newRadio.setLayoutParams(new LayoutParams(

LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

newRadio.setText("新增");

radioGroup.addView(newRadio,radioGroup.getChildCount());

break;

default:

break;

}

}

};

}





代码解释如下。

 上述代码中,标号①
处定义了一个TextView类型的属性变量chooseTxt,用于获取当前被选中按钮的文本,其他属性的定义请见注释,此处不再赘述。

 标号②
处对标号①
处所定义的各个属性变量进行初始化,通过对属性变量的赋值,使其可以进行后续的业务逻辑操作。

 标号③
处定义了一个按钮组监听器对象,用于获取当前在按钮组中选中的单选按钮对象,并将文本显示在chooseTxt对象上。

 标号④
处定义一个普通按钮监听器对象,用于实现radioClearBtn和radioAddBtn的业务逻辑功能,当用户单击radioClearBtn按钮时,按钮组中被选中的单选按钮状态被清空; 当用户单击radioAddBtn时,系统会在radioGroup对象中增加一个单选按钮对象。

运行上述代码,选中一个单选按钮的效果如图324所示。当用户单击“添加子项”按钮后,并选中新增的选项,效果如图325所示。




图324选中单选按钮




图325添加子项




可以通过android:drawableRight属性使单选按钮在文本的右边显示,对布局文件进行修改,代码如下。

【案例324】radiobutton_demo.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:layout_marginRight="5dp" android:orientation="vertical" >

<!-- 显示选择的内容  -->

<TextView android:id="@+id/chooseTxt" ...

android:text="我选择的是...?" android:textSize="30sp" />

<!-- 单选按钮组 -->

<RadioGroup android:id="@+id/radioGroup" ...>

<RadioButton android:id="@+id/radioButton1" ...

android:button="@null"

android:drawableRight="@android:drawable/btn_radio"

android:text="按钮1" />

...





运行修改后的代码,效果如图326所示。



图326改变RadioButton样式


3.4.6CheckBox复选框







CheckBox复选框是一种具有双状态的按钮,具有选中和未选中两种状态。在布局文件中定义复选框时,对每一个复选框注册OnCheckedChangeListener事件监听,然后在onCheckedChanged()事件处理方法中根据isChecked参数来判断选项是否被选中。

CheckBox和RadioButton的主要区别如下。

 RadioButton单选按钮被选中后,再次单击时无法改变其状态,而CheckBox复选框被选中后,可以通过单击来改变其状态。

 在RadioButton单选按钮组中,只允许选中一个; 而在CheckBox复选框组中,允许同时选中多个。

 在大部分UI框架中,RadioButton默认都以圆形表示,CheckBox默认都以矩形表示。

下述代码通过一个简单的示例演示CheckBox的用法,以“体育爱好”的多选为例,人们的“体育爱好”可能有足球、篮球等,而人的性别选择有所不同,性别只能选择“男”或“女”,且两者互斥。示例的界面布局代码如下。

【案例325】checkbox_demo.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:orientation="vertical" >

<!-- 基本显示 ①-->

<TextView android:text="@string/title" android:textSize="20sp" ...

android:textStyle="bold" android:textColor="#FFFFFF" /> 

<!-- 足球 ②-->

<CheckBox android:id="@+id/checkbox1" ...







android:text="@string/football" android:textSize="16sp" /> 

<!-- 篮球 ③-->

<CheckBox android:id="@+id/checkbox2" ...

android:text="@string/basketball" android:textSize="16sp" /> 

<!-- 排球 ④-->

<CheckBox android:id="@+id/checkbox3" ...

android:text="@string/volleyball" android:textSize="16sp" /> 

</LinearLayout>





代码解释如下。

 上述代码中,标号①
处的TextView用于显示用户的标题。

 标号②
处定义的是“足球”复选框。

 标号③
处定义的是“篮球”复选框。

 标号④
处定义的是“排球”复选框。

上述代码中,复选框的文本部分使用了字符串资源,例如,“足球”的文本是引用的strings.xml文件中的字符串,其中,strings.xml中的字符串定义如下。

【案例326】strings.xml



<?xml version="1.0" encoding="utf-8"?> 

<resources> 

<string name="title">你喜欢的运动是: </string> 

<string name="app_name">复选框测试</string> 

<string name="football">足球</string> 

<string name="basketball">篮球</string> 

<string name="volleyball">排球</string> 

</resources>





通常在开发过程中使用strings.xml文件的目的如下。

 国际化: Android建议将屏幕中显示的文字定义在strings.xml中,如果今后需要进行国际化时仅需要修改string.xml文件即可。例如,原本开发的应用是面向国内用户的,在屏幕上使用中文,当需要将应用国际化时,用户希望屏幕上所显示的内容是英文,此时如果没有把文字信息定义在string.xml中,就需要修改程序的内容来实现。但如果把所有屏幕上出现的文字信息都集中存放在string.xml文件中,在需要国际化时只需修改string.xml中所定义的字符串资源即可,Android操作系统会根据用户手机的语言环境和国家来自动选择相应的string.xml文件,实现起来更加方便。

 为了减少应用的体积,降低数据的冗余,例如,在应用中要使用“我们一直在努力”这段文字1000次,如果不将“我们一直在努力”定义在string.xml文件中,而是在每次使用时直接使用该字符串,这样会浪费大量的空间,并且维护起来较为麻烦。



注意关于各种类型的资源文件的讲解会在后面的章节中介绍,此处不再赘述。


下面在相应的Activity中演示复选框的使用,当用户选择不同的“爱好”时,在屏幕上显示用户的选择结果,代码如下。

【案例327】CheckBoxDemoActivity.java



public class CheckBoxDemoActivity extends AppCompatActivity {

//声明复选框 ①

private CheckBox footballChx;

private CheckBox basketballChx;

private CheckBox volleyballChx;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.checkbox_demo);

//通过findViewById获得CheckBox对象 ②

footballChx = (CheckBox) findViewById(R.id.footballChx);

basketballChx = (CheckBox) findViewById(R.id.basketballChx);

volleyballChx = (CheckBox) findViewById(R.id.volleyballChx);

//注册事件监听器 ③

footballChx.setOnCheckedChangeListener(listener);

basketballChx.setOnCheckedChangeListener(listener);

volleyballChx.setOnCheckedChangeListener(listener);

}



//响应事件 ④

private OnCheckedChangeListener listener = new OnCheckedChangeListener() {

@Override

public void onCheckedChanged(CompoundButton buttonView,

boolean isChecked) {



switch (buttonView.getId()) {

case R.id.footballChx:

//选择足球

if (isChecked) {

//Toast的使用 ⑤

Toast.makeText(CheckBoxDemoActivity.this, "你喜欢足球",

Toast.LENGTH_LONG).show();

}

break;

case R.id.basketballChx:

//选择篮球

if (isChecked) {

Toast.makeText(CheckBoxDemoActivity.this, "你喜欢篮球",

Toast.LENGTH_LONG).show();

}

case R.id.volleyballChx:

//选择排球

if (isChecked) {

Toast.makeText(CheckBoxDemoActivity.this, "你喜欢排球",

Toast.LENGTH_LONG).show();

}

default:

break;

}

}

};

}







图327爱好的选择

代码解释如下。

 上述代码中,标号①
处定义了三个CheckBox复选框,供用户进行选择。

 标号②
处对标号①
处所定义的各个属性变量初始化,通过对属性变量赋值,使其可以进行后续的业务逻辑操作。

 标号③
处分别为三个CheckBox对象设置监听器,用于监听各自的选中或取消事件。

 标号④
处定义了一个监听器对象,用于监听并实现三个CheckBox的业务逻辑功能,当用户单击不同的CheckBox时,屏幕上会通过Toast对象显示相应的文本信息。

运行上述Activity,并选择“篮球”复选框时,界面效果如图327所示。



注意Toast是Android中用来显示提示信息的一种机制,与Dialog不同的是: Toast提示没有焦点且时间有限,在一定的时间后会自动消失。Toast使用简单,主要用于向用户显示提示消息,在后续章节会有详细的介绍。







3.4.7开关控件

ToggleButton、Switch、CheckBox和RadioButton组件均继承自android.widget.CompoundButton,都是选择类型的按钮,因此这些组件的用法非常相似。CompoundButton按钮共有两种状态: 选中(checked)和未选中(unchecked)状态。而Switch控件是Android 4.0后出现的控件。

ToggleButton所支持的XML属性和方法如表320所示。


表320ToggleButton的XML属性和相关方法



XML属性对 应 方 法功 能 描 述


android:checkedsetChecked(boolean)设置该按钮是否被选中
android:textOffsetTextOff(CharSequence)设置按钮的状态关闭时所显示的文本
android:textOnsetTextOn(CharSequence)设置按钮的状态打开时所显示的文本


下述代码通过一个简单的示例演示ToggleButton的用法; 当ToggleButton处于选中的状态时,文本显示“已开启”,当ToggleButton处于未选中状态时,文本显示“已关闭”。首先实现界面布局,代码如下。

【案例328】togglebutton_demo.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:orientation="horizontal" >

<!-- 文本展示 ①-->

<TextView android:id="@+id/tvSound" ...







android:text="已开启" android:textColor="@android:color/black"

android:textSize="14.0sp" />

<!-- 定义ToggleButton对象 ②-->

<ToggleButton android:id="@+id/tglSound"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginLeft="10dp"

android:checked="true"

android:text=""

android:textOff="OFF"

android:textOn="ON" />

</LinearLayout>





代码解释如下。

 上述代码中,标号①
处的TextView用于显示ToggleButton按钮的On/Off时的标题。

 标号②
处定义了一个ToggleButton,用于测试按钮的开启或关闭。

然后,在Activity中展示ToggleButton的使用: 当用户选择了On/Off时,在屏幕上显示用户的选择。对应的Activity代码如下。

【案例329】ToggleButtonDemoActivity.java



public class ToggleButtonDemoActivity extends AppCompatActivity {

//声明XML中定义的组件 ①

private ToggleButton mToggleButton;

private TextView tvSound;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.toggleswitch_demo);

initView();

}

//初始化控件方法 ②

private void initView() {

//获取控件

mToggleButton = (ToggleButton) findViewById(R.id.tglSound); 

tvSound = (TextView) findViewById(R.id.tvSound);

//注册监听器,添加监听事件 ③

mToggleButton.setOnCheckedChangeListener(new OnCheckedChangeListener() {

@Override

public void onCheckedChanged(CompoundButton buttonView,

boolean isChecked) {

if (isChecked) {

tvSound.setText("已开启");

} else {

tvSound.setText("已关闭");

}

}

});

}

}







图328选中状态

代码解释如下。

 上述代码中,标号①
处分别声明了ToggleButton类型和TextView类型的属性变量。

 标号②
处对标号①
处所定义的各个属性变量进行初始化,通过对属性变量的赋值,使其可以进行后续的业务逻辑操作。

 标号③
处对ToggleButton对象注册监听器,用来监听按钮的开启或关闭事件。

运行上述代码后,当用户单击ON按钮后,显示效果如图328所示。

如图328所示,当用户切换按钮变为ON状态时,显示的文本变为“已开启”。从图中可以看出,默认的ToggleButton比较难看,在实际开发中可以通过设置按钮的背景图片来实现较为美观的开/关。本示例中提供了用于切换ON/OFF的较为美观的图片,通过设置按钮的背景图片来实现较为美观的效果,修改后的代码如下。

【案例330】togglebutton_demo.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:orientation="horizontal" >

<!-- 文本展示 ①-->

<TextView android:id="@+id/tvSound"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="已开启"

android:textColor="@android:color/black"

android:textSize="14.0sp" />

<!-- 定义ToggleButton对象  ②-->

<ToggleButton android:id="@+id/tglSound"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginLeft="10dp"

android:background="@drawable/selector_btn_toggle"

android:checked="true"

android:text=""

android:textOff=""

android:textOn="" />

</LinearLayout>





代码解释如下。

 上述代码中,标号①
处的TextView用于显示ON/OFF后的标题。

 标号②
处定义了一个ToggleButton按钮,用于显示开启或关闭; 此时,需要将属性android:textOff和android: textOn设置为空,否则将会覆盖在图片上。然后将android:backgroud的属性值设置为selector_btn_toggle,该值对应的并不是一幅图片,而是一个资源文件selector_btn_toggle.xml。



图329ToggleButton背景优化

在资源目录res\\drawable下,创建selector_btn_toggle.xml资源文件,代码如下。

【案例331】selector_btn_toggle.xml



<?xml version="1.0" encoding="utf-8"?>

<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:state_checked="true" android:drawable="@drawable/btn_open" />

<item android:drawable="@drawable/btn_close" />

</selector>





在上述资源文件中,指定了ToggleButton在默认状态下的背景图片和选中状态下的图片背景。运行更改后的代码,展示效果如图329所示。

通过效果可以看出,美化后的ToggleButton更注重用户的体验。

Switch的使用方式与ToggleButton类似,Switch所支持的XML属性和方法如表321所示。


表321Switch的XML属性及对应方法



XML属性对 应 方 法功 能 描 述


android:checkedsetChecked(boolean)设置当前按钮的状态,选中或未选中
android:textOffsetTextOff(CharSequence)设置按钮关闭状态所显示的文本
android:textOnsetTextOn(CharSequence)设置按钮打开状态所显示的文本
android:switchMinWidthsetSwitchMinWidth(int)设置开关的最小宽度
android:textStylesetSwitchTypeface (Typeface , int)设置开关的文本风格
android:typefacesetSwitchTypeface(Typeface)设置开关的文本的字体风格
android:switchPaddingsetSwitchPadding(int)设置开关与标题文本之间的空白
android:thumbsetThumbResource(int)使用自定义的Drawable来绘制开关的开关按钮
android:tracksetTrackResource(int)使用自定义的Drawable来绘制开关的开关轨道


下述代码通过一个示例演示Switch的使用。通过改变Switch状态来实现界面布局中的LinearLayout布局的方向在水平和垂直布局之间切换,布局文件代码如下。

【案例332】switch_demo.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:orientation="vertical" >

<!-- 定义一个Switch组件 ① -->

<Switch	android:id="@+id/switcher" android:checked="true"

android:textOff="横向排列"	android:textOn="纵向排列"

android:showText="ture"	android:thumb="@drawable/check" />

<!-- 定义一个可以动态改变方向的线性布局 ②-->

<LinearLayout android:id="@+id/test" android:orientation="vertical" ...>







<TextView android:text="测试文本1" .../>

<TextView android:text="测试文本2" .../>

<TextView android:text="测试文本3" .../>

</LinearLayout>

</LinearLayout>





代码解释如下: 

 上述代码中,标号①
处定义了一个Switch组件,并将android:thumb的属性设置为“@drawable/check”。

 标号②
处定义了一个可以动态改变方向的线性布局,其中包含3个文本框用于显示效果。

下面在Activity中演示Switch的使用: 当用户选择了“横向排列”/“纵向排列”时,界面布局发生相应的变化。Activity代码的实现如下。

【案例333】SwitchDemoActivity.java



public class SwitchDemoActivity extends AppCompatActivity {

//定义变量 ①

Switch switcher;



@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.switch_demo);

//初始化组件②

switcher = (Switch) findViewById(R.id.switcher);

final LinearLayout test = (LinearLayout) findViewById(R.id.test);

OnCheckedChangeListener listener = new OnCheckedChangeListener() {

@Override

public void onCheckedChanged(CompoundButton button,

boolean isChecked) {

if (isChecked) {

//设置LinearLayout垂直布局

test.setOrientation(LinearLayout.VERTICAL);

switcher.setChecked(true);

} else {

//设置LinearLayout水平布局

test.setOrientation(LinearLayout.HORIZONTAL);

switcher.setChecked(false);

}

}

};

//为switch组件添加事件监听器 ③

switcher.setOnCheckedChangeListener(listener);

}

}





代码解释如下。

 上述代码中,标号①
分别声明了一个Switch类型的属性变量。

 标号②
处用于初始化标号①
处所声明的属性变量,通过对属性变量的赋值,使其可以进行后续的业务逻辑操作。

 标号③
处对Switch对象设置监听器,用于监听按钮的开启或关闭事件。当事件发生时,判断按钮的开启/关闭状态,并切换界面的布局。

运行上述代码后,界面效果如图330所示。当用户再次单击“纵向排列”按钮时,系统会自动切换到“横向排列”状态,效果如图331所示。




图330Switch实现纵向布局




图331Switch实现横向布局









3.4.8图片视图(ImageView)

ImageView继承自View组件,主要用于显示图像资源(例如图片等)。ImageView可以定义所显示的尺寸等。此外,ImageView还派生了ImageButton、ZoomButton等组件。ImageView所支持的XML属性和方法如表322所示。


表322ImageView的XML属性及方法



XML属性对 应 方 法功 能 描 述


android:adjustViewBoundssetAdjustViewBounds(boolean)是否保持宽高比。需要与maxWidth、MaxHeight一起使用,单独使用没有效果
android:cropToPaddingsetCropToPadding(boolean)截取指定区域是否使用空白代替。单独设置无效果,需要与scrollY一起使用
android:maxHeightsetMaxHeight(int)设置View的最大高度,单独使用无效,需要与setAdjustViewBounds()方法一起使用。如果想设置图片固定大小,又想保持图片宽高比,需要如下设置。

(1) 设置setAdjustViewBounds为true。

(2) 设置maxWidth和MaxHeight属性。

(3) 设置layout_width和layout_height均为wrap_content
android:maxWidthsetMaxWidth(int)设置View的最大宽度。具体描述与SetMaxHeight()方法类似
android:srcsetImageResource(int)设置ImageView所显示的Drawable对象
android:scaleTypesetScaleType(ImageView.ScaleType)设置所显示的图片如何缩放或移动以适应ImageView的大小


ImageView的android:scaleType属性可以指定如下属性值。

 matrix: 用矩阵来绘图。

 fitXY: 拉伸图片(不按比例)以填充View的宽高。

 fitStart: 按比例拉伸图片,图片拉伸后的高度为View的高度,且显示在View的左边。

 fitCenter: 按比例拉伸图片,图片拉伸后的高度为View的高度,且显示在View的中间。

 fitEnd: 按比例拉伸图片,图片拉伸后的高度为View的高度,且显示在View的右边。

 center: 按原图大小显示图片,当图片宽高大于View的宽高时,截图图片中间部分显示。

 centerCrop: 按比例放大原图,直至等于View某边的宽高。

 centerInside: 当原图宽高等于View的宽高时,按原图大小居中显示; 反之将原图缩放至View的宽高居中显示。

此外,为了控制ImageView所显示的图片,该组件提供了如下方法。

 setImageBitmap(Bitmap): 使用Bitmap位图来设置ImageView所显示的图片。

 setImageDrawable(Drawable): 使用Drawable对象来设置ImageView所显示的图片。

 setImageResource(int): 使用图片资源ID来设置ImageView所显示的图片。

 setImagURI(Uri): 使用图片的URI来设置ImageView所显示的图片。

下面通过一个简单示例演示ImageView的使用。通过单击“下一页”/“上一页”按钮,实现国旗的切换,对应的布局文件代码如下。

【案例334】imageview_demo.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:orientation="vertical" >

<!-- 国旗及文字 ①-->

<LinearLayout android:layout_gravity="center"

android:orientation="vertical" ...>

<ImageView android:id="@+id/guoqiImageView"

android:layout_width="200dp" android:layout_height="wrap_content"

android:layout_gravity="center" android:layout_marginTop="30dp"

android:background="@android:color/white"

android:scaleType="fitCenter" android:src="@drawable/china" />

<TextView android:id="@+id/guoqiTxt" ...

android:layout_gravity="center" android:text="中国" />

</LinearLayout>

<!-- 分页 ②-->

<LinearLayout android:gravity="center"

android:orientation="horizontal" ...>

<ImageButton android:id="@+id/backImageBtn"

android:layout_width="50dp" android:layout_height="50dp"







android:layout_gravity="center" android:src="@drawable/back" />

<ImageButton android:id="@+id/forwardImageBtn"

android:layout_marginLeft="20dp" android:layout_width="50dp"

android:layout_height="50dp" android:layout_gravity="center"

android:src="@drawable/forward" />

</LinearLayout>

</LinearLayout>





代码解释如下。

 上述代码中,标号①
处定义了一个ImageView组件,用来显示国旗的图片; 通过设置android:scaleType="fitCenter"属性,使用拉伸后图片的高度作为View的高度,且在View的中间显示。同时还定义了一个TextView组件用来显示国别。

 标号②
处定义了两个ImageButton组件,分别用于显示“上一页”和“下一页”的国旗和国别。

下面在Activity中演示了图片分页效果: 当用户分别单击“上一页”/“下一页”按钮时,在屏幕上会显示相应的国旗和国别。对应的Activity代码实现如下。

【案例335】ImageViewDemoActivity.java



public class ImageViewDemoActivity extends AppCompatActivity {

//定义变量 ①

//国旗对应的ImageView

ImageView flagImageView;

TextView flagTxt;

//上一页

ImageButton backImageBtn;

//下一页

ImageButton forwardImageBtn;

//国旗数组 中国 德国 英国 ②

int[]flag= {R.drawable.china,R.drawable.germany,R.drawable.britain};

String[]flagNames = {"中国","德国","英国"};

//当前页为默认第一页

int currentPage = 0;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.imageview_demo);

//初始化组件 ③

flagImageView = (ImageView) findViewById(R.id. flagImageView);

//国旗名称

guoqiTxt = (TextView)findViewById(R.id. flagTxt);

//上一页、下一页

backImageBtn = (ImageButton)findViewById(R.id.backImageBtn);

forwardImageBtn = (ImageButton)findViewById(R.id.forwardImageBtn);

//注册监听器

backImageBtn.setOnClickListener(onClickListener);

forwardImageBtn.setOnClickListener(onClickListener);

}

//定义单击事件监听器 ④

private OnClickListener onClickListener = new OnClickListener() {

@Override







public void onClick(View v) {

switch (v.getId()) {

case R.id.backImageBtn:

if(currentPage == 0){

Toast.makeText(ImageViewDemoActivity.this, 

"第一页,前面没有了", Toast.LENGTH_SHORT).show();

return;

}

//上翻一页

currentPage-- ;

//设置国旗图片

flagImageView.setImageResource(flag [currentPage]);

//设置国旗名字

flagTxt.setText(flagNames[currentPage]);

break;

case R.id.forwardImageBtn:

if(currentPage == (flag.length-1)){

Toast.makeText(ImageViewDemoActivity.this,

"最后一页,后面没有了", Toast.LENGTH_SHORT).show();

return;

}

//下翻一页

currentPage++;

//设置国旗图片

flagImageView.setImageResource(flag [currentPage]);

//设置国旗名字

flagTxt.setText(flagNames[currentPage]);

break;

default:

break;

}

}

};

}





代码解释如下。

 上述代码中,标号①
分别声明了ImageView类型和ImageButton类型的属性变量。

 标号②
处定义了两个数组并进行初始化,分别表示国旗图片资源和国旗名称。

 标号③
处用于初始化标号①
处所声明的属性变量; 并对backImageBtn和forwardImageBtn对象设置监听器,来监听各自的单击事件。

 标号④
定义了一个OnClickListener类型的监听器,用于处理按钮单击事件; 当单击按钮时根据所单击的按钮不同来显示不同的国旗和国别。

运行上述代码后,默认的界面效果如图332所示。当用户单击“>”按钮后,界面显示效果如图333所示。


注意在实际开发中,如果要实现页面的切换功能,通常使用ViewPager类; 该类是Android Support Liberary中自带的一个附加包中的一个类,用来实现屏幕间的切换。





图332切换国旗一




图333切换国旗二




3.5Dialog对话框

Dialog对话框对于应用程序而言是一个必不可少的组件,在Android中,对话框对于一些重要的提示信息或者需要用户额外的交互是很有帮助的。所有的对话框都直接或间接继承自Dialog类,其中,AlertDialog直接继承Dialog类,而其他的几个类则继承自AlertDialog类。

对话框就是一个小窗口,并不会填满整个屏幕,通常是以模态显示,需要用户采取行动才能进行后续操作。Android提供了丰富的对话框支持,其中常用的对话框有以下4种。

 AlertDialog提示对话框: 是一种使用广泛且功能丰富的对话框。AlertDialog不仅可以包含0~3个响应按钮,还可以包含一个单选框、复选框或列表。警告对话框通常用于创建交互式界面,是最常用的对话框类型。

 ProgressDialog进度条对话框: 只是对进度条进行了简单的封装,用于显示一个进度环或进度条,由于ProgressDialog是AlertDialog的扩展,所以也支持按钮选项。

 DatePickerDialog日期对话框: 用于用户选择日期的对话框。

 TimePickerDialog时间对话框: 用于用户选择时间的对话框。






3.5.1AlertDialog提示对话框

AlertDialog继承自Dialog类,可以包含一个标题、一个内容消息或者一个选择列表以及0~3个按钮。在创建AlertDialog时推荐使用AlertDialog的Builder内部类来创建。首先使用Builder对象来设置AlertDialog的各种属性,然后通过Builder.create()方法生成一个AlertDialog对象; 如果只是显示一个AlertDialog对话框,可以直接使用Builder.show()方法返回一个AlertDialog对象并显示。

当仅提示一段信息时,可以直接使用AlertDialog的属性设置提示信息,相关方法如表323所示。


表323AlertDialog的相关方法



方法功 能 描 述


void create()根据设置的属性,创建一个AlertDialog
void show()根据设置的属性,显示已创建的AlertDialog
AlertDialog.Builder setTitle()设置标题
AlertDialog.Builder setIcon()设置标题的图标
AlertDialog.Builder setMessage()设置标题的内容
AlertDialog.Builder setCancelable()设置是否模态; 一般设置为false,表示采用模态形式,要求用户必须采取行动才能继续进行剩下的操作
AlertDialog setPositiveButton()为对话框添加Yes按钮
AlertDialog setNegativeButton为对话框添加No按钮




注意AlertDialog.Builder的大部分设置属性的方法返回是此AlertDialog.Builder对象,所以可以使用链式方式编写代码,这样更方便。



当一个对话框调用show()方法将其展示到屏幕上时,如果需要消除该对话框,可以使用DialogInterface接口所提供的cancel()方法来取消或者dismiss()方法来消除对话框。cancel()和dismiss()方法的作用是一样的,但推荐使用dismiss()方法。Dialog和AlertDialog都实现了DialogInterface接口,因此所有的对话框都可以使用这两个方法来消除对话框。对话框使用的场景较多,主要分为如下几种形式。

1. 普通对话框

下面通过一个简单的示例,演示使用AlertDialog如何提示信息,布局文件代码如下。

【案例336】dialog_demo.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:orientation="vertical" >

<!-- 普通对话框  ①-->

<Button android:id="@+id/normalBtn" android:text="普通对话框" .../>

</LinearLayout>





上述代码中,标号①
处定义了一个Button组件,当用户单击时,弹出一个普通对话框。

接下来在对应的Activity中实现按钮事件的业务逻辑: 当用户单击“普通对话框”按钮时,在屏幕上显示对话框; 当用户单击对话框中的“确认”按钮时,将退出应用程序; 当用户单击“取消”按钮时,程序返回到主界面。代码实现如下。

【案例337】DialogDemoActivity.java



public class DialogDemoActivity extends AppCompatActivity {

//普通对话框 ①

Button normalBtn;

@Override

protected void onCreate(Bundle savedInstanceState) {







super.onCreate(savedInstanceState);

setContentView(R.layout.dialog_demo);

//初始化组件 ②

normalBtn = (Button) findViewById(R.id.normalBtn);

//设置监听器对象

normalBtn.setOnClickListener(onClickListener);

}

//定义单击事件监听器 ③

private OnClickListener onClickListener = new OnClickListener() {

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.normalBtn: {

//普通对话框 ④

AlertDialog.Builder builder = new AlertDialog.Builder(

DialogDemoActivity.this);

builder.setMessage("确认退出吗?")

.setTitle("提示");

//单击“确认”按钮后触发事件

builder.setPositiveButton("确认",

new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog,

int which) {

dialog.dismiss();

DialogDemoActivity.this.finish();

}

});

//单击“取消”按钮后触发事件

builder.setNegativeButton("取消",

new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog,

int which) {

dialog.dismiss();

}

});

builder.create().show();

break;

}

default:

break;

}

}

};

}





代码解释如下。

 上述代码中,标号①
声明了Button类型的属性变量,当用户单击该按钮时,弹出普通对话框。



图334普通对话框

 标号②处对标号①
处所声明的属性变量进行初始化,通过对属性变量的赋值,使其可以进行后续的业务逻辑操作。

 标号③
处创建了一个监听器,用来监听用户单击按钮时所触发的事件。

 标号④
处实现了OnClickListener事件处理的业务逻辑; 当事件触发时,弹出一个带有“确认”和“取消”的对话框; 单击“确认”按钮退出当前应用,单击“取消”按钮返回程序的主界面。

运行上述代码后,在屏幕上单击“普通对话框”按钮时,打开提示对话框,如图334所示。

2. 内容型对话框

下面在上述实例的基础上,演示内容型对话框的使用。在dialog_demo.xml文件中添加一个“内容型对话框”按钮,当用户单击该按钮时,弹出一个含有三个按钮的对话框。以观众对电影的喜好为例,实现上述功能; 首先,在布局文件中添加“内容型对话框”按钮,代码如下。

【案例338】dialog_demo.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:orientation="vertical" >

<!-- 普通对话框   省略-->

<!-- 内容型对话框   -->

<Button android:id="@+id/contentBtn" android:text="内容型对话框" .../>

</LinearLayout>





然后,在DialogDemoActivity.java中按照“普通对话框”的编写步骤,添加“内容型对话框”对应的代码,代码如下。

【案例339】DialogDemoActivity.java



...

public class DialogDemoActivity extends AppCompatActivity {

//普通对话框

Button normalBtn;

//内容型对话框 

Button contentBtn;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.dialog_demo);

//初始化组件

normalBtn = (Button) findViewById(R.id.normalBtn);

// 设置监听器对象

normalBtn.setOnClickListener(onClickListener);

//

contentBtn =(Button)findViewById(R.id.contentBtn);

contentBtn.setOnClickListener(onClickListener);

}



private OnClickListener onClickListener = new OnClickListener() {







@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.normalBtn: {

 …

}

case R.id.contentBtn:{

//处理内容型的对话框

AlertDialog.Builder builder 

= new AlertDialog.Builder(DialogDemoActivity.this);

builder.setIcon(android.R.drawable.btn_star)

.setTitle("喜欢度调查").setMessage("你喜欢成龙的电影吗?")

.setPositiveButton("很喜欢",

new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

Toast.makeText(DialogDemoActivity.this, 

"我很喜欢他的电影。",Toast.LENGTH_LONG).show();

}

});

//不喜欢

builder.setNegativeButton("不喜欢", 

new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

Toast.makeText(DialogDemoActivity.this,

"我不喜欢他的电影。", Toast.LENGTH_LONG).show();

}

});

builder.setNeutralButton("一般", 

new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

Toast.makeText(DialogDemoActivity.this, 

"一般吧,谈不上喜欢。", Toast.LENGTH_LONG).show();

}});

//显示对话框

builder.show();

}

default:

break;

}

}

};

}





运行上述代码后,在屏幕上单击“内容型对话框”按钮,弹出一个内容型对话框,效果如图335所示。



图335内容型对话框




注意除了上述两种类型的对话框之外,开发人员还可以在对话框中实现一组单选框、多选框或列表项等多种形式,限于篇幅,此处不再赘述。


3.5.2ProgressDialog进度对话框







在用户使用App的过程中,有些操作需要提示用户等待,比如在执行耗时较多的操作时,可以使用进度对话框显示一个进度信息来提示用户等待,此时可以使用ProgressDialog组件来实现。

ProgressDialog有以下两种显示方式。

 滚动的环状图标,是一个包含标题和提示内容的等待对话框。

 带刻度的进度条,和常规进度条的用法一致。

上述两种方式的显示样式可以通过ProgressDialog.setProgressStyle()方法进行设置,该方法的参数取值情况如下。

 STYLE_HORIZONTAL——刻度滚动。

 STYLE_SPINNER——图标滚动,默认选项。

其中,图标滚动可以使用两种方式来实现,一种是使用构造方法创建ProgressDialog对象,再设置对象的属性; 另外一种是直接使用ProgressDialog.show()静态方法返回一个ProgressDialog对象,然后调用show()方法来显示。

下面通过一个简单示例演示ProgressDialog的使用。当用户单击“滚动等待对话框”按钮时,弹出滚动等待类型的对话框; 当用户单击“进度条对话框”按钮时,弹出进度条类型的对话框,对应的布局文件代码如下。

【案例340】progress_demo.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

android:orientation="vertical" >

<!-- 滚动等待对话框   -->







<Button android:id="@+id/progressCircleBtn"

android:text="滚动等待对话框" .../>

<!-- 进度条对话框   -->

<Button android:id="@+id/progressBarBtn"

android:text="进度条对话框" .../>

</LinearLayout>





上述代码中定义了两个按钮,分别用于实现弹出“滚动等待对话框”和“进度条对话框”。

下面在相应的Activity中实现以下功能: 当用户单击“滚动等待对话框”按钮时,屏幕上显示滚动等待对话框; 当用户单击“进度条对话框”按钮时,屏幕上会显示进度条对话框。代码实现如下。

【案例341】ProgressDemoActivity.java



public class ProgressDemoActivity extends AppCompatActivity {

//滚动等待对话框

Button progressCircleBtn;

//进度条对话框

Button progressBarBtn;

//存储进度条当前值,初始为 0

int count = 0;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.progress_demo);

//初始化组件

progressCircleBtn = (Button) findViewById(R.id.progressCircleBtn);

//设置监听器对象

progressCircleBtn.setOnClickListener(onClickListener);

//

progressBarBtn = (Button) findViewById(R.id.progressBarBtn);

progressBarBtn.setOnClickListener(onClickListener);

}

private OnClickListener onClickListener = new OnClickListener() {

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.progressCircleBtn: {

//滚动等待对话框

final ProgressDialog progressDialog = new ProgressDialog(

ProgressDemoActivity.this);

progressDialog.setIcon(R.drawable.ic_launcher);

progressDialog.setTitle("等待");

progressDialog.setMessage("正在加载....");

progressDialog.show();

new Thread(new Runnable() {

@Override

public void run() {

try {







Thread.sleep(5000);

} catch (Exception e) {

e.printStackTrace();

} finally {

progressDialog.dismiss();

}

}

}).start();

}

case R.id.progressBarBtn: {

//滚动等待对话框

final ProgressDialog progressDialog = new ProgressDialog(

ProgressDemoActivity.this); //得到一个对象

//设置为矩形进度条

progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);

progressDialog.setTitle("提示");

progressDialog.setMessage("数据加载中,请稍后...");

progressDialog.setIcon(R.drawable.ic_launcher);

//设置进度条是否为不明确

progressDialog.setIndeterminate(false);

progressDialog.setCancelable(true);

//设置进度条的最大值

progressDialog.setMax(200);

//设置当前默认进度为 0

progressDialog.setProgress(0);

//设置第二条进度值为1000

progressDialog.setSecondaryProgress(1000);

progressDialog.show(); //显示进度条

new Thread() {

public void run() {

while (count <= 200) {

progressDialog.setProgress(count++);

try {

Thread.sleep(100);  //暂停 0.1秒

} catch (Exception e) {

}

}

}

}.start();

}

default:

break;

}

}

};

}





运行上述代码,在屏幕上单击“滚动等待对话框”按钮,效果如图336所示。当用户单击“进度条对话框”按钮后,效果如图337所示。




图336滚动等待对话框




图337进度条对话框




小结

(1) Android应用的绝大部分UI组件都放在android.widget包及其子包中,Android应用程序的所有UI组件都继承了View类。

(2) Android中的界面元素主要由以下几个部分构成: 视图、视图容器、Fragment、Activity和布局管理器。

(3) Android的所有UI组件都是建立在View、ViewGroup基础之上的,Android采用了“组合器”模式来设计View和ViewGroup,其中,ViewGroup是View的子类。

(4) 布局管理器可以根据运行平台来调整组件的大小,程序员的工作只是为容器选择合适的布局管理器即可。

(5) Android提供了多种布局,常用的布局有以下几种: LinearLayout(线性布局)、RelativeLayout(相对布局)、TableLayout(表格布局)和AbsoluteLayout (绝对布局)。

(6) Android提供了两种方式的事件处理: 基于回调的事件处理和基于监听的事件处理。

(7) Android系统中引用Java的事件处理机制,包括事件、事件源和事件监听器三个事件模型。

(8) Android的事件处理机制是一种委派式事件处理方式,该处理方式类似于人类社会的分工协作。这种委派式的处理方式将事件源和事件监听器分离,从而提供更好的程序模型,有利于提高程序的可维护性和代码的健壮性。

(9) 对于基于回调的事件处理模型而言,事件源和事件监听器是统一的,当用户在GUI组件上触发某个事件时,组件自身的方法将会负责处理该事件。

(10) 对Widget组件进行UI设计时既可以采用XML布局方式,也可以采用编码方式来实现,其中,XML布局方式更加简单易用,被广泛使用。

习题

1. 下列可作为EditText编辑框的提示信息的是。



A. android:inputType
B. android:text

C. android:digits
D. android:hint

2. (多选)关于widget组件属性的写法,下列正确的是。

A. android:id="@+id/tv_username"
B. android:layout_width="100px"

C. android:src="@drawable/icon"
D. android:id="@id/tabhost"

3. 下列不是Android SDK中的ViewGroup(视图容器)的是。

A. LinearLayout
B. ListView

C. GridView
D. Button

4. Android提供了和两种事件处理方式。

5. Android中的所有UI组件都是建立在基础之上。

6. 使用来设置AlterDialog的各种属性。

7. 简述Android中常用的几种布局方式。

8. 利用线性布局或相对布局实现一个加、减、乘、除及数字1~9的计算器完整布局,并编写相应的处理事件来完善整个计算功能。

9. 利用网格布局或相对布局实现一个注册界面,注册信息包括用户名、密码、确认密码和邮箱。