专栏首页Android群英传Android事件处理机制

Android事件处理机制

1.前言

在android的进阶之路上,看《android开发艺术探索》实在看不下去了,开始怀疑自己的android基础了,于是找了两本android基础的书把android基础过一遍,还确实发现了好多知识漏洞-基于回调的事件,这个还真以前没用过,然后就想着把android事件处理机制的知识点都整理一遍,嗯,就是这样的。

2.基于监听的事件

基于监听的事件更接近于“面向对象”的事件处理,这种处理方法与java的AWT/Swing的处理方式相同。

2.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本身实现监听器接口)
  • 绑定到界面组件

2.2内部类作为事件监听器类

MainActivity.java主要代码如下:

点击按钮后:

2.3外部类形式作为事件监听器类

布局界面如下

MainActivity.java主要代码如下

其中,Claculator.java 代码如下:

看看结果

2.4使用匿名内部类作为事件监听器类

我们就在上面的基础上直接改MainActivity.java就行了

2.5Activity作为事件监听器

直接改MainActivity.java就行了

结果:

2.6绑定到组件事件属性

就是在界面组件中为指定的组件通过属性标签定义监听器类 刚刚那个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){
        //逻辑处理代码和使用外部类相同
    }

结果:

3.基于回调的事件

3.1回调机制与监听机制

如果说事件监听机制是一种委托的事件处理,那么回调机制则与之相反,对于基于事件的处理模型来说,事件源与事件监听器是统一的,或者说是事件监听器完全消失了,当用户在UI组件上触发某个事件时,组建自己特定的方法将会负责处理事件 为了使回调方法机制类处理UI组件上发生的事件,开发者需要为该组件提供对应的事件处理方法,而java是一种静态语言,无法为某个对象动态的添加方法,因此只能继续是用UI组件类,并通过重写该类的事件处理的方法来实现 为了处理回调机制的事件处理,android为所有UI组件提供了一些事件处理的回调方法。

下面以一个小例子来说明一下,新建一个工程,布局文件很简单,就一个textview,MainActivity.java中重写了onKeyDown和onKeyUp方法 代码如下

运行截图有四张,按下数字0和松开数字0,按下返回键和松开返回键:

3.2基于回调事件的传播流程

几乎所有基于回调的事件都有一个boolean类型的返回值,发方法用于标识该处理方法是否能够完全处理该事件 (1),如果处理事件的回调方法返回的值为true,则表明该处理方法已完全处理该事件,且事件不会被传播出去 (2),如果处理事件的回调方法返回的值为false,则表明该处理方法并未完全处理该事件,且事件会被传播出去 对于基于回调的事件传播而言,某组件上所发生的事件不仅能触发该组件上的回调方法,也会触发该组件所在的activity类的回调方法-只要事件传播到该activity类

下面以一个小例子来说明android系统中的事件传播流程,该程序重写了EditText类的onKeyDown()方法,而且重写了该EditText所在的Activity类的onKeyDown()方法,由于程序中没有阻止事件的传播,所以程序中可以看到事件从RditText传播到Activity的全过程

自定义的组件类MyTestBox.java

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,不过此处包含进来的时候必须要完整包

    <cn.aiyuan1996.huidiaoprocess.MyTestBox        
        android:id="@+id/myTextBox"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
   </cn.aiyuan1996.huidiaoprocess.MyTestBox>

然后就是MainActivity了:

然后运行程序,发现程序崩溃了,很好,对于这个问题我花了四个小时没解决,后来我同学也花了半小时没解决,后来他回宿舍看了一个他以前写的,才发现问题,下面我们先来看看报错信息

下面是主要报错信息

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): Caused by: android.view.InflateException: Binary XML file line #8: Error inflating class cn.aiyuan1996.huidiaoprocess.MyTestBox

但是看到这个信息,你大概知道是你的自定义view出问题了,但是你可能还是不知道怎么解决,正如笔者,后来研究了好长时间,也查看了相关blog,但是还是没有解决,还好有同学的帮忙。

其实就是构造函数那块出了问题,构造函数要用有两个参数的那个,把上面那个构造函数改成这个就行了

public MyTestBox(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

为什么必须要是这个构造函数呢,看看这三个构造函数先

很明显,两个参数的那个构造函数是负责自定义组件的构造的 bug改好后,我们再运行一遍

随便输入一个东西,我们看看打印了什么内容

主要是看这个顺序,首先是触发的是该组件的绑定的事件监听器,然后是该组件所在的类提供的事件回调方法,最后才是传播给组件所在Activity类,如果在任何一个事件处理方法返回了true,那么该事件将不会被继续向外传播

3.3基于回调触摸事件处理

屏幕事件的处理方法onTouchEvent(),该方法的返回值与键盘响应事件相同,都是当程序完整的处理的该事件,且不希望其他回调方法再次处理该事件时返回true,否则返回false 1)屏幕被按下MotionEvent.getAction()==MotionEvent.ACTION_DOWN 2)离开屏幕MotionEvent.getAction()==MotionEvent.ACTION_UP 3)在屏幕中拖动MotionEvent.getAction()==MotionEvent.ACTION_MOVE

下面以一个小例子来说明没有布局文件,直接上MainActivity.java

看看运行截图:

4.Handler消息传递机制

出于性能优化考虑,android的ui线程操作是不安全的,这意味者如果多个线程并发操作UI组件,可能导致线程安全问题,为了解决这个问题,android制定了一条简单的规则,只允许UI线程修改android里的UI组件 当一个程序第一次启动时,android会同时启动一条主线程,这线程主要负责与UI相关度事件,例如用户的按键事件,用户的触摸事件,以及屏幕绘图事件,并非相关的时间分发到组件进行处理,所以主线程又叫UI线程,故android平台只允许Ui线程修改activity的ui组件,新的进程需要动态改变界面组件的属性值时,就需要用到Handler了

4.1Handler类简介

Handler类主要有两个作用:在新启动的线程中发送消息,在主线程中获取和处理消息 只能通过回调的方法来实现-开发者只需要重写Handler类中处理的消息的方法即可,当新启动的线程发送消息时,消息会发送到与之关联的MessageQueue,而Handler会不断的从MessageQueue中获取并处理消息-这将导致Handler中的处理消息的方法被回调

下面一个实例演示如何在界面中修改界面的组件,循环播放相册中的照片 布局文件很简单,就一个imageview 然后是MainActivity.java的主要代码

上面代码中的Timer类会启动一个新线程,由于不允许在线程中修改UI界面,所以该线程每隔1200毫秒会发送一个消息,该消息会传递到Activity中,再由Handler类进行处理,从而实现了动态切换的效果。

其实Handler还有蛮多要写的,这篇blog篇幅已经不少了,今天先把Handler写道这里,改天有时间后好好研究研究Handler和Looper后,整理一些资料,再写。

总结

  • 内部类:使用内部类作为事件监听器,可以在当前类中重复使用,另外,由于监听器是外部类的内部类,所以可以自由访问外部类的所有界面组件
  • 外部类,外部类作为事件监听器的情况比较少见,原因两点:1.事件监听器通常属于特定的UI界面组件,定义成外部类不利于提高程序的内聚性2.外部类形式的监听器,不能自由访问UI界面组件所在类的组件,编程不够简洁。 但是如果某个事件监听器确实需要被多个GUI界面所共享,而且主要是用来完成某种业务逻辑的实现,则可以考虑是用外部类的形式来定义事件监听器类。
  • 匿名内部类:我还是最喜欢是用匿名内部类,因为大多书监听器都是一次性使用的,是用也蛮简单,new 监听器接口 就行了,java语法好点的人相对容易掌握
  • Activity作为事件监听器:这种做法虽然形式简单,但是有两个缺点 1.造成程序的混乱,Activity的主要作用是完成初始化界面的工作,但是此时居然还要包含时间处理方法,可能会引起混乱 2.Activity实现监听器接口,那么他给开发者的感觉会比较奇怪
  • 绑定到组件事件属性:这种在界面中绑定组件的方式比较直观

作为一个开发者,我还是最喜欢是用匿名内部类的形式,代码简洁,一目了然。

本文分享自微信公众号 - Android群英传(android_heroes),作者:艾圆

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2016-11-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 他山之石,可以攻玉

    用户1907613
  • 推送,从入门到放弃

    用户1907613
  • Bitmap.recycle引发的血案

    用户1907613
  • 如何让PubMed 主动为自己推送最新收录的文献?

    有些小伙伴反应,疫情期间在家闲得发慌,想要读点文献提升下自我吧,还要每天去盯着不同杂志搜寻自己感兴趣的内容非常的心累。所以平时状态就是:

    百味科研芝士
  • 快速学习-在线人数统计

    cwl_java
  • JavaWeb(十六)Listener监听器

    监听器:专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。

    leeqico
  • Servlet监听器

    对于Servlet监听器事件源就是三个域对象request、session、servletContext

    木瓜煲鸡脚
  • Java监听器Listener的使用详解

    监听器用于监听Web应用中某些对象的创建、销毁、增加,修改,删除等动作的发生,然后作出相应的响应处理。当监听范围的对象的状态发生变化的时候,服务器自动调用监听器...

    nnngu
  • .Net 转战 Android 4.4 日常笔记(3)--目录结构分析

    看了创建项目后,出现的文件夹很多确实有点晕,不过经过简单的了解还是跟我们asp.net的目录有点相识滴。 ? 下面这张图,概括了主要的文件用途。其实也只需要了解...

    用户1149182
  • linux内核移植过程问题总结

    移植内核:2.6.30.4 内核根目录下的.config为当前配置内核的且已经配置好的内核配置。make zImage以此为依据 配置内核的过程: cd lin...

    Daotin

扫码关注云+社区

领取腾讯云代金券