2.2 屏幕中内容的控制和响应
Android屏幕中的内容是android.view.View类的继承者。按照通常的概念,这些View的继承者可以称为控件。对于屏幕中内容(控件)的控制和响应是GUI系统最基础的内容。
程序中,对于屏幕中内容(控件)的控制和响应的两个使用要点为:
获得控件句柄;
定制控件的行为;
在Android中,得到控件的句柄通常是由id属性来指定的。在布局文件中,用于指定id的XML属性为android:id。
findViewById()方法用于得到一个控件句柄,其参数为控件的id,方法如下所示:
public View findViewById (int id)
findViewById()是View和Activity都具有的方法,对于View表示获得其某个子控件句柄,对于Activity,表示获得其中内容视图(ContentView)的某个子控件句柄。
定制控件的行为,通常需要使用View类中的SetOn{XXX}Listener()系列方法,设置的内容也就是前面所述的android.view包中的几个接口。这个方法可以在每一个继承View控件中被调用。
2.2.1 基本响应方法
本节介绍Android中的几种基本的程序控制方法,要获得的效果是通过两个按钮来控制一个文本框的文字和字体颜色,其运行结果如图2-3所示。
图2-3 控件事件的响应运行结果
本例构建一个应用程序,TestEvent1是活动的名称,res/layout目录中的testevent.xml是界面布局文件。本例布局文件的代码如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/screen" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <TextView android:id="@+id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:textSize="24sp" android:text="@string/text1" /> <Button android:id="@+id/button1" android:layout_width="80sp" android:layout_height="wrap_content" android:layout_gravity="center" android:text="@string/red"/> <Button android:id="@+id/button2" android:layout_width="80sp" android:layout_height="wrap_content" android:layout_gravity="center" android:text="@string/green"/> </LinearLayout>
以上布局文件中定义了两个按钮和一个文本框,这个布局文件被活动设置为Vi e w后,显示的内容就如图2-2所示,只是行为还没有实现。
行为将在源代码文件TestEvent1.java中实现,这部分的代码如下所示:
import android.app.Activity; import android.os.Bundle; import android.graphics.Color; import android.widget.Button; import android.widget.TextView; import android.view.View; import android.view.View.OnClickListener; import android.util.Log; public class TestEvent1 extends Activity { private static final String TAG = "TestEvent1"; public TestEvent1() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.testevent); final TextView Text = (TextView) findViewById(R.id.text1); // 获得句柄 final Button Button1 = (Button) findViewById(R.id.button1); final Button Button2 = (Button) findViewById(R.id.button2); Button1.setOnClickListener(new OnClickListener() { // 实现行为功能 public void onClick(View v) { Text.setTextColor(Color.RED); // 设置文本和文本颜色为“红” Text.setText("RED"); } }); Button2.setOnClickListener(new OnClickListener() { public void onClick(View v) { Text.setTextColor(Color.GREEN); // 设置文本和文本颜色为“绿” Text.setText("GREEN"); } }); } }
在创建的过程中,通过findViewById()获得各个屏幕上面的控件(控件)的背景,这里使用的R.id.button1等和布局文件中各个元素的id是对应的。在布局文件中即使不写android:id这一项也可以正常显示,这一项的目的是在代码中对其进行控制:通过Activity的findViewById()方法根据id获得每一个控件的句柄,以View类型返回,可以转换成实际的类型来使用。
Button控件的setOnClickListener()设置了其中的点击行为,这个方法的参数实际上是一个View.OnClickListener类型的接口,这个接口需要被实现才能够使用,因此在本例的设置中,实现了其中的onClick()方法。这样即可在点击的时候实现相应的功能,在点击的方法中,将通过Te x t的句柄对其进行控制。
在获取句柄时需要转换成相应的控件类型,findViewById()方法的参数是一个整数,返回值是View类型。通过R.id.XXX找到布局文件中定义的ID,然后通过将基类View转换成其实际的类获得真正的句柄。控件转换类应该和布局文件中描述的控件一致,如果转换成这个类的祖先类,将会损失部分继承者自身功能,如果转换成非祖先类并调用其中的方法将会错误。
提示:在代码中使用R.id.myid的时候,如果当前包中根本没有myid这个id,编译将报错。但是如果这个包中的其他布局文件中生成myid这个id,但当前布局文件中没有这个id,编译不会报错,运行时将会返回null,造成运行错误。
2.2.2 变化的响应方法
在实现控件行为方面,除了上述的使用方法,根据Java的语法,还具有其他的实现方式。第二种响应方法可以将不同控件的同一种行为聚集到一个方法的实现中;第三种响应方法可以为一个控件单独实现一个响应。
1.第二种响应方法:由Activity实现某接口
对控件的响应可以由Activity类来实现某个Listener接口,完成其中的方法,然后将其设置为某个控件的监听者,以此得到对控件的响应。
以下程序使用的是上述实现方式,在使用同样的布局文件的情况下,本例使用的源代码文件如下所示:
import android.app.Activity; import android.os.Bundle; import android.graphics.Color; import android.widget.Button; import android.widget.TextView; import android.view.View; import android.view.View.OnClickListener; import android.util.Log; public class TestEvent2 extends Activity implements OnClickListener { // 实现相关的接口 private static final String TAG = "TestEvent2"; private TextView mText; // 保存控件类的引用 private Button mButton1; private Button mButton2; public TestEvent2() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.testevent); mText = (TextView) findViewById(R.id.text1); mButton1 = (Button) findViewById(R.id.button1); mButton1.setOnClickListener(this); // 设置监听者为TestEvent2 mButton2 = (Button) findViewById(R.id.button2); mButton2.setOnClickListener(this); // 设置监听者为TestEvent2 } public void onClick(View v) { Log.v(TAG, "onClick()"); switch(v.getId()){ // 通过getId()区分不同的控件 case R.id.button1: mText.setTextColor(Color.RED); // 设置文本和文本颜色为“红” mText.setText("RED");
break; case R.id.button2: mText.setTextColor(Color.GREEN); // 设置文本和文本颜色为“绿” mText.setText("GREEN"); break; default: Log.v(TAG, "other"); break; } } }
这个例子的主要变化是让活动实现了(implements)OnClickListener()这个接口,也就是需要实现其中的onClick()方法。然后通过setOnClickListener()将其设置到按钮中的参数就是this,表示了当前的活动。
通过以这种方式设置,如果程序中有多个控件需要设置,那么所设置的也都是一个方法。为了保证对不同控件有不同的处理,可以由onClick()方法的参数进行判断,参数是一个View类型,通过getId()获得它们的ID,使用switch…case分别进行处理。
在本例中,需要将文本框(TextView)句柄保存为类的成员(mText),这样就可以在类的各个方法中都能获得这个句柄进行处理。这和第一种方法是有区别的,因为第一个例子实现的接口和获得的TextView在同一个方法中,因此不需要保存TextView的句柄,只需要使用final的局部变量即可。
2.第三种响应方法:构建一个类实现某接口
在响应控件事件方面,一种最为直接的方法是:构建一个类实现所需要的Listener接口,创建这个类的实例之后,将其设置到控件中。
以下为同样功能的第三种实现方法,在布局文件相同的情况下,Java源代码的内容如下所示:
package com.android.basicapp; import android.app.Activity; import android.os.Bundle; import android.graphics.Color; import android.widget.Button; import android.widget.TextView; import android.view.View; import android.view.View.OnClickListener; import android.util.Log; public class TestEvent3 extends Activity{ private static final String TAG = "TestEvent3"; private TextView mText; private Button1_OnClickListener mListener1 = new Button1_Listener(); private Button2_OnClickListener mListener2 = new Button2_Listener(); public TestEvent3() { } class Button1_Listener implements OnClickListener { // 接口的第一个实现 public void onClick(View v) { mText.setTextColor(Color.RED); // 设置文本和文本颜色为“红” mText.setText("RED"); } }
class Button2_Listener implements OnClickListener { // 接口的第一个实现 public void onClick(View v) { mText.setTextColor(Color.GREEN); //设置文本和文本颜色为“绿” mText.setText("GREEN"); } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.testevent); mText = (TextView) findViewById(R.id.text1); final Button mButton1 = (Button) findViewById(R.id.button1); final Button mButton2 = (Button) findViewById(R.id.button2); mButton1.setOnClickListener(mListener1); // 设置监听者的类 mButton2.setOnClickListener(mListener2); // 设置监听者的类 } }
本例通过定义实现活动类中的两个子类,来实现View.OnClickListener这个接口,这种方式是一种最为直接的方式,即为不同的控件单独实现它的相应类。
使用这种实现方法,如果由一个类继承若干个Listener接口,可以让其实现多个不同的行为。实现同时点击和长按事件的方法如下所示:
class MyListener implements OnClickListener,OnLongClickListener { public void onClick(View v) { // ......点击事件的实现 } public boolean onLongClick(View v){ // ......长按事件的实现 } }
随后可以调用setOnClickListener()和setOnLongClickListener()将这个监听者的实现设置到某个控件当中。这种方式尤其适合一个控件需要定制多个行为,并且可能多个行为之间还有共同处理(例如,调用同一个方法)的场合。
2.2.3 控件响应方法比较
以上列出了在Java代码中对控件事件做出响应的三种方法,比较这三种方法,分别具有以下的特点。
第一种方法利用Java的语法,将所有内容都组织到一个方法当中。这种方法虽然看似简单,但是这种程序的结构性并不好。
第二种方法将不同控件的同一种行为聚集到一个方法中:只需要实现一个接口中的方法,就可以为所有控件使用。
第三种方法可以将同一个控件的不同行为聚集到一个类当中。如果构建一个类让其继承多个Listener,并实现其中的方法,可以同时完成对多个行为的响应。
在实际的应用场景中,由于多个同类控件响应同一种行为的情况比较多见,因此在Android应用程序的实现中,第二种方法的使用比第三种方法多。
如果同一个布局中控件的id有所重复,即多个控件使用相同的id,在这种情况下,第二种方法的switch…case将不太容易区分每个控件。这时可以考虑使用第三种方法,自己实现不同的类,在设置监听器的阶段对不同控件做出区分。