第三章 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++中,如果访问数组越界,程序可能会意外终止(像死循环)

    #include<iostream> using namespace std; ];// int main(){ vis[]=;//访问越界 ; } 程序错误表现:

  2. 使用SSD检测框架训练自己的数据

    数据集做好后,训练程序为/examples/ssd/ssd_pascal.py,运行之前,我们需要修改相关路径代码,主要是训练路径的修改和关于自己数据集参数的一些修改. cd /examples/ss ...

  3. java知识点总结--java数据类型

    java中的两大数据类型 1.基本数据类型:也称作内置类型,java语言本身提供的基本数据类型是其他类型(包括java核心类和用户自定义类)的基础 2.引用数据类型:java语言根据基本类型扩展数的其 ...

  4. git bash + gitee

    使用Git Bash从Gitee上下载代码到本地以及上传代码到码云Git: https://www.cnblogs.com/babysbreath/p/7274195.html 指定克隆远端分支 ht ...

  5. Angular 2/4/5+ 重复点击菜单刷新界面

    记一下,网上没找到方法 自己搞了好久  通过跳转到别的界面在跳回来的方式进行实现             //再次点击刷新界面       if (this.router.url == item.ur ...

  6. vue+koa实现简单的图书小程序(3)

    实现一个今年过了多少天的组件的记录我们使用了原生的微信小程序开发文档里的组件 “Progress” 并不需要自己去写: https://developers.weixin.qq.com/minipro ...

  7. 3D数学基础(三)矩阵

    3D引擎中对于矩阵的使用非常多,介绍这些知识也是为了告诉开发者原理,更有助于开发者编写逻辑. (1)固定流水线 各种坐标系之间的转化是通过矩阵相乘得到的,这里面就涉及到了3D固定流水线.作为3D游戏开 ...

  8. jQuery-3.事件篇---键盘事件

    jQuery键盘事件之keydown()与keyup()事件 鼠标有mousedown,mouseup之类的事件,这是根据人的手势动作分解的2个触发行为.相对应的键盘也有这类事件,将用户行为分解成2个 ...

  9. Winscp无法连接linux虚拟机解决

    之前需要从主机传文件到虚拟机上,安装了vmware tools,拖拽文件后发现文件总是会损坏一些,查了一下,使用Winscp就不会出现这个问题. 安装好后配置连接:(Centos7) 打开虚拟机,找到 ...

  10. VBO最佳实践

    VBO的大小 一个VBO应该多大? 你可以创建一个很小的VBO,但最佳方案是把很多对象放到一个VBO里面,这样的话可以减少调用gl函数的数量,比如glBindBuffer.glVertexPointe ...