第5 章
UI 组件与布局 
第4章介绍了Activity,以及Android应用程序中与用户交互的基本单元。一个
Activity中通常包含各种UI控件,以完成不同的功能。这些控件按照一定的位置分布在
Activity的不同区域,这称为布局(layout)。第2 章和第4 章已经使用了Button、
TextView 等控件,以及最简单的布局———LinearLayout(线性布局)。本章将介绍
Android应用程序开发中常用的UI组件和布局。
5.1 常用控件
Android中有多种编写程序界面的方式可供选择。AndroidStudio和Eclipse中都
提供了可视化的界面编辑器。可视化编辑器允许使用拖曳控件的方式对布局进行编辑, 
并能在视图上直接修改控件的属性,但是这种方式并不利于真正了解界面背后的原理和
技术。本节在讲解UI组件和布局时,大多数都采用编写XML代码的方式。
下面从Android系统中几种最常见的控件开始熟悉UI组件。
Android提供了大量的UI控件,合理使用这些控件能轻松地编写出美观的界面。本
节将选择几种最常用的控件,详细介绍其使用方法。
Android控件继承结构如图5.1所示。
从图5.1可以看出,View 是Android所有控件的基类,同时ViewGroup也继承自
View。知道View的层级关系有助于理解View。从图5.1可以发现常用的控件都继承
自View,如果掌握了View的知识体系,那么在界面编程时会更加得心应手。
5.1.1 View 类
可以看到,所有的UI控件(主要在包android.view 和包android.widget中)都是
View的子类。使用较早的Android版本进行应用程序开发时,每当用findViewById(R. 
id.xx)方法时,总要将其返回类型进行强制类型转换,因为该方法返回的是一个View 实
例。其中不得不提View的子类———ViewGroup。Android系统中的所有UI类都建立在
View和ViewGroup 类的基础上。所有View 的子类都称为Widget(小部件),所有
ViewGroup的子类都称为Layout(布局)。View和ViewGroup之间采用组合设计模式, 
可以使得“部分-整体”同等对待。ViewGroup作为布局容器类的最上层,布局容器里又
可以有View和ViewGroup。通过这种方式,获得了UI的组合方式。
ViewGroup的子类用不同的方式管理容器中View 控件的摆放位置以及显示方式;

99
第
5 
章 UI 
组件与布局

但是,对于UI控件具体摆放到什么位置,以及大小等属性,则需要每个布局类的内部类
LayoutParams进行处理,该类是ViewGroup的内部类。LayoutParams类有多个子类实
现,用于指定不同的布局参数。


图5.1 
Android控件继承结构

可以看到,Android中所有的UI控件都是View的子类,所以可以通过继承View类
实现自定义控件。注意,此时需要重载View的构造函数。View的构造方法有下面4 
个,比较常用的是第一个和第二个。

.publicView(Contextcontext); 
.publicView(Contextcontext,AtributeSetatrs); 
.publicView(Contextcontext,AtributeSetatrs,intdefStyleAtr); 
.publicView(Contextcontext,AtributeSetatrs,intdefStyleAtr,intdefStyleRes); 
第一个构造方法的参数context代表该View对象所属的Context对象。第二个构
造方法的参数atrs代表在布局文件中该View对象对应的元素相关的属性值的集合。
使用UI控件时,一般采用下面的步骤。

(1)在布局文件中添加该UI控件的元素(元素的名称与该UI控件的类名完全相
同),并定义该元素的一些属性的值(其中id属性非常重要,下一步将会使用id属性的值
引用该元素)。
(2)在其他资源文件中引用该UI控件;在源代码中定义一个该UI控件类型的变量
(成员变量、局部变量均可),使用findViewById()方法使该变量引用步骤(1)中添加的UI 
控件。

1 00 移动应用开发技术
(3)定义处理UI控件某种事件的监听器,并调用步骤(2)中变量的相关方法,把监听
器与UI控件绑定,之后如果用户触发了该种UI事件,那么Android系统将会回调已经
绑定的监听器中的事件处理方法(例如,第4章中Button控件事件监听器的onClick()方
法,该方法由View.OnClickListener接口定义)。
上述步骤将会在后续各种控件的介绍中多次看到。
5.1.2 TextView 
为展示Android系统中常见控件的使用方法,可在AndroidStudio中新建一个项目
UIWidgetDemo,使用自动创建的MainActivity以及布局文件activity_main.xml。
第2章已经使用TextView控件显示了HelloWorld! 的欢迎信息。
修改项目UIWidgetDemo中activity_main.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"> 
<TextView 
android:id="@+id/tvHello" 
android:layout_width="match_parent" 
android:layout_height="wrap_content" 
android:text="厉害了,我的国!" /> 
</LinearLayout> 
图5.2 TextView 控件运行结果
观察上述布局文件中的<TextView>标签,使用android:id属性给当前控件定义了
一个唯一的标识符,然后使用android:layout_width 和
android:layout_height属性指定了控件的宽度和高度。
Android中所有的控件都具有这两个属性,可选值有3种: 
match_parent、fill_parent和wrap_content,其中match_ 
parent和fill_parent的意义相同,现在更推荐使用match_ 
parent。match_parent表示让当前控件的大小和父布局的
大小一样,也就是由父布局决定当前控件的大小。wrap_ 
content表示让当前控件的大小能够刚好包含控件的内容, 
也就是由控件的内容决定当前控件的大小。根据上述规
则,上述布局文件的代码表示让TextView 的宽度和父布
局的宽度(也就是手机屏幕的宽度)一样,而TextView 的
高度足够包含其中的内容就可以了。当然,除了上述选项
外,也可以对控件的宽度和高度指定固定的值,但是这样
做有时会在多种手机屏幕(大小和分辨率不同)的适配方
面出现问题。
下面通过android:text属性指定了TextView 中显示
的文本内容,运行该程序,运行结果如图5.2所示。

第5 章 UI 组件与布局1 01 
虽然指定的文本内容正常显示了,但是好像并没有看出TextView 控件的宽度和手
机屏幕宽度相同,其实这是由于TextView 中的文本默认居左显示,虽然TextView 控件
的宽度充满了整个屏幕,但是由于文本内容不够长,因此从效果上看不出TextView 控件
的宽度。修改布局文件中TextView控件的对齐方式,代码如下所示。 
<?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"> 
<TextView 
android:id="@+id/tvHello" 
android:layout_width="match_parent" 
android:layout_height="wrap_content" 
android:gravity="center" 
android:text="厉害了,我的国!" /> 
</LinearLayout> 
使用android:gravity属性指定文字的对齐方式,可选值有top、bottom、left、right、
center等,也可以使用|同时指定多个值。这里指定的center,效果上等价于center_ 
vertical|center_horizontal,表示文字在垂直和水平方向都居中对齐。重新运行该程序, 
运行结果如图5.3所示。
这也说明了TextView的宽度的确和屏幕宽度一样。
另外,还可以对TextView中文字的大小和颜色进行修改,代码如下所示。 
<?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"> 
<TextView 
android:id="@+id/tvHello" 
android:layout_width="match_parent" 
android:layout_height="wrap_content" 
android:gravity="center" 
android:textSize="36sp" 
android:textColor="#ff0000" 
android:text="厉害了,我的国!" /> 
</LinearLayout> 
通过android:textSize属性可以指定文字的大小,通过android:textColor属性可以
指定文字的颜色。在Android中,字体的大小使用sp(scaledpixels)作为单位。重新运行
程序,运行结果如图5.4所示。
除上述属性之外,TextView还有很多其他的属性,此处不再一一列举。

1 02 移动应用开发技术
图5.3 TextView 控件运行结果(居中对齐) 
图5.4 TextView 控件运行结果(修改字体大小) 
5.1.3 Button 
Button是应用程序中和用户进行交互的一种常用的控件。第4章中多次使用到
Button。从图5.1 可以看出,Button 类的直接父类是TextView 类,因此5.1.2 节中
TextView的属性Button都会自动继承得到。在布局文件activity_main.xml中添加一
个Button,代码如下: 
<?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"> 
<Button 
android:id="@+id/buttonTest" 
android:layout_width="match_parent" 
android:layout_height="wrap_content" 
android:text="TestButton" /> 
</LinearLayout> 
加入Button之后的界面如图5.5所示。
仔细观察会发现,在布局文件中设置的文本值是TestButton,但最终的显示结果却
是TESTBUTTON,这是因为Android系统会将Button中的所有英文字母自动显示为

第5 章 UI 组件与布局1 03 
图5.5 加入Button之后的界面
大写状态。如果仍然想显示包含大小写的原有形式,可以设置android:textAllCaps属性
值为false,代码如下: 
<Button 
android:id="@+id/buttonTest" 
android:layout_width="match_parent" 
android:layout_height="wrap_content" 
android:text="TestButton" 
android:textAllCaps="false" /> 
接下来为该Button控件的单击事件绑定一个监听器,代码如下: 
public class MainActivity extends AppCompatActivity { 
Button button; 
TextView textView; 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
super.onCreate(savedInstanceState); 
setContentView(R.layout.activity_main); 
this.button=findViewById(R.id.buttonTest); 
this.textView=findViewById(R.id.tvHello); 
//创建监听器类的一个对象 
ButtonListener btnListener=new ButtonListener();

1 04 移动应用开发技术 
//把按钮控件与监听器对象绑定,也称为注册 
this.button.setOnClickListener(btnListener); 
} 
private class ButtonListener implements View.OnClickListener { 
@Override 
public void onClick(View v) { 
MainActivity.this.textView.setText("中国梦!"); 
} 
} 
}
这样,每当用户单击该按钮时,Android系统会执行监听器类中定义的onClick()方
法,只在该方法中编写相关的业务处理逻辑即可。对于初学者来说,上述编程方式比较清
晰明了,但是代码编写比较繁杂,其实,创建的监听器类的对象btnListener只在绑定监听
器时使用,因此使用监听器类的无名对象即可。上述创建对象和绑定监听器的两行代码
可以用下面的一行代码替换。 
this.button.setOnClickListener(new ButtonListener()); 
这种方法调用中的参数,一般称为创建了ButtonListener类的一个匿名对象(也称无
名对象)。更进一步,ButtonListener类的类名也只在上述绑定事件监听器中使用一次, 
因此也可以使用实现了View.OnClickListener接口的一个匿名类的一个匿名对象作为
setOnClickListener()方法的参数,这样就省略了定义类ButtonListener,而只是实现
View.OnClickListener接口中的onClick()方法,在Mainactivity类的onCreate()方法的
最后添加如下代码: 
this.button.setOnClickListener(new View.OnClickListener() { 
@Override 
public void onClick(View v) { 
MainActivity.this.textView.setText("中国梦!"); 
} 
}); 
添加了Button按钮的程序,单击该按钮后TextView 控件中的内容会改为“中国
梦!”,运行结果如图5.6所示。
可以看出,在调用方法时使用实现了View.OnClickListener接口的匿名类的匿名对
象作为参数,代码非常简洁,只是写法看上去不是很直观,理解起来稍有难度。这样,如果
在MainActivity中有多个控件,就不需要为每个控件定义一个事件处理的监听器类了, 
因为那种方式会导致MainActivity的代码结构过于繁杂,不利于代码的编写和维护。这
种方法调用时使用实现了某接口的匿名类的匿名对象的方式,在第6章的多线程程序设
计中还会多次出现,不同的是,接口变成了Runnable,需要实现的方法变成了publicvoid 
run()。
另外,还有一种实现上述功能的方式,那就是在定义类MainActivity的时候,除了从

第5 章 UI 组件与布局1 05 
图5.6 单击Button后的运行结果
AppCompatActivity继承外,还实现View.OnClickListener接口,并在MainActivity类中
实现onClick()方法,代码如下: 
public class MainActivity extends AppCompatActivity 
implements View.OnClickListener { 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
super.onCreate(savedInstanceState); 
setContentView(R.layout.activity_main); 
this.button=findViewById(R.id.buttonTest); 
this.textView=findViewById(R.id.tvHello); 
this.button.setOnClickListener(this); 
} 
@Override 
public void onClick(View v) { 
switch(v.getId()) { 
case R.id.buttonTest: 
this.textView.setText("中国梦!"); 
break; 
default: 
break; 
} 
}

1 06 移动应用开发技术
由于是MainActivity类本身实现了View.OnClickListener接口,实现了onClick() 
方法,因此在绑定事件监听器的时候,方法调用的参数使用this 即可。如果在
MainActivity类有多个控件,这些控件都使用this对象作为事件的监听器,那么在实现
onClick()方法时,需要判断用户单击事件发生在哪个控件上,代码中使用v.getId()获得
发生单击事件的控件对象的id号,之后使用switch-case语句进行处理。由于这种方法
会改变MainActivity类的签名,因此不推荐使用。
另外,无论是TextView,还是Button控件,都可以使用setText()方法设置其中的文
字内容,该方法的定义如下: 
. publicfinalvoidsetText(CharSequencetext); 
. publicfinalvoidsetText(char[]text,intstart,intlen); 
. publicfinalvoidsetText(intresid); 
. publicfinalvoidsetText(intresid,BufferTypetype); 
第一个方法的参数类型是CharSequence接口,String、StringBuffer、StringBuilder等
类都实现了该接口,因此实际参数使用上述类的对象均可。第三个方法的参数是指定的
字符串资源的id,如R.string.app_name,而不是需要显示的整数的内容。如果需要在控
件中显示数值类型的内容,须转换为String类型(最简单的办法是调用toString()方法) 
后调用第一个方法,而不是直接调用第三个方法。
5.1.4 EditText 
EditText是应用程序用来和用户进行交互中使用非常广泛的一种控件,它和
TextView的区别在于,EditText允许用户在控件里输入和编辑内容,同时可以在程序中
对这些内容进行处理。EditText控件的使用场景非常广泛,在进行用户注册、用户登录、
搜索、发送短信、发送微信消息、发微博、聊QQ 等操作中,都会使用EditText控件。从图
5.1中可以看出,EditText和Button类一样,都继承自TextView 类,因此也会继承得到
TextView类的相关成员变量和方法,例如重载的多个setText()方法。
在activity_main.xml布局文件中添加一个EditText控件,代码如下: 
<?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"> 
<EditText 
android:id="@+id/etStatement" 
android:layout_width="match_parent" 
android:layout_height="wrap_content"/> 
</LinearLayout> 
通过上述代码可以看出使用UI控件的一般步骤:首先通过android:id属性给控件
定义一个唯一的id,然后指定该控件的宽度和高度,最后再添加一些该控件特有的属性。

第5 章 UI 组件与布局1 07 
运行程序,运行结果如图5.7所示。
图5.7 EditText运行结果
如果具有比较丰富的使用Android手机的经验,会发现一些人性化的软件会在输入
框中显示一些提示性的文字,然后当用户输入任何内容之后,原有的提示性的文字就会消
失,这种提示功能在Android中很容易实现,通过android:hint属性(hint的意思是提示、
注意事项、暗示等)设置即可。
设置android:hint属性的代码如下: 
<EditText 
android:id="@+id/etStatement" 
android:layout_width="match_parent" 
android:layout_height="wrap_content" 
android:hint="请输入您的心情" /> 
重新运行程序,界面如图5.7(b)所示,输入信息后如图5.7(c)所示。可以看到, 
EditText控件中显示了一段提示信息,当输入内容时,这段文本就会自动消失。
但是,随着输入内容的不断增加,EditText会被不断拉长,这是由于EditText的高度
指定的值是wrap_content,因此该控件总能包含用户输入的文本内容,但是当输入的文本
内容过多时,可能会破坏整个Activity的布局,导致这个界面很难看。为了解决这个问
题,可以使用android:maxLines属性修改布局文件,如下所示。 
<EditText 
android:id="@+id/etStatement" 
android:layout_width="match_parent"