在基于监听器的事件处理模型中,主要涉及三个与之相关的对象:
· Event Source(事件源):发生事件的源头,通常就是各个界面组件。
· Event(事件):界面组件上发生的特定的操作,对于需要保存事件发生时信息的事件,还会把事件封装成一个对应到的XxxEvent对象,传递给事件监听器。
· Event Listener(事件监听器):负责监听事件源所发生的事件,并对不同的事件做出对应的响应。
Android的基于监听器的事件处理机制是一种委派式的事件处理机制:事件源将特定的事件处理委派给事件监听器去监听,当该事件源发生指定的事件之后,事件监听器调用对应的事件处理器来做对应的处理。
每个事件源均可以对特定的事件指定一个事件监听器,每个事件监听器也可以监听一个或多个事件源。因为同一事件源可能发生多种事件,委派式的事件处理机制可以把不同的事件委派给不同的事件监听器来监听处理,但是对于同一事件源的同一事件,只能委派一个事件处理器,如果存在多个,以最后的委派为准。而对于事件处理器而言,它可以同时监听一个或多个事件源的同一事件。
下图诠释了基于监听器的事件整个过程:
下面通过一个简单的实例讲解基于监听器的事件处理模式。
示例:布局一个Button,对它的点击事件做出监听并相应。
代码清单:\codes\04\02\EventListenerDemo\src\com\bookdemo\eventlistenerbasedemo\EventListenerActivity.java
public class EventListenerActivity extends Activity { private Button btn_event; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_eventlistener); // 一个按钮组件,在此为事件源 btn_event = (Button) findViewById(R.id.btnEvent); btn_event.setOnClickListener(click); } // 点击事件的事件监听器 private View.OnClickListener click = new View.OnClickListener() { @Override public void onClick(View v) { // 点击事件的事件处理器 Toast.makeText(EventListenerActivity.this, "按钮被点击了", Toast.LENGTH_SHORT) .show(); } }; }
上面示例程序中,定义了一个View.OnclickListener类,并实现了其中的onClick()方法,这个类将作为事件监听器使用,而其中的onClick()方法被作为事件响应器。从这个示例可以看出,基于监听器的事件处理模型的基本步骤如下:
1. 获取事件源(可以是任意界面组件),也就是被事件监听器监听的对象。
2. 实现事件监听器类,它实际上是一个接口,实现它就是实现它内部的具体方法。
3. 调用事件源的setXxxListener方法将事件监听器对象注册到事件源上。
实际开发中,获取事件源、实现事件监听器,在事件源上注册事件监听器,这三个步骤中,实现事件监听器是重点,其它的步骤按照规范即可。
对于这个实例而言,其事件对象虽然是系统自动产生的,无需开发人员关心,但在实例里事件对象并没有得到明显的体现。这是因为Android对事件监听模型进行了简化,如果事件源触发的事件足够简单,无需传递事件发生时的信息,那么就无需封装事件对象并传递给事件监听器。而上实例中监听的是一个按钮的点击事件,无需封装一个事件对象来记录点击事件发生时的环境信息,但是对于一些其它比较特殊的事件,需要传递事件发生时的环境信息那么就会封装一个XxxEvent对象传递给事件监听器。如OnTouch(触摸)事件的处理器就需要一个额外的MotionEvent参数,它封装了触摸的时候需要传递的额外信息,如触摸点的坐标。
所谓事件监听器,无非就是一个特殊的Java接口,在程序中实现事件监听器其实就是实现Java接口所定义的各个方法。所以Java中实现接口的方法也可以参考使用到Android的监听器事件器的实现中,通常有如下形式:
· 内部类形式:将事件监听器定义成当前类的内部类。
· 外部类形式:将事件监听器定义成一个外部类。
· Activity本身作为事件监听器:让Activity本身实现事件监听器接口,并实现其方法。
· 匿名内部类形式:使用匿名内部类创建事件监听器对象。
这里介绍的几种实现事件监听器的方法,下面分别进行详细介绍以及演示。这里介绍的实例均使用的是同一个布局,定义了两个按钮,分别为其注册了点击事件的事件监听器,这里不再额外的提供布局代码。
使用内部类的形式实现事件监听器,可以在外部类中声明一个内部类并实现事件监听器接口。这里的事件监听器具有和外部类中其它属性具有一样的生命周期以及访问权限,所以外部类中定义的界面组件均可以注册这个内部类的事件监听器,在事件监听器中也可以访问外部类的属性以及方法。
示例:使用内部类实现事件监听器。
代码清单:\codes\04\02\EventListenerDemo\src\com\bookdemo\eventlistenerbasedemo\InnerClassActivity.java
public class InnerClassActivity extends Activity { private Button btnClick1, btnClick2; private ButtonClick buttonClick; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); // 找到事件源 btnClick1 = (Button) findViewById(R.id.btnClick1); btnClick2 = (Button) findViewById(R.id.btnClick2); // 实例化事件监听器 buttonClick=new ButtonClick(); // 为事件源注册事件监听器 btnClick1.setOnClickListener(buttonClick); btnClick2.setOnClickListener(buttonClick); } private class ButtonClick implements View.OnClickListener { // 实现View.OnClickListener 点击事件监听器 @Override public void onClick(View v) { switch (v.getId()) { case R.id.btnClick1: Toast.makeText(InnerClassActivity.this, "点击了按钮1", Toast.LENGTH_SHORT).show(); break; case R.id.btnClick2: Toast.makeText(InnerClassActivity.this, "点击了按钮2", Toast.LENGTH_SHORT).show(); break; default: break; } } } }
使用外部类定义事件监听器,就是在Android组件之外单独声明一个类,并实现事件监听器接口。但是该方法很少在实际项目中使用,因为它有局限性,将不利于程序的内聚性。
示例:使用外部类实现事件监听器。
代码清单:\codes\04\02\EventListenerDemo\src\com\bookdemo\eventlistenerbasedemo\ButtonClickListener.java
public class ButtonClickListener implements OnClickListener { private Context context; public ButtonClickListener(Context context) { // 保存上下文对象 this.context=context; } @Override public void onClick(View v) { // 实现View.OnClickListener 点击事件监听器 switch (v.getId()) { case R.id.btnClick1: Toast.makeText(context, "点击了按钮1", Toast.LENGTH_SHORT).show(); break; case R.id.btnClick2: Toast.makeText(context, "点击了按钮2", Toast.LENGTH_SHORT).show(); break; default: break; } } }
代码清单:\codes\04\02\EventListenerDemo\src\com\bookdemo\eventlistenerbasedemo\OuterClassActivity.java
public class OuterClassActivity extends Activity { private Button btnClick1, btnClick2; private ButtonClickListener buttonClickListener; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); // 找到事件源 btnClick1 = (Button) findViewById(R.id.btnClick1); btnClick2 = (Button) findViewById(R.id.btnClick2); // 实例化外部类定义的事件监听器 buttonClickListener=new ButtonClickListener(OuterClassActivity.this); // 为事件源注册事件监听器 btnClick1.setOnClickListener(buttonClickListener); btnClick2.setOnClickListener(buttonClickListener); } }
在上面实例中,外部类无法访问到当前使用它的上下文对象,所以需要在实例化的时候传递当前上下文,如果需要操作界面组件,还需要传递当前界面中组件的对象。
外部类使用起来虽然比较麻烦,但也并不是一无是处,它可以用来实现一些应用中多个页面通用并共有的操作界面组件的功能。
因为Activity其实也是一个类,它也可以实现事件监听器。所以可以通过Activity去实现事件监听器接口,以Activity来承载事件监听器。
使用Activity作为事件监听器虽然看似非常简洁,无需额外实现事件监听器,只需要实现它的方法即可,但是也并不是全无缺点,Activity主要是作用是承载应用界面的,在其中包含内部组件的事件处理器方法,会导致代码功能逻辑混乱。
示例:使用Activity作为事件监听器。
代码清单:\codes\04\02\EventListenerDemo\src\com\bookdemo\eventlistenerbasedemo\ActivitySelfListenerActivity.java
public class ActivitySelfListenerActivity extends Activity implements View.OnClickListener { private Button btnClick1, btnClick2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); // 找到事件源 btnClick1 = (Button) findViewById(R.id.btnClick1); btnClick2 = (Button) findViewById(R.id.btnClick2); // 为事件源注册事件监听器 //因为当前Activity自是事件监听器,所以传递this即可 btnClick1.setOnClickListener(this); btnClick2.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btnClick1: Toast.makeText(this, "点击了按钮1", Toast.LENGTH_SHORT).show(); break; case R.id.btnClick2: Toast.makeText(this, "点击了按钮2", Toast.LENGTH_SHORT).show(); break; default: break; } } }
事件处理器本身是没有什么复用价值的,因此多数情况下事件监听器只是针对特定组件使用一次,所以使用匿名内部类实现事件监听器是非常合适的。
示例:使用匿名内部类作为事件监听器
代码清单:\codes\04\02\EventListenerDemo\src\com\bookdemo\eventlistenerbasedemo\AnonymousInnerClassActivity.java
public class AnonymousInnerClassActivity extends Activity { private Button btnClick1, btnClick2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); // 找到事件源 btnClick1 = (Button) findViewById(R.id.btnClick1); btnClick2 = (Button) findViewById(R.id.btnClick2); // 为事件源注册事件监听器 btnClick1.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(AnonymousInnerClassActivity.this, "点击了按钮1", Toast.LENGTH_SHORT).show(); } }); btnClick2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(AnonymousInnerClassActivity.this, "点击了按钮2", Toast.LENGTH_SHORT).show(); } }); } }
这里使用的匿名内部类作为事件监听器只能被一个组件注册,如果需要复用,可以把这个匿名内部类的对象保存下来,进而在不同的组件上复用。
示例:可以被复用的匿名内部类事件监听器对象。
代码清单:\codes\04\02\EventListenerDemo\src\com\bookdemo\eventlistenerbasedemo\AnonymousInnerClassReuseActivity.java
public class AnonymousInnerClassReuseActivity extends Activity { private Button btnClick1, btnClick2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); // 找到事件源 btnClick1 = (Button) findViewById(R.id.btnClick1); btnClick2 = (Button) findViewById(R.id.btnClick2); // 为事件源注册事件监听器 btnClick1.setOnClickListener(click); btnClick2.setOnClickListener(click); } // 匿名内部类对象 可以被复用 private View.OnClickListener click = new View.OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.id.btnClick1: Toast.makeText(AnonymousInnerClassReuseActivity.this, "点击了按钮1", Toast.LENGTH_SHORT).show(); break; case R.id.btnClick2: Toast.makeText(AnonymousInnerClassReuseActivity.this, "点击了按钮2", Toast.LENGTH_SHORT).show(); break; default: break; } } }; }
Android还提供了一种更简单的绑定事件处理器的方法,它是基于反射来实现的,直接在布局文件中,为指定组件的标签通过设置属性的方式,指定事件的处理方法。
使用直接在标签中通过设置属性的方式指定标签,虽然简洁,但是也有不足的地方,并不是所有的事件,标签都提供了对应的属性。一般而言,用的最多的就是大多数UI组件标签具有的onClick、onLongClick属性,分别设置点击与长按事件。
注意,使用标签属性设定事件处理的实现方法,其在具体实现的时候,方法名必须与标签属性的一致,并且传递参数也必须与当前事件处理器的实现方法一致。
示例:使用XML属性设置事件处理器
代码清单:\codes\04\02\EventListenerDemo\res\layout\activity_demotoxml.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/btnClick1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="OnClick1" android:text="按钮1" /> <Button android:id="@+id/btnClick2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="OnClick1" android:text="按钮2" /> </LinearLayout>
代码清单:\codes\04\02\EventListenerDemo\src\com\bookdemo\eventlistenerbasedemo\BindingXMLAttrActivity.java
public class BindingXMLAttrActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demotoxml); } // 方法名必须和标签属性设定的一致,并且方法签名也必须规范 public void OnClick1(View view) { Toast.makeText(BindingXMLAttrActivity.this, "点击了按钮1", Toast.LENGTH_SHORT).show(); } public void OnClick2(View view) { Toast.makeText(BindingXMLAttrActivity.this, "点击了按钮2", Toast.LENGTH_SHORT).show(); } }