Android事件处理机制
与界面编程紧密相关的就是事件处理机制,当用户在程序界面上执行各种操作时,应用程序必须为用户动作提供响应动作,这种响应动作就需要通过事件处理来完成。
Android提供了两种方式的事件处理:基于回调的事件处理和基于监听的事件处理。
一般来说,基于回调的事件可用于处理一些具有通用性的事件,基于回调的事件处理代码会显得比较简洁。但对于某些特定的事件,无法使用基于回调的事件处理,只能采用基于监听的事件处理。
一 基于监听器的事件处理
相比于基于回调的事件处理,这是更具“面向对象”性质的事件处理方式。在监听器模型中,主要涉及三类对象:
1)事件源Event Source:产生事件的来源,通常是各种组件,如按钮,窗口等。
2)事件Event:事件封装了界面组件上发生的特定事件的具体信息,如果监听器需要获取界面组件上所发生事件的相关信息,一般通过事件Event对象来传递。
3)事件监听器Event Listener:负责监听事件源发生的事件,并对不同的事件做相应的处理。
基于监听器的事件处理机制是一种委派式Delegation的事件处理方式,事件源将整个事件委托给事件监听器,由监听器对事件进行响应处理。这种处理方式将事件源和事件监听器分离,有利于提供程序的可维护性。
举例:
View类中的OnLongClickListener监听器定义如下:(不需要传递事件)
public interface OnLongClickListener {
boolean onLongClick(View v);
}
View类中的OnLongClickListener监听器定义如下:(需要传递事件MotionEvent)
public interface OnTouchListener {
boolean onTouch(View v, MotionEvent event);
}
二 基于回调的事件处理
相比基于监听器的事件处理模型,基于回调的事件处理模型要简单些,该模型中,事件源和事件监听器是合一的,也就是说没有独立的事件监听器存在。当用户在GUI组件上触发某事件时,由该组件自身特定的函数负责处理该事件。通常通过重写Override组件类的事件处理函数实现事件的处理。
举例:
View类实现了KeyEvent.Callback接口中的一系列回调函数,因此,基于回调的事件处理机制通过自定义View来实现,自定义View时重写这些事件处理方法即可。
public interface Callback {
// 几乎所有基于回调的事件处理函数都会返回一个boolean类型值,该返回值用于
// 标识该处理函数是否能完全处理该事件
// 返回true,表明该函数已完全处理该事件,该事件不会传播出去
// 返回false,表明该函数未完全处理该事件,该事件会传播出去
boolean onKeyDown(int keyCode, KeyEvent event);
boolean onKeyLongPress(int keyCode, KeyEvent event);
boolean onKeyUp(int keyCode, KeyEvent event);
boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
}
三、比对
基于监听器的事件模型符合单一职责原则,事件源和事件监听器分开实现;
Android的事件处理机制保证基于监听器的事件处理会优先于基于回调的事件处理被触发;
某些特定情况下,基于回调的事件处理机制会更好的提高程序的内聚性。
四、下面拿例子说一说两种事件处理事件的不同流程:
1.基于监听的事件:监听的处理流程
基于监听的事件处理主要涉及3个对象
Event Source(事件源):事件发生的场所,通常就是组件,每个组件在不同情况下发生的事件不尽相同,而且产生的事件对象也不相同
Event(事件):事件封装了界面组件上发生的特定事件,通常是用户的操作,如果程序需要获得界面组件上发生的相关信息,一般可通过Event对象来获取
Event Listener(事件监听器):负责监听事件源所发生的事件,并对各种事件做出相应的处理
-
那么我们怎么把事件源与事件联系到一起呢?就需要为事件注册监听器了,就相当于把事件和监听器绑定到一起,当事件发生后,系统就会自动通知事件监听器来处理相应的事件.怎么注册监听器呢,很简单,就是实现事件对应的Listener接口。
1)为事件对象添加监听

2)当事件发生时,系统会将事件封装成相应类型的事件对象

3)当监听器对象接收到事件对象之后,系统调用监听器中相应的事件处理来处理事件

注意:事件源可以是任何的界面组件,不太需要开发者参与,注册监听器叶只要一行代码就实现了,因此事件编程的重点是实现事件监听器类
android设备可用物理编码按键及案件编码

View.OnClickListener:单击事件的事件监听器必须要实现的接口
View.OnCreateContextMenuListener:创建上下文菜单的事件监听器必须要实现的接口
View.OnFocusChangedListener:焦点改变事件的事件监听器必须实现的接口
View.OnKeyListener:按钮事件的事件监听器必须实现的接口
View.OnLongClickListener:长单击事件的事件监听器必须要实现接口
View.OnTouchListener:触摸事件的事件监听器必须要实现的接口
与普通java方法调用不同的是:普通java程序里的方法是由程序主动调用的,而事件处理中的初见处理器方法是由系统负责调用的
程序中实现监听器有以下几种方法
内部类形式
外部类形式
匿名内部类形式
Activity作为事件监听器类形式(activity本身实现监听器接口)
1>.内部类作为事件监听器类
新建一个工程,界面如下 :

xml代码如下
<LinearLayout 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:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:text="helloworld" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="内部类形式"/>
</LinearLayout>
MainActivity.Java代码如下
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
private Button Button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button = (android.widget.Button) findViewById(R.id.button);
Button.setOnClickListener(new EnterClickListener());
}
class EnterClickListener implements android.view.View.OnClickListener{
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "内部类形式", Toast.LENGTH_SHORT).show();
}
}
}
点击按钮后

2>.外部类形式作为事件监听器类
布局界面如下

xml代码如下
<LinearLayout 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:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:text="操作数1" />
<EditText
android:id="@+id/operator1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="请输入一个操作数"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:text="操作数2" />
<EditText
android:id="@+id/operator2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="请输入一个操作数"/>
<Button
android:paddingTop="20dp"
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="结果"/>
</LinearLayout>
MainActivity.java代码如下
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.content.DialogInterface;
import android.view.View.OnClickListener;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity {
private EditText etNumber1;
private EditText etNumber2;
private Button btnResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
btnResult.setOnClickListener(new Claculator(this,etNumber1,etNumber2));
}
private void findView() {
etNumber1 = (EditText) findViewById(R.id.operator1);
etNumber2 = (EditText) findViewById(R.id.operator2);
btnResult = (Button) findViewById(R.id.result);
}
}
其中,Claculator.java 代码如下
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;
public class Claculator implements OnClickListener {
private Activity activity;
private EditText etNumber1;
private EditText etNumber2;
public Claculator(Activity activity,EditText editText,EditText editText2){
this.activity = activity;
this.etNumber1 = editText;
this.etNumber2 = editText2;
}
@Override
public void onClick(View v) {
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(activity, "计算结果为:" + result, Toast.LENGTH_SHORT).show();
}
}
看看结果

3>.使用匿名内部类作为事件监听器类
就在上面的基础上直接改MainActivity.java
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity {
private EditText etNumber1;
private EditText etNumber2;
private Button btnResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
//btnResult.setOnClickListener(new Claculator(this,etNumber1,etNumber2));//是用外部类
//是用匿名内部类
btnResult.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(getApplicationContext(), "计算结果为:" + result, Toast.LENGTH_SHORT).show();
}
});
}
private void findView() {
etNumber1 = (EditText) findViewById(R.id.operator1);
etNumber2 = (EditText) findViewById(R.id.operator2);
btnResult = (Button) findViewById(R.id.result);
}
}
4>.Activity作为事件监听器
直接改MainActivity.java
package cn.aiyuan1996.eventhandler;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity implements OnClickListener{
private EditText etNumber1;
private EditText etNumber2;
private Button btnResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
//btnResult.setOnClickListener(new Claculator(this,etNumber1,etNumber2));//是用外部类
//是用匿名内部类
/*btnResult.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(getApplicationContext(), "计算结果为:" + result, Toast.LENGTH_SHORT).show();
}
});*/
btnResult.setOnClickListener(this);
}
private void findView() {
etNumber1 = (EditText) findViewById(R.id.operator1);
etNumber2 = (EditText) findViewById(R.id.operator2);
btnResult = (Button) findViewById(R.id.result);
}
@Override
public void onClick(View v) {
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(getApplicationContext(), "计算结果为:" + result, Toast.LENGTH_SHORT).show();
}
}
结果

5>.绑定到组件事件属性
就是在界面组件中为指定的组件通过属性标签定义监听器类
刚刚那个xml文件把button那个部分改一下
<Button
android:paddingTop="20dp"
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="结果"
android:onClick="getResult"/>
然后,在MainActivity.java 里面写那个方法就行了,注意,必须是public void getResult(View view)这个格式
public void getResult(View view){
int number1 = Integer.parseInt(etNumber1.getText().toString().trim());
int number2 = Integer.parseInt(etNumber2.getText().toString().trim());
int result = number1 + number2;
Toast.makeText(getApplicationContext(), "计算结果为:" + result, Toast.LENGTH_SHORT).show();
}
结果

2.基于回调的事件
1>.回调机制与监听机制
如果说事件监听机制是一种委托的事件处理,那么回调机制则与之相反,对于基于事件的处理模型来说,事件源与事件监听器是统一的,或者说是事件监听器完全消失了,当用户在UI组件上触发某个事件时,组建自己特定的方法将会负责处理事件
为了使回调方法机制类处理UI组件上发生的事件,开发者需要为该组件提供对应的事件处理方法,而java是一种静态语言,无法为某个对象动态的添加方法,因此只能继续是用UI组件类,并通过重写该类的事件处理的方法来实现
为了处理回调机制的事件处理,android为所有UI组件提供了一些事件处理的回调方法,以view为例
public boolean onKeyDown(int keyCode,
KeyEvent event)
Default implementation of KeyEvent.Callback.onKeyMultiple(): perform press of the view
when KeyEvent.KEYCODE_DPAD_CENTER or KeyEvent.KEYCODE_ENTER
is released, if the view is enabled and clickable.
public boolean onKeyShortcut(int keyCode,
KeyEvent event)
Called when an unhandled key shortcut event occurs.
public boolean onKeyUp(int keyCode,
KeyEvent event)
Default implementation of KeyEvent.Callback.onKeyMultiple(): perform clicking of the view
when KeyEvent.KEYCODE_DPAD_CENTER or
KeyEvent.KEYCODE_ENTER is released.
public boolean onTouchEvent(MotionEvent event)
Implement this method to handle touch screen motion events.
public boolean onTrackballEvent(MotionEvent event)
Implement this method to handle trackball motion events. The
relative movement of the trackball since the last event
can be retrieve with MotionEvent.getX() and
MotionEvent.getY(). These are normalized so
that a movement of 1 corresponds to the user pressing one DPAD key (so
they will often be fractional values, representing the more fine-grained
movement information available from a trackball).
下面以一个小例子来说明一下,新建一个工程,布局文件很简单,就一个textview,MainActivity.java中重写了onKeyDown和onKeyUp方法
代码如下
package cn.aiyuan1996.huidiao;
import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
}
public void showText(String string){
text.setText(string);
}
public void showToast(String string){
Toast toast = Toast.makeText(this, string, Toast.LENGTH_LONG);
toast.setGravity(Gravity.TOP, 0, 100);
toast.show();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_0:
showText("你按下了数字键0");
break;
case KeyEvent.KEYCODE_BACK:
showText("你按下了返回键");
break;
default:
break;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_0:
showToast("你松开了数字键0");
break;
case KeyEvent.KEYCODE_BACK:
showToast("你松开了返回键");
break;
default:
break;
}
text.setText("你没有按下任何键");
return super.onKeyUp(keyCode, event);
}
}
运行截图有四张,按下数字0和松开数字0,按下返回键和松开返回键




2>.基于回调事件的传播流程
几乎所有基于回调的事件都有一个boolean类型的返回值,发方法用于标识该处理方法是否能够完全处理该事件
(1),如果处理事件的回调方法返回的值为true,则表明该处理方法已完全处理该事件,且事件不会被传播出去
(2),如果处理事件的回调方法返回的值为false,则表明该处理方法并未完全处理该事件,且事件会被传播出去
对于基于回调的事件传播而言,某组件上所发生的事件不仅能触发该组件上的回调方法,也会触发该组件所在的activity类的回调方法-只要事件传播到该activity类
下面以一个小例子来说明android系统中的事件传播流程,该程序重写了EditText类的onKeyDown()方法,而且重写了该EditText所在的Activity类的onKeyDown()方法,由于程序中没有阻止事件的传播,所以程序中可以看到事件从RditText传播到Activity的全过程
自定义的组件类MyTestBox.java
package cn.aiyuan1996.huidiaoprocess;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.EditText;
public class MyTestBox extends EditText{
public MyTestBox(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
Log.i("MyTestBox", "这里是MyTestBox的onKeyDown");
return false;
}
}
上面的MyTextBox类重写了EditText类的onKeyDwon()方法,因此,当用户在此组件上按下任意键时都会触发OnKeyDown()方法,在该方法中返回false,即按键事件会继续向外传递
布局文件挺简单的,就是把上面那个自定义的组件包含进来就ok,不过此处包含进来的时候必须要完整包
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res/cn.aiyuan1996.huidiaoprocess"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<cn.aiyuan1996.huidiaoprocess.MyTestBox
android:tag="cn.aiyuan1996.huidiaoprocess.MyTestBox"
android:id="@+id/myTextBox"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
</cn.aiyuan1996.huidiaoprocess.MyTestBox>
</LinearLayout>
然后就是MainActivity了
package cn.aiyuan1996.huidiaoprocess;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnKeyListener;
public class MainActivity extends Activity {
private MyTestBox myTestBox;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myTestBox = (MyTestBox) findViewById(R.id.myTextBox);
myTestBox.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if(event.getAction() == KeyEvent.ACTION_DOWN)
Log.i("onCreate", "这里是MyTextBox的OnKeyListener");
return false;
}
});
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
Log.i("OnKeyDown", "这里是Activity的onKeyDown");
return false;
}
}
然后运行程序,发现程序崩溃了,很好,对于这个问题我花了四个小时没解决,后来我同学也花了半小时没解决,后来他回宿舍看了一个他以前写的,才发现问题,下面我们先来看看报错信息

下面是报错信息
10-25 15:51:20.378: W/IInputConnectionWrapper(1411): showStatusIcon on inactive InputConnection
10-25 16:50:33.068: I/MainActivity(1463): ------>>>>1
10-25 16:50:33.368: D/AndroidRuntime(1463): Shutting down VM
10-25 16:50:33.378: W/dalvikvm(1463): threadid=1: thread exiting with uncaught exception (group=0x409961f8)
10-25 16:50:33.398: E/AndroidRuntime(1463): FATAL EXCEPTION: main
10-25 16:50:33.398: E/AndroidRuntime(1463): java.lang.RuntimeException: Unable to start activity ComponentInfo{cn.aiyuan1996.huidiaoprocess/cn.aiyuan1996.huidiaoprocess.MainActivity}: android.view.InflateException: Binary XML file line #8: Error inflating class cn.aiyuan1996.huidiaoprocess.MyTestBox
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1955)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1980)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.access$600(ActivityThread.java:122)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1146)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.os.Handler.dispatchMessage(Handler.java:99)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.os.Looper.loop(Looper.java:137)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.app.ActivityThread.main(ActivityThread.java:4340)
10-25 16:50:33.398: E/AndroidRuntime(1463): at java.lang.reflect.Method.invokeNative(Native Method)
10-25 16:50:33.398: E/AndroidRuntime(1463): at java.lang.reflect.Method.invoke(Method.java:511)
10-25 16:50:33.398: E/AndroidRuntime(1463): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
10-25 16:50:33.398: E/AndroidRuntime(1463): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
10-25 16:50:33.398: E/AndroidRuntime(1463): at dalvik.system.NativeStart.main(Native Method)
10-25 16:50:33.398: E/AndroidRuntime(1463): Caused by: android.view.InflateException: Binary XML file line #8: Error inflating class cn.aiyuan1996.huidiaoprocess.MyTestBox
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.createView(LayoutInflater.java:589)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:680)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.rInflate(LayoutInflater.java:739)
10-25 16:50:33.398: E/AndroidRuntime(1463): at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
10-
