图形界面开发有两件大事:控件布局,以及控件的事件处理。Android 的事件处理秉承了 Java SE 图形界面的风格,提供两套机制:基于监听,以及基于回调。
基于监听:为界面组件绑定特定的事件监听器。基于回调:重写组件的回调方法,或重写 Activity 的回调方法。
一、基于监听的事件处理
监听模型里主要有三类对象:
-
Event Source(事件源):事件发生的场所,通常是按钮、窗口、菜单等组件。
-
Event(事件):封装界面组件上发生的特定事情(通常是一次用户操作)。
-
Event Listener(事件监听器):监听事件源上的事件,并做出响应。
事件处理流程示意图:

内部类可以作为事件监听器类;如果某个监听器要被多个 GUI 界面共享,可以用外部类;还有一种是匿名内部类作为事件监听器类。
二、三种事件处理模型
通常有三种写法:接口实现、内部类、匿名内部类。下面都以响应 Button 单击、在 TextView 里弹出结果为例。通过 DroidDraw 设计的带 Button 布局如图 1;运行前后对比如图 2。
1. 接口实现
单击 Button 时,屏幕上的 TextView 输出「你好!」。实现接口处理事件,主要是完成 OnClickListener 的 onClick,并绑定到事件源。布局与 Activity 代码(同属原文第一个代码块):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/userlayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/mytext"
android:layout_width="165dp"
android:layout_height="38dp"
android:text="TextView" />
<Button
android:id="@+id/mybutton"
android:layout_width="172dp"
android:layout_height="52dp"
android:text="Button" />
</LinearLayout>
以下代码为在Activity中实现接口,并且绑定在Button按钮上:
package com.wjy.usersrc;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class TestActivity extends Activity implements OnClickListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button mybutton = (Button) findViewById(R.id.mybutton);
mybutton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.mybutton:
TextView mytext = (TextView) findViewById(R.id.mytext);
mytext.setText("你好!");
}
}
}
配置文件 AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wjy.usersrc"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".TestActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
2. 内部类
布局和配置文件与上面相同,主要改造 Activity 里的事件处理。Clicklistener 也可以做成普通类,效果与内部类一样。原文内部类名为 Clicklistener,setOnClickListener 处写成了 clicklistener;switch 里写的是 R.id.mytext。下面按原文保留全部代码。
package com.wjy.usersrc;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class TestActivity extends Activity {
TextView mytext = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button mybutton = (Button) findViewById(R.id.mybutton);
mytext = (TextView) findViewById(R.id.mytext);
mybutton.setOnClickListener(new clicklistener());
}
class Clicklistener implements OnClickListener {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.mytext:
mytext.setText("你好");
}
}
}
}
3. 匿名内部类
官方源码里也很常见。布局和配置不变,只改 Activity 中的绑定代码。三种方式都是实现 onClick 并绑定到事件源:接口实现和内部类通过实现 OnClickListener;匿名内部类则直接覆盖 onClick。
package com.wjy.usersrc;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class TestActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button mybutton = (Button) findViewById(R.id.mybutton);
final TextView mytext = (TextView) findViewById(R.id.mytext);
mybutton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.mytext:
mytext.setText("你好!");
}
}
});
}
}
还有一种更简单的绑定方式:直接在布局里为标签写 android:onClick="clickHandler",对应 Activity 中定义 void clickHandler(View source) 处理单击。该方法必须是 Activity 的成员方法,不能写在 onCreate 里面(原文示例嵌套在 onCreate 中,按原样保留以便对照):
public class Ex003_01Activity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//定义一个事件的处理方法
//轻重source参数代表事件源
public void clickHandler(View source){
TextView show=(TextView)findViewById(R.id.tv);
show.setText("bn按钮被点击了");
}
}
}
三、基于回调的事件处理
从代码看,回调模型更简单。如果说监听是委托式,回调恰好相反:事件源与事件监听器是统一的。Java 是静态语言,无法给对象动态加方法,因此只能继承 GUI 组件类并重写事件处理方法。Android 为所有 GUI 组件提供了回调,以 View 为例:
-
boolean onKeyDown(int keyCode, KeyEvent event):按下某个键。
-
boolean onKeyLongPress(int keyCode, KeyEvent event):长按某个按钮。
-
boolean onKeyShortcut(int keyCode, KeyEvent event):快捷键事件。
-
boolean onKeyUp(int keyCode, KeyEvent event):松开某个按键。
-
boolean onTouchEvent(MotionEvent event):触摸屏事件。
-
boolean onTrackballEvent(MotionEvent event):轨迹球事件。
重写 Button 的 onKeyDown,负责处理按钮上的键盘事件:
public class MyButton extends Button
{
public MyButton(Context context , AttributeSet set)
{
super(context , set);
// TODO Auto-generated constructor stub
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
super.onKeyDown(keyCode , event);
Log.v("-crazyit.org-" , "the onKeyDown in MyButton");
//返回true,表明该事件不会向外扩散
return true;
}
}
回调事件的传播
几乎所有基于回调的事件处理方法都有 boolean 返回值,标识是否完全处理该事件:
某组件上发生的事情不仅激发该组件的回调,也会触发该组件所在 Activity 的回调——只要事件能传播到该 Activity。
| 方式 |
核心做法 |
适用 |
| 接口实现 |
Activity implements OnClickListener,setOnClickListener(this) |
多个控件共用一个监听器 |
| 内部类 / 外部类 |
独立 Listener 类 |
要被多个界面共享时用外部类 |
| 匿名内部类 |
new OnClickListener() { onClick } |
官方源码里很常见 |
| 布局 onClick |
android:onClick 对应 Activity 方法 |
最简单的单击绑定 |
| 回调 |
继承组件并重写 onKeyDown / onTouchEvent 等 |
事件源与监听器合一 |
一句话总结:先给组件绑监听器(接口、内部类或匿名类),需要拦截按键或触摸时再重写回调,并用返回值控制事件是否继续传播。