5.6 视图(View)
尽管前面的章节没有单独介绍View,但在所有的例子中都涉及了View。因为没有View,就无法将可视控件(TextView、EditText、Button等)显示在窗口上。Activity只是视图的一个容器,本身并不能显示任何控件,必须将一个或多个视图与Activity绑定才可以显示我们看到的丰富多彩的UI。下面就让我们深入了解一下View到底是什么东西,以及如何来操作View。
在Android系统中,任何可视化控件都需要从android.view.View类继承。而任何从android.view.View继承的类都可以称为视图(View)。开发人员可以使用两种方式创建视图对象,一种方式是使用XML来配置视图的相关属性,然后再装载这些视图;另外一种方式是完全使用Java代码的方式来创建视图对象。本节将详细介绍如何使用这两种方式来创建视图对象。
5.6.1 视图简介
Android SDK中的视图类可分为3种:布局(Layout)类、视图容器(View Container)类和视图类(例如,TextView就是一个直接继承于View类的视图类)。这3种类都是android.view.View的子类。
android.view.ViewGroup是一个容器类,该类也是View的子类,所有的布局类和视图容器类都是ViewGroup的子类,而视图类直接继承自View类。图5-32描述了View、ViewGroup、视图容器类及视图类的继承关系。
▲图5-32 视图的继承关系
从如图5-32所示的继承关系可以看出,Button、TextView、EditText都是视图类,TextView是Button和EditText的父类。在Android SDK中还有很多这样的控件类。读者在学习后面的内容时会逐渐接触到这些控件。虽然GridView和ListView是ViewGroup的子类,但并不是直接子类,在GridView、ListView和ViewGroup之间还有几个视图容器类,从而形成了视图容器类的层次结构。虽然布局视图也属于容器视图,但由于布局视图具有排版功能,所以将这类视图单独作为一类。
5.6.2 使用XML布局文件定义视图
XML布局文件是Android系统中定义视图的常用方法,所有的XML布局文件必须保存在res/layout目录中。在前面章节的例子中已经多次使用了XML布局文件来设置窗口中控件的位置、大小等属性。
XML布局文件的命名及定义需要注意如下几点。
XML布局文件的扩展名必须是xml。
由于ADT会根据每一个XML布局文件名在R类的内嵌类中生成一个int类型的变量,这个变量名就是XML布局文件名,因此,XML布局文件名(不包含扩展名)必须符合Java变量名的命名规则,例如,XML布局文件名不能以数字开头。
每一个XML布局文件的根节点可以是任意的视图标签,如<LinearLayout>、<TextView>。
XML布局文件的根节点必须包含android命名空间,而且命名空间的值必须是http://schemas.android.com/apk/res/android。
为XML布局文件中的标签指定ID时需要使用这样的格式:@+id/somestringvalue,其中“@+”表示如果ID值在R.id类中不存在,则新产生一个与ID同名的变量;如果在R.id类中存在该变量,则直接使用这个变量。somestringvalue表示ID值,例如,@+id/textview1。在R.id类中生成的变量对应的资源称为ID资源,这种资源的详细内容会在后面介绍Android资源的章节中讨论。
由于每一个视图ID都会在R.id类中生成与之相对应的变量,因此,视图ID的值也要符合Java变量的命名规则,这一点与XML布局文件名的命名规则相同。
下面是一个标准的XML布局文件的内容:
<!-- 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="fill_parent"
android:layout_height="fill_parent" >
<TextView android:id="@+id/textview1" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="textview1" />
<Button android:id="@+id/button1" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="第一个按钮" />
</LinearLayout>
如果要使用上面的XML布局文件(main.xml),通常需要在onCreate方法中使用setContentView方法指定XML布局文件的资源ID,代码如下:
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
如果想获得在main.xml文件中定义的某个View,可以使用如下代码:
// R.id.textview1是TextView的ID
TextView textView1 = (TextView) findViewById(R.id.textview1);
// R.id.button1是Button的ID
Button button1 = (Button) findViewById(R.id.button1);
在获得XML布局文件中的视图对象时需要注意如下几点。
在使用findViewById方法之前必须先使用setContentView方法装载XML布局文件,否则findViewById方法会由于未找到控件而返回null。也就是说,findViewById方法要在setContentView方法后面使用。
虽然所有的XML布局文件中的视图ID都在R.id类中生成了相应的变量,但使用findViewById方法只能获得已经装载的XML布局文件中的视图对象。例如,有两个XML布局文件test1.xml和test2.xml。在test1.xml文件中定义了一个<TextView>标签,android:id属性值为@+id/textview1。在test2.xml文件中也定义了一个<TextView>标签,android:id属性值为@+id/textview2。这时在R.id类中会生成两个变量:textview1和textview2。但通过setContentView方法装载R.layout.test1后,只能使用findViewById方法获得test1.xml中定义的视图对象。FindViewById(R.id.textview2)则会返回null。
在不同的XML布局文件中可以有相同ID值的视图,但在同一个XML布局文件中,虽然也可以有相同ID值的视图,但通过ID值获得视图对象时,只能获得按定义顺序的第一个视图对象,其他相同ID值的视图对象将无法获得。因此,在同一个XML布局文件中应尽量使视图的ID唯一。
5.6.3 在代码中控制视图
虽然使用XML布局文件可以非常方便地对控件进行布局,但若想控制这些控件的行为,仍然需要编写Java代码。
在上一节介绍了使用findViewById方法获得指定的视图对象,当获得视图对象后,就可以使用Java代码来控制这些视图对象了。例如,下面的代码获得了一个TextView对象,并修改了TextView的文本。
TextView textView = (TextView) findViewById(R.id.textview1);
textView.setText("文本内容");
setText方法不仅可以直接使用字符串来修改TextView的文本,还可以使用字符串资源(会在后面的章节详细介绍)对TextView的文本进行修改,代码如下:
textView.setText(R.string.hello);
其中R.string.hello是字符串资源ID,系统会使用这个ID对应的字符串设置TextView的文本。
注意
当setText方法的参数值是int类型时,会被认为这个参数值是一个字符串资源ID,因此,如果要将TextView的文本设为一个整数,需要将这个整数转换成String类型,例如,可以使用textView.setText(String.valueOf(200))将TextView的文本设为200。
任何应用程序都离不开事件。在Android应用程序中一般使用以setOn开头的方法来设置事件类的对象。例如,下面的代码为一个Button对象设置了单击事件。
Button button = (Button) findViewById(R.id.button1);
// 当前类必须实现android.view.View.OnClickListener接口,在触发单击事件时,系统需要回调
// OnClickListener.onClick方法
button.setOnClickListener(this);
在更高级的Android应用中,往往需要动态添加视图。要实现这个功能,最重要的是获得当前的视图容器对象,这个容器对象所对应的类需要继承ViewGroup类。
将其他的视图添加到当前的容器视图中需要如下几步:
第1步:获得当前的容器视图对象。
第2步:获得或创建待添加的视图对象。
第3步:将相应的视图对象添加到容器视图中。
假设有两个XML布局文件:test1.xml和test2.xml。这两个XML布局文件的根节点都是<LinearLayout>,下面的代码获得了test2.xml文件中的LinearLayout对象,并将该对象作为test1.xml文件中的<LinearLayout>标签的子节点添加到test1.xml的LinearLayout对象中。
// 获得test1.xml中的LinearLayout对象
LinearLayout textLinearLayout1 = (LinearLayout) getLayoutInflater().inflate(R.layout. test1, null);
// 将test1.xml中的LinearLayout对象设为当前容器视图
setContentView(testLinearLayout1);
// 获得test2.xml中的LinearLayout对象,并将该对象添加到test1.xml的LinearLayout对象中
// getLayoutInflater方法可以获取LayoutInflater对象,通常该对象用来装载独立的XML布局文件
LinearLayout testLinearLayout2 = (LinearLayout) getLayoutInflater().inflate(R.layout. test2, testLinearLayout1);
其中inflate方法的第1个参数表示XML布局资源文件的ID,第2个参数表示获得容器视图对象后,要将该对象添加到哪个容器视图对象中,在这里是testLinearLayout1对象。如果不想将获得的容器视图对象添加到任何其他的容器中,inflate方法的第2个参数值为null。
除了上面的添加方式外,也可以使用addView方法向容器视图中添加视图对象,但要将inflate方法的第2个参数值设为null,代码如下:
// 获得test1.xml中的LinearLayout对象
LinearLayout textLinearLayout1 = (LinearLayout) getLayoutInflater().inflate(R.layout. test1, null);
// 将test1.xml中的LinearLayout对象设为当前容器视图
setContentView(testLinearLayout1);
// 获得test2.xml中的LinearLayout对象,并将该对象添加到test1.xml的LinearLayout对象中
LinearLayout testLinearLayout2 = (LinearLayout) getLayoutInflater().inflate(R.layout. test2, null);
// 将testLinearLayout2添加到testLinearLayout1中
testLinearLayout1.addView(testLinearLayout2);
除此之外,还可以完全使用Java代码创建一个视图对象,并将该对象添加到容器视图中,代码如下:
// 创建EditText对象(文本编辑控件)
EditText editText = new EditText(this);
testLinearLayout1.addView(editText);
向容器视图添加视图对象时需要注意如下几点:
如果要向容器视图中添加新的视图或进行其他的操作,setContentView方法的参数值应直接使用容器视图对象,而不是布局文件的资源ID,因为这样可以向容器视图对象中添加新的视图。
一个视图对象只能有一个父视图,也就是说,一个视图对象只能被包含在一个容器视图中。因此,在向容器视图添加其他视图时,不能将XML布局文件中非根节点的视图对象添加到其他的容器视图中。例如,在前面的例子中不能将使用testLinearLayout2.findViewById(R.id.textView2)方法获得的TextView对象添加到testLinearLayout1对象中,这是因为这个TextView对象已经属于test2.xml中的<LinearLayout>标签了,不能再属于test1.xml中的<LinearLayout>标签了。