5.3 事件处理模型

在图形用户界面的开发中,有两个非常重要的内容:一个是界面布局,另一个是控件的事件处理。在Android中,事件处理秉承了JavaSE图形用户界面的处理方式和风格。

Android在事件处理的过程中,主要涉及三个概念:

❏ 事件源:事件发生的场所,通常就是各个控件,例如按钮Button、文本框EditText和活动等。

❏ 事件:用户在界面上的操作的描述,可以封装成为一个类的形式出现,例如键盘操作的事件类是KeyEvent,触摸屏的移动事件类是MotionEvent。与JavaSE图形界面不一样的是并非所有的事件都被封装成为一个类,例如Button单击事件就没有封装成为一个类的形式。

❏ 事件处理者:接收事件对象并对其进行处理的对象,事件处理一般是一个实现某些特定接口类创建的对象。

事件源、事件和事件处理者之间是如何运作的?例如:图5-3所示的LabelButton实例,当单击OK按钮时,将Label标签内容修改为HelloWorld,那么这里的OK按钮就是事件源,单击就是事件,处理“将Label标签修改为HelloWorld”的对象(或程序代码)被称为事件处理者。

一个类(或程序代码)能够成为事件处理者,要求有两个前提:一是要求实现特定接口,LabelButton实例OK按钮事件处理者要求实现android.view.View.OnClickListener接口;二是事件处理者必须在事件源上注册。

提示 事件处理者由于实现XXXListener接口,因此也称为事件监听器。本书以后将事件处理者统一称为事件监听器。

具体的事件处理代码有很多种模型,下面会一一介绍。

5.3.1 活动作为事件监听器

在这种事件处理模型中,事件监听器是当前活动(Activity),活动实现android.view. View.OnClickListener接口。

下面以LabelButton实例为例介绍这种事件处理模型。LabelButton中的MainActivity. java代码如下:

        public class MainActivity extends AppCompatActivity implements View.OnClickListener{     ①
            @Override
            protected void onCreate(Bundle savedInstanceState){
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                //通过id获得OK按钮对象
                Button btnOK =(Button) findViewById(R.id.button);                                ②
                //注册事件监听器 
                btnOK.setOnClickListener(this);                                                  ③
            }

            /*
              * 实现View.OnClickListener接口方法
              */
              @Override
              public void onClick(View view){                                                    ④
                TextView text =(TextView) findViewById(R.id.textView);                           ⑤
                text.setText("HelloWorld");
              }
        }

当前界面的MainActivity实现View.OnClickListener接口,见代码第①行。代码第④行的onClick(View view)方法是实现View.OnClickListener接口所要求的方法,参数view是View类型,事实上该参数就是事件源Button对象。

代码第③行是通过Button的setOnClickListener(View.OnClickListener listener)方法注册事件监听器为this,即MainActivity。

代码第②行和第⑤行是通过id获得控件对象,这些id都是布局文件activity_main.xml中声明的控件id属性。

布局文件activity_main.xml代码如下:

        <?xml version="1.0" encoding="utf-8"?>
        <RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
          android:paddingLeft="@dimen/activity_horizontal_margin"
          android:paddingRight="@dimen/activity_horizontal_margin"
          android:paddingTop="@dimen/activity_vertical_margin"
          tools:context="com.a51work6.labelbutton.MainActivity">

          <TextView
              android:id="@+id/textView"                                           ①
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignParentTop="true"
              android:layout_centerHorizontal="true"
              android:layout_marginTop="59dp"
              android:text="Label"
              android:textSize="18sp"/>
          <Button
              android:id="@+id/button"                                             ②
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_below="@+id/textView"
              android:layout_centerHorizontal="true"
              android:layout_marginTop="49dp"
              android:text="OK"/>
        </RelativeLayout>

在布局文件activity_main.xml中,代码第①行是声明TextView(标签)控件id为textView,代码第②行是声明Button控件的id为button。

❏ 属性android:id="@+id/Button01"是Button按钮的id,通过id可以找到此按钮对象。

❏ 属性android:layout_width="wrap_content"是设置宽度。

❏ 属性android:layout_height ="wrap_content"是设置高度。

其中,宽和高都可以是wrap_content(适合文本大小)值,也可以是fill_parent(根据屏幕大小占满)值,或是match_parent(匹配父容器大小)值,还可以是具体数字,例如200px。数字的单位可以是以下几类:

❏ px:像素屏幕上的点。

❏ dp:与密度无关的像素,是一种基于屏幕密度的抽象单位。在每英寸160点的显示器上,1dp = 1px;在大于160点的显示器上可能增大。

❏ dip:与dp相同。

❏ sp:与刻度无关的像素,是与dp类似,但是可以根据用户的字体大小首选项进行缩放等。

5.3.2 内部类事件监听器

在这种事件处理模型中,事件监听器是活动(Activity)类中声明的内部类,该内部类也要求实现android.view.View.OnClickListener接口。

下面以LabelButton实例为例介绍这种事件处理模型。LabelButton中MainActivity. java代码如下:

        public class MainActivity extends AppCompatActivity{

            @Override
            protected void onCreate(Bundle savedInstanceState){
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                //通过id获得OK按钮对象
                Button btnOK =(Button) findViewById(R.id.button);                           ①
                //注册事件监听器
                btnOK.setOnClickListener(new ButtonOKOnClickListener());                    ②
          }

          class ButtonOKOnClickListener implements View.OnClickListener{                    ③
              /*
                * 实现View.OnClickListener接口方法
                */
              @Override
              public void onClick(View view){
                  TextView text =(TextView) findViewById(R.id.textView);                    ④
                  text.setText("HelloWorld");
              }
          }

      }

代码第③行是实现android.view.View.OnClickListener接口的内部类ButtonOKOn-ClickListener,因此代码第②行注册事件监听器时候,需要实例化ButtonOKOnClickListener。

注意 任何实现android.view.View.OnClickListener的接口类,都可以成为Button事件监听器,无论它是内部类还是外部类。但是需要注意:如果外部类实现接口时,外部类无法访问活动(Activity)中的视图,所以代码第①行和第④行无法使用findViewById方法,findViewById方法是在Activity或View类中定义的。

5.3.3 匿名内部类事件监听器

既然内部类很适合作为事件处理模型,那么内部类的一个特例匿名内部类是否也适合事件处理呢?事实上,在Android中事件处理时经常使用匿名内部类,包括官方的很多源代码都采用匿名内部类的方式。

下面以LabelButton实例为例介绍这种事件处理模型。LabelButton中的MainActivity. java代码如下:

        public class MainActivity extends AppCompatActivity{

            @Override
            protected void onCreate(Bundle savedInstanceState){
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                //通过id获得OK按钮对象
                Button btnOK =(Button) findViewById(R.id.button);
                //注册事件监听器
                btnOK.setOnClickListener(new View.OnClickListener(){                       ①
                    /*
                    * 实现View.OnClickListener接口方法
                    */
                    @Override
                    public void onClick(View v){
                      TextView text =(TextView) findViewById(R.id.textView);
                      text.setText("HelloWorld");
                  }
              });                                                                          ②
          }

      }

上述代码第①行~第②行是注册事件监听,其中new View.OnClickListener(){…}是典型的Java匿名内部类的写法。

提示 匿名内部类就是在使用接口(或者抽象类)的时候,直接给出这个接口(或者抽象类)实现。在Java中,接口(或者抽象类)是不能实例化的,实例化的是它们的实现类。匿名内部类的优点是:编译之后代码紧凑,可以在一定程度上减少字节码文件长度,提高虚拟机的加载速度,从而提高运行速度。它的缺点是:代码可读性差。

纵观三种事件处理模型,各有利弊,用户可以根据自己的喜好来选择,在实际的应用开发过程中,事件处理情况会更加复杂,同一个事件源上会有多个不同事件,因此有的时候不是单一的一种处理模型,而是多种模型的结合。