第三章 Android的事件处理

Android提供两种事件处理方式,基于回调和基于监听器。前者常用于传统图形界面编程中,而后者在AWT/Swing开发中常用。

3.1 事件处理概述

对于基于回调的事件处理而言,主要是重写Android组件特定的回调方法,或者重写Activity的回调方法,一般用于处理通用性的事件。

对于监听的事件处理而言,主要是为Android界面组件绑定特定的事件监听器。

3.2 基于监听的事件处理

事件响应的动作通常以方法的形式组织,但Java是面向对象的编程语言,必须用类把这些方法组织起来,所以事件监听器的核心就是这些方法——事件处理器(Event Handler)。普通Java方法是由程序员调用,而事件处理器方法由系统调用。

事件监听器是特殊的Java对象,注册在事件源上,当用户触发事件时,就会调用事件处理器来响应。

委派式的事件处理:每个组件可以针对不同的事件注册不同的事件监听器,而每个事件监听器可以监听一个或者多个事件源。

1. 使用内部类

// 获取按钮,为按钮注册监听器,在onCreate方法中
Button btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new MyClickListener());
// 定义监听器,内部类
class MyClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
// 事件处理
}
}

编程步骤:

获取界面组件(事件源);

实现监听器类(编程重点),实现XxxListener接口;

setXxxListener方法将事件监听器对象注册给事件源。

在触发事件后,事件会作为参数传递给事件监听器。但在上面的代码中,并没有出现事件的踪迹,原因是,当事件足够简单,事件中封装的信息比较有限,就无须封装事件对象了。但对于键盘事件,触摸屏事件等,就需要详细的信息。Android将其封装成XxxEvent对象。

// 用了匿名内部类
planeView.setOnKeyListener(new OnKeyListener() {
@override
public boolean onKey(View source, int keyCode, KeyEvent event){
switch(event.getKeyCode()){
// 获取由哪个键触发的事件,并处理
}
return true;
}
});

常用的几个接口:

View.OnClickListener // 单击事件
View.OnCreateContextMenuListener // 创建上下文菜单
View.OnFocusChangeListener // 焦点改变
View.OnKeyListener // 按键
View.OnLongClickListener // 长按
View.OnTouchListener // 触摸屏

使用内部类的好处是:可以在当前类中复用该监听器类;内部类也可以自由访问外部类的界面组件。

2. 使用外部类

如果某个事件监听器需要被多个GUI界面共享,且主要是完成某种业务逻辑的实现,则可以定义为外部类。

但并不推荐将业务逻辑写在事件监听器中,包含业务逻辑的事件监听器将导致程序的显示逻辑与业务逻辑耦合。如果确实有多个监听器需要实现相同的业务逻辑方法,可以考虑使用业务逻辑组件来定义业务逻辑功能,再让事件监听器来调用其方法。

// SendSms类onCreate方法中
btn.setOnLongClickListener(new SendSmsListener(this, address, content));
// SendSmsListener类中
private Activity act;
private EditText address, content;
public SendSmsListener(Activity act, EditText address, EditText context) {
this.act = act;
this.address = address;
this.content = content;
}
@Override
public boolean onLongClick(View source) {
// 事件处理代码
return true;
}

3. Activity本身作为事件监听器

缺点如下:

Activity主要是完成界面初始化工作,如果还需包含事件处理方法,会引起程序结构的混乱;

Activity界面类不该实现监听器接口。

// onCreate方法中
btn.setOnClickListener(this);
// 实现事件处理方法
@Override
public void onClick(View v) {
// 事件处理
}

4. 使用匿名内部类

一般来说,事件处理器没有什么复用价值,可复用的代码都被抽象成了业务逻辑方法了,所以应该使用匿名内部类,这是最好的方法。

示例代码在上面内部类部分已经给出。直接new 监听器接口,或者new 事件适配器。

5. 直接绑定到标签

另一种简单的方法是直接在布局文件中指定事件处理方法。

// xml文件中
android:onClick="clickHandler"
// Activity类中
public void clckHandler(View source) {
// 事件处理
}

3.3 基于回调的事件处理

回调机制中事件源和事件监听器是统一的,组件自己负责处理该事件,可以提高程序的内聚性。

继承组件类,并重写该类的事件处理方法。常用的有:

// 简单起见,省略了返回值和参数
onKeyDown() // 按键
onKeyLongPress() // 长按
onKeyShortcut() // 键盘快捷键
onKeyUp() // 松开按键
onTouchEvent() // 触摸屏
onTrackballEvent() // 轨迹球屏

代码示例:

// 自定义View
public class MyButton extends Button {
// 构造方法略
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
// 事件处理
return true;
}
}
// xml文件中,各种属性略
<org.crazyit.event.MyButton />

事件传播

基于回调的事件处理方法都有一个布尔返回值,该返回值标识该方法能否处理该事件,如果不能则返回false,事件将继续传播。

先监听,后回调,最后到Activity

当组件上发生某个按键事件时,首先触发的是该按键绑定的事件监听器(Listener),接着触发该组件提供的事件回调方法(自定义组件中重写的),然后还会传播到该组件所在的Activity

Adnroid事件处理机制保证基于监听的事件监听器被优先触发,基于监听的事件模型中事件源与监听器分工明确,易于维护。

回调模型更适合于处理那些逻辑比较固定的场景,不需要监听,组件自行处理。

3.4 响应系统设置的事件

Configuration可以用于获取用户特定的配置项,以及系统的动态设备配置。可用如下代码:

Configuration cfg = getResources().getConfiguration();

可以获取屏幕方向,触摸方式,导航设备等。

监听系统设置的改变可用如下基于回调的方法:

onConfigurationChanged(Configuration newConfig)

动态更改屏幕方向,可以用setRequestedOrientation(int)方法。

为了能监听系统设置的改变,在AndroidMenifest文件中配置Activityandroid:configChanges属性,各属性值之间用竖线隔开。

如果targetSdkVersion设置超过12,则属性值应当为orientation|screenSize,因为转屏时屏幕大小也变了。

3.5 Handler消息传递机制

只允许主线程(UI线程)修改UI组件,其他线程借助Handler消息传递机制来实现对界面组件的修改。

通过回调的方式:开发者重写Handler中处理消息的方法,新线程发送消息到MessageQueue,而Handler不断从中获取并处理消息。Handler类中处理消息的方法被回调。

void handleMessage(Message msg) // 处理消息,用于被重写
final boolean hasMessage(int what) // 检查消息队列中有无指定what属性的消息
final boolean hasMessage(int what, Object obj) // 检查消息队列中object属性为指定对象的消息
Message obtainMessage() // 多个重载方法,获取消息
sendEmptyMessage(int what) // 发送空消息
final boolean sendMessage(Message msg) // 立即发送消息
final boolean sendEmptyMessageDelayed(int what, long delayMillis) // 指定多少毫秒后发送空消息
final boolean sendMessageDelayed(Message msg, long delayMillis) // 指定多少毫秒后发送消息

示例代码如下:

final Handler myHandler = new Handler()
{
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x1233) {
// do something
}
}
};
new Timer().schedule(new TimerTask()
{
@Override
public void run() {
myHandler.sendEmptyMessage(0x1233);
}
}, 0, 1200);

当新线程发送消息时,handleMessage方法被自动回调。

UI线程中,系统已经初始化了一个Looper对象,所以直接创建Handler即可,而在子线程中,需要程序员创建一个Looper对象,并调用其prepare()方法。

使用Handler的步骤如下:

调用prepare()方法为当前线程创建Looper对象,之后它的构造器会创建与之配套的MessageQueue

创建Handler子类的实例,重写handleMessage方法;

调用loop()方法启动Looper

如果在UI线程中执行耗时操作,将导致ANR异常(20s),这会导致程序无法响应输入事件和Broadcast

3.6 异步任务

一种轻量级的解决方案,不需要借助线程和Handler即可实现。

AsyncTask<Params, Progress, Result>是抽象类,其中参数:

Params:启动任务执行的输入参数类型;

Progress:后台任务完成的进度值类型;

Result:返回结果的类型。

实现步骤:

第一步,创建AsyncTask的子类,并为三个泛型参数指定类型,如不需要指定,用Void

第二步,根据需要,实现如下方法:

doInBackground():后台将要执行的任务,该方法可以调用publishProgress()方法更新进度;

onPrograssUpdate():调用PublishProgress()方法后将触发该方法;

onPreExecute():一些初始化工作,如显示进度条等;

onPostExecute():当doInBackground()完成后,将其返回值传给此方法。

第三步,调用execute()开始执行耗时任务。

第二步中的方法由系统调用,程序员只需重写。

阅读学习材料:《疯狂Android讲义》(第二版)

本章源代码地址:Github

Android学习笔记-事件处理的更多相关文章

  1. Android学习笔记-事件处理之Handler消息传递机制

    内容摘要:Android Handler消息传递机制的学习总结.问题记录 Handler消息传递机制的目的: 1.实现线程间通信(如:Android平台只允许主线程(UI线程)修改Activity里的 ...

  2. Android 学习笔记之Volley(七)实现Json数据加载和解析...

    学习内容: 1.使用Volley实现异步加载Json数据...   Volley的第二大请求就是通过发送请求异步实现Json数据信息的加载,加载Json数据有两种方式,一种是通过获取Json对象,然后 ...

  3. Android学习笔记进阶之在图片上涂鸦(能清屏)

    Android学习笔记进阶之在图片上涂鸦(能清屏) 2013-11-19 10:52 117人阅读 评论(0) 收藏 举报 HandWritingActivity.java package xiaos ...

  4. android学习笔记36——使用原始XML文件

    XML文件 android中使用XML文件,需要开发者手动创建res/xml文件夹. 实例如下: book.xml==> <?xml version="1.0" enc ...

  5. Android学习笔记之JSON数据解析

    转载:Android学习笔记44:JSON数据解析 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,为Web应用开发提供了一种 ...

  6. udacity android 学习笔记: lesson 4 part b

    udacity android 学习笔记: lesson 4 part b 作者:干货店打杂的 /titer1 /Archimedes 出处:https://code.csdn.net/titer1 ...

  7. Android学习笔记36:使用SQLite方式存储数据

    在Android中一共提供了5种数据存储方式,分别为: (1)Files:通过FileInputStream和FileOutputStream对文件进行操作.具体使用方法可以参阅博文<Andro ...

  8. Android学习笔记之Activity详解

    1 理解Activity Activity就是一个包含应用程序界面的窗口,是Android四大组件之一.一个应用程序可以包含零个或多个Activity.一个Activity的生命周期是指从屏幕上显示那 ...

  9. Pro Android学习笔记 ActionBar(1):Home图标区

     Pro Android学习笔记(四八):ActionBar(1):Home图标区 2013年03月10日 ⁄ 综合 ⁄ 共 3256字 ⁄ 字号 小 中 大 ⁄ 评论关闭 ActionBar在A ...

随机推荐

  1. C# FTP操作报550错误

    最近在做FTP创建文件夹和上传文件的功能,测试之后一直提示“远程服务器返回错误: (550) 文件不可用(例如,未找到文件,无法访问文件)”,我在网上找了很久的解决方案也没有解决掉这个问题,网上找到的 ...

  2. L362 When to Bring up Salary During the Job Interview Process

    Money is an awkward topic of conversation for many professionals—even more so when you’re busy tryin ...

  3. Python第六章(北理国家精品课 嵩天等)

    一 1.集合类型定义及其操作: 集合用{}表示,元素用逗号分隔,无序,唯一 集合操作符: |:并 -:减 &:交 ^ :补 <= <:判断子集关系 >= >:判断包含关 ...

  4. OpenGL4.6+vs2017+CMake+Glad+Glfw-3.2.1+GLM随手记一发完整版OpenGL配置过程

    参考自:https://blog.csdn.net/sigmarising/article/details/80470054 下载过程参考上面的链接. 下载好的OpenGL文件夹我已经做好(具体的版本 ...

  5. <html> ---- position

    position 固定的属性,是全局的.和DIV层次无关. <!DOCTYPE html> <html lang="en"> <head> &l ...

  6. JS查看对象属性的方式

    var person = { type: 'person', say: function(){ console.log("Hellow World!") } } //以person ...

  7. UML作业第三次:分析《书店图书销售管理系统》,绘制类图

    一. 类图语法学习小结(类间关系的表示方法) 1.抽象类和接口 我们用关键字abstract或abstract class来定义抽象类(抽象类用斜体显示).也可以使用interface,annotat ...

  8. 再见了,我最爱的OI~~~

    唔,迟到了三个月的感言呢. 我就这样离开OI了,成为了一个退役的OIer,当年高一的时候还觉得自己有很多时间,没想转眼间自己就退役了.呵呵,来到OI 从没有在这个世界带起一丝风浪,也没有拿到一个满意的 ...

  9. anaconda3下64位python和32位python共存

    查看当前工作平台:conda info 切换64位和32位: set CONDA_FORCE_32BIT=1是切换到32位 set CONDA_FORCE_32BIT= 是切换到64位 注意=号前后不 ...

  10. 封装Thread的两种方法 via C++ in Linux

    方法一: 代理线程函数(proxyThreadFunc)作为类的静态成员函数, 回调函数指针作为类的私有成员变量 方法二: 代理线程函数(proxyThreadFunc)作为全局函数,  回调函数指针 ...