Android 的事件处理
标题:Android 的事件处理
作者:猫猫、有点乖
文章目录
1 Android 的事件处理
不管是桌面应用还是手机应用程序,面对最多的就是用户,经常需要处理的就是用户动作 —— 也就是需要为用户动作提供响应,这种为用户动作提供响应的机制就是事件处理。Android 提供了两种方式的事件处理:
- 基于回调的事件处理;
- 基于监听器的事件处理;
对于 Android 基于监听的事件处理而言,主要做法就是为 Android 界面组件绑定特定的事件监听器。
对于 Android 基于回调的事件处理而言,主要做法就是 重写 Android 组件特定的回调方法
,或 重写 Activity 的回调方法
。Android 为绝大部分界面组件都提供了事件响应的回调方法,开发者只要重写它们就可以了。
那这两者有什么区别吗?一般来说,基于回调的事件处理可用于处理一些具有通用性的事件,并且代码会显得比较简洁。但是对于某些特定的事件,无法使用基于回调的事件处理,只能采用基于监听的事件处理。
2 基于监听的事件处理
基于监听的事件处理是一种更“面向对象”的事件处理,这种处理方式与 Java 的 AWT、Swing 的处理方式几乎完全相同。
2.1 事件监听的处理模型
在事件监听的处理模型中,主要涉及如下三类对象:
- EventSource(事件源):事件发生的场所,通常就是各个组件;
- Event(事件):事件封装了界面组件上发生的特定事情,如果程序需要获得界面组件上所发生的相关信息,一般通过 Event 对象来获得;
- EventListener(事件监听器):负责监听事件源所发生的事件,并对各种事件做出相应的响应;
下面举一个简单的例子来说明:
activity_main.xml
<EditText
android:id="@+id/txt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:editable="false"
android:cursorVisible="false"
android:textSize="12pt"
/>
<Button
android:id="@+id/bn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="单击我"
/>
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取应用程序中的 bn 按钮
Button bn = (Button)findViewById(R.id.bn);
// 为按钮绑定事件监听器
bn.setOnClickListener(new MyClickListener());
}
class MyClickListener implements View.OnClickListener
{
@Override
public void onClick(View arg0)
{
EditText txt = (EditText)findViewById(R.id.txt);
txt.setText("bn按钮被单击了!");
}
}
}
以下对上面代码作个简单分析(基于监听的事件处理模型的编程步骤):
- 获取普通界面组件(事件源),也就是被监听的对象;
- 实现事件监听器类,该监听器类是一个特殊的 Java 类,必须实现一个
XxxListener
接口; - 调用事件源的
setXxxListener
方法将事件监听器对象注册给普通组件;
开发者只需要关注实现事件监听器类,注册监听器也只要一行代码即可。
2.2 事件和事件监听器
如果事件源触发的事件足够简单、事件里封装的信息比较有限,那就无须封闭事件对象,将事件对象传入事件监听器。但对于键盘事件、触摸屏事件等,此时程序需要获取事件发生的详细信息:例如键盘事件需要获取是哪个键触发的事件;触摸屏事件需要获取事件发生的位置等,对于这种包含更多信息的事件,Android 同样会将事件信息封装成 XxxEvent
对象,并把该对象作为参数传入事件处理器。下面举一个简单飞机移动例子:
PlaneView.java
public class PlaneView extends View {
public float currentX;
public float currentY;
Bitmap plane;
public PlaneView(Context context) {
super(context);
// 定义飞机图片
plane = BitmapFactory.decodeResource(context.getResources(), R.drawable.plane);
setFocusable(true);
}
@Override
public void onDraw (Canvas canvas) {
super.onDraw(canvas);
// 创建画笔
Paint p = new Paint();
// 绘制飞机
canvas.drawBitmap(plane, currentX, currentY, p);
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
// 定义飞机的移动速度
private int speed = 12;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 去掉窗口标题
requestWindowFeature(Window.FEATURE_NO_TITLE);
// 全屏显示
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
// 创建 PlaneView 组件
final PlaneView planeView = new PlaneView(this);
setContentView(planeView);
planeView.setBackgroundResource(R.drawable.back);
// 获取窗口管理器
WindowManager windowManager = getWindowManager();
Display display = windowManager.getDefaultDisplay();
// 获取屏幕宽和高
int screenWidth = display.getWidth();
int screenHeight = display.getHeight();
// 设置飞机初始位置
planeView.currentX = screenWidth / 2;
planeView.currentY = screenHeight - 500;
// 为 draw 组件键盘事件绑定监听器
planeView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// 获取由哪个键触发的事件
switch (event.getKeyCode()) {
// 控制飞机下移
case KeyEvent.KEYCODE_DPAD_DOWN:
planeView.currentY += speed;
break;
// 控制飞机上移
case KeyEvent.KEYCODE_DPAD_UP:
planeView.currentY -= speed;
break;
// 控制飞机左移
case KeyEvent.KEYCODE_DPAD_LEFT:
planeView.currentX -= speed;
break;
// 控制飞机右移
case KeyEvent.KEYCODE_DPAD_RIGHT:
planeView.currentX += speed;
break;
}
// 通知 planeView 组件重绘
planeView.invalidate();
return true;
}
});
}
}
显示效果如下:
通过键盘上的上下左右键可控制飞机的移动。从以上可得到,在基于事件监听的处理模型中,事件监听器必须实现事件监听器接口,Android 为不同的界面组件 提供了不同的监听接口,这些接口通常以 内部类
的形式存在,在程序中实现事件监听器,通常有如下几种形式:
- 内部类形式:将事件监听器类定义成当前类的内部类;
- 外部类形式:将事件监听器类定义成一个外部类;
- Activity 本身作为事件监听器类:让 Activity 本身实现监听器接口,并实现事件处理方法;
- 匿名内部类形式:使用匿名内部类创建事件监听器对象;
2.3 内部类作为事件监听器类
使用内部类作为事件监听器类有两个优势:
- 使用内部类可以在当前类中复用该监听器类;
- 因为监听器类是外部类的内部类,所以可以*访问外部类的所有界面组件;
例如上面第一个给出的例子是内部类作为事件监听器类。
2.4 外部类作为事件监听器类
使用外部类作为事件监听器类比较少见,主要是因为以下两个原因:
- 事件监听器通常属于特定的 GUI 界面,定义成外部类不利于提高程序的内聚性;
- 外部类形式的事件监听器不能*访问创建 GUI 界面的类中的组件,编程不够灵活;
那什么情况下用外部类作为事件监听器类呢?答案是如果某个事件监听器确实需要被多个 GUI 界面所共享,而且主要是完成某种业务逻辑的实现,则可以考虑使用外部类的形式来定义事件监听器类。
2.5 Activity 本身作为事件监听器
这种形式使用 Activity 本身作为事件监听器类,可以直接在 Activity 类中定义事件处理器方法,这种形式非常简洁,但是有两个缺点:
- 这种形式可能造成程序结构混乱,Activity 的主要职责应该是完成界面初始化工作,但此时还需包含事件处理器方法,从而引起混乱;
- 如果 Activity 界面类需要实现监听器接口,让人感觉比较怪异;
当为某个组件添加该事件监听器对象时,直接使用 this
作为事件监听器对象即可。
2.6 匿名内部类作为事件监听器类
大部分时候,事件处理器都没有什么复用价值,因此大部分事件监听器只是临时使用一次,所以使用匿名内部类形式的事件监听器更合适。实际上,这种形式是目前使用最广泛的事件监听器形式。
2.7 直接绑定到标签
Android 还有种更简单的绑定事件监听器的方式,直接在界面布局文件中为指定标签绑定事件处理方法。对于很多 Android 标签而言,它们都支持 onClick、onLongClick
等属性,这种属性的属性值就是一个形如 xxx(View source) 的方法的方法名。
activity_main.xml
<Button
android:id="@+id/bn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
android:onClick="clickHandler"
/>
MainAcitivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void clickHandler(View source) {
EditText show = (EditText)findViewById(R.id.show);
show.setText("bn按钮被单击了!");
}
}
3 基于回调的事件处理
除了前面介绍的基于监听的事件处理模型之外,Android 还提供了一种基于回调的事件处理模型。从代码实现的角度来看,基于回调的事件处理模型更加简单。
3.1 回调机制与监听机制
如果说事件监听机制是一种委托式的事件处理,那么回调机制则恰好与之相反:对于基于回调的事件处理模型来说,事件源与事件监听器是统一的,或者说事件监听器完全消失了。当用户在 GUI 组件上激发某个事件时,组件自己特定的方法将会负责处理该事件。
为了使用回调机制类处理 GUI 组件上所发生的事件,我们需要为该组件提供对应的事件处理方法 —— 而 Java 又是一种静态语言,我们无法为某个对象动态地添加方法,因此只能继承 GUI 组件类,并重写该类的事件处理方法来实现。
public class MyButton extends Button {
public MyButton(Context context, AttributeSet set) {
super(context, set);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
Log.v("-fanfanblog.cn-", "the onKeyDown in MyButton");
// 返回 true,表明该事件不会向外扩散
return true;
}
}
接下来在布局文件中使用这个自定义的 View,如下:
<cn.fanfanblog.myapplication.MyButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="单击我"
/>
Java 程序无须为该按钮绑定事件监听器 —— 因为该按钮自己重写了 onKeyDown(int keyCode, KeyEvent event)
方法,这意味着该按钮将会自己处理相应的事件。
3.2 基于回调的事件传播
几乎所有基于回调的事件处理方法都有一个 boolean
类型的返回值,该返回值用于标识该处理方法是否能完全处理该事件:
- 如果处理事件的回调方法返回 true,表明处理方法已完全处理该事件,该事件不会传播出去;
- 如果处理事件的回调方法返回 false,表明处理方法并未完全处理该事件,该事件会传播出去;
对于基于回调的事件传播而言,某组件上所发生的事情不仅激发该组件上的回调方法,也会触发该组件所在 Activity 的回调方法 —— 只要事件能传播到该 Activity。
(本文完)
推荐阅读
-
Android SQLite数据库版本升级的管理实现
-
Android+SQLite数据库实现的生词记事本功能实例
-
Android 解决WebView调用loadData()方法显示乱码的问题
-
Android 基于IntentService的文件下载的示例代码
-
Android顶部状态栏透明化并释放空间的两种实现方法
-
Android ListView填充数据的方法
-
小米笔记本大曝光:低压低功耗的Intel Skylake-U系列处理器
-
android调用原生图片裁剪后图片尺寸缩放的解决方法
-
Android使用GPS获取用户地理位置并监听位置变化的方法
-
Android Call(打电话)的基本知识详解