窥探Android Touch事件内幕系列之一

最近在使用Robolectric进行单元测试的时候经常需要模拟click、touch等UI操作,期间遇到了各种问题,例如:onTouch和onTouchEvent有什么区别?onClick什么时候会响应?使用ShadowView中clickOn模拟点击操作为什么无法响应onTouch?为什么ListView添加滑动菜单功能后,ListView本身不能再滑动?被这些问题困扰了很久,因此决定彻底搞清楚Android Touch事件分发机制。这篇文章先整体介绍了Android UI事件处理机制-基于监听的处理方式和基于回调的处理方式,然后分别从View、ViewGoup角度分析Touch事件背后的实现逻辑,最后结合Robolectric介绍如何在单元测试中测试相关的回调、监听方法。

Android UI事件处理机制

事件是指一个对象的状态发生变化,事件处理是指事件发生时执行的代码。与界面编程相关的就是UI事件-用户在程序界面中执行各种操作,之后应用程序需要对这些操作提供响应动作,这个响应就是由UI事件的处理机制来完成。

Android针对UI事件,提供了两种处理机制:一种是基于监听方式的处理机制,另一种是基于回调方式的处理机制。对于基于监听的处理方式,主要是为Android 界面组件绑定特定的事件监听器;对于基于回调的处理方式,主要的做法是通过为Android 组件重写�特定的回调方法实现。下面我们详细介绍二者具体模型、流程、使用方法。

基于监听的事件处理机制

基于监听的事件处理方式实际是一种委托式(代理)模式,某个组件(事件源)将整个事件的处理委托给特定的对象(绑定在该组件上的事件监听器),由这个特定的对象来进行该事件的响应。

事件监听处理模型

事件监听处理模型涉及到三类对象:

  • 事件源-事件产生的地方,一般就是界面组件。
  • 事件-事件封装了界面组件上的一次用户操作,如果程序需要获取界面组件上所发生事件的相关信息,就可以通过Event对象来获取。
  • 事件监听器-包含事件处理方法,负责监听事件源所发生的事情,并对各种事件做出响应。
事件处理流程

简单来说,基于监听器的事件处理流程是事件源组件发生事件,系统会执行该事件源组件上监听器的对应处理方法。具体流程如下图所示。

基于监听的事件处理流程.png
常见的事件监听器实现方式

在事件处理模型三个重要组成部分中,事件由系统负责生成、任意界面组件都可作为事件源,而事件监听器是整个事件处理核心。因此我们主要的工作就是实现事件监听器。所谓实现事件监听器其实就是实现特定接口的Java类实例,常用的方法包括:

  • 内部类实现

    内部类实现事件监听器的优点是可以在当前外部类中复用该监听器类、监听器类是外部类的内部类,可以自由访问外部类的所有界面组件。

  • 外部类实现

    使用外部类定义事件监听器的形式比较少见,使用外部类实现的缺点主要是事件监听器属于特定GUI,定义成外部类不利于提高程序内聚性、外部类形式的事件监听器不能自由访问创建GUI界面的类中组件,编程不够简洁。

  • Activity实现

    直接在Activity中实现监听器接口,这种方式虽然简单,但是却使得Activity类的职能混乱,Activity主要职责是完成界面初始化工作,但此时还需包含事件处理器方法,违反面向对象单职能原则。

  • 匿名内部类实现

    使用匿名内部类实现,代码简洁,是目前使用最广泛的方法。

基于回调的事件处理机制

与监听的委托式事件处理不一样,基于回调事件处理模型,事件源和事件监听器是统一的。用户在组件上激发某个事件,组件自己特定的方法将会负责处理该事件。通常实现的方法是通过继承组件类,重写相关的事件处理方法。

基于回调的事件传播

基于回调的事件处理方法都有一个boolean返回值,使用该返回值标识该回调方法是否已经完全消费该事件。

  • true-该处理方法已经完全处理事件,该事件不会传播出去
  • false-该处理方法并未完全处理该事件,该事件会传播出去

二者对比

基于监听器的事件处理机制优点:

  • 事件源、事件监听器由两个类实现,使用委托(代理)模式,分工明确,易于维护。
  • Android事件处理机制优先触发该组件绑定的事件监听器,然后才会触发该组件提供的事件回调方法。即监听机制优先级更高。

基于回调的事件处理机制优点:

  • 适合处理事件逻辑比较固定的View

Android Touch 事件分发、响应机制

Android UI事件包括Touch事件、Key事件等,我们这里主要研究Touch事件分发、消费的流程。Touch事件分发消费流程中包括两个主角:View和ViewGroup(Activity的Touch事件实际上是调用它内部的ViewGroup的Touch事件)。Touch事件类型包括ACTION_DOWN,ACTION_UP,ACTION_MOVE。这里我们通过解析View、ViewGroup事件分发响应的源码,来对Android Touch事件的分发进行研究。

View Touch事件响应详细流程

View中关于Touch事件主要涉及两个方法:dispatchTouchEvent、onTouchEvent。Android View 事件分发机制 源码解析 (上)中结合实例及源码对这两个方法进行了详细研究,具体细节可参考该文章。

dispatchTouchEvent

View的dispatchTouchEvent主要就是将事件按照onTouchListener监听器、onTouchEvent事件回调的顺序依次分发。任意一个返回true则表示该事件已经被当前这个View处理了。再回过头来看上面我们提到的Android提供的两种事件处理方法优缺点中『Android事件处理机制优先触发该组件绑定的事件监听器,然后才会触发该组件提供的事件回调方法。即监听机制优先级更高。』,这段话是不是更能理解了?


View的dispatchTouchEvent流程图.png
onTouchEvent

View的onTouchEvent是Android系统处理Touch事件的回调方法,这个方法针对不同事件类型-ACTION_DOWN、ACTION_MOVE、ACTION_UP分别进行处理,我们下面也按照这三种事件类型进行分析。

  • ACTION_DOWN:更新View状态、发送一个检测长按的延时任务(500ms)。检测长按的延时任务是指

    用户从ACTION_DOWN触发开始算起,如果500ms内没有抬起则认为产生了长按事件,这个时候就会触发长按事件的监听器 onLongClickListener的onLongClick方法执行,如果该方法返回为true,则设置mHasPerformedLongPress值为true,这个值 会在ACTION_UP事件中协助判断是否需要触发Click事件的监听器。如果onLongClick方法执行返回false或者是没有产生长按事件,mHasPerformedLongPress值仍为false。

  • ACTION_MOVE:这个事件的处理相对会比较简单,主要是判断是否移出了当前View,如果移出则取消在ACTION_DOWN中设置的View状态、取消长按操作检测等。

  • ACTION_UP:执行ACTION_UP后续步骤的前提条件是当前View的状态需要是pressed或者prepressed,即该View已经响应了ACTION_DOWN事件。然后通过判断mHasPerformedLongPress来决定是否对Click事件进行响应,如果mHasPerformedLongPress为true表示该View已经响应了长按事件,那么就不会再对Click事件进行处理;反之则表示需要处理Click事件,处理方法就是调用Click事件监听器onClickListener的onClick方法。最后清除View的状态,刷新页面完成该事件的处理。

View的onTouchEvent流程图 .png
总结
  • 我们在屏幕上简单的一个触摸操作就会产生Touch事件、LongClick事件、Click事件。
  • 当我们在屏幕上触摸时,第一个产生的事件就是Touch事件,。Android系统为Touch事件提供了两种处理机制-基于监听器方式(onTouchListener的onTouch处理方法)和基于回调的方式(onTouchEvent),其中优先处理监听器方式。
  • 当ACTION_DOWN持续500ms以上,会产生LongClick事件。Android系统为LongClick事件提供了监听器方式(onLongClickLIstener的onLongClick处理方法)。
  • 当ACTION_UP随ACTION_DOWN而来,就会产生Click事件。Android系统为Click事件也是提供了监听器方式(onClickListener的onClick处理方法)。但是Click事件的监听器是否触发则取决于该View是否消费了LongClick事件,如果未消费,那么则触发当前View 的Click事件监听器;如果已经消费了LongClick,则不会触发Click事件监听器。

下一篇将总结ViewGroup Touch事件处理流程及如何结合Robolectric在单元测试中测试Touch相关的回调方法、监听器。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,836评论 25 707
  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    passiontim阅读 15,412评论 2 45
  • 上一篇文章我们主要介绍了Android UI事件处理机制-基于监听器方式、基于回调方法,同时从View的角度分析了...
    桃子妈咪阅读 911评论 1 13
  • 我见过这世间最好的爱情。 没有惊天动地、轰轰烈烈,没有缠绵悱恻、风花雪月。 1 他是家中独子,她却终生不孕。他们厮...
    英语老师青城阅读 473评论 3 2
  • 第四轮打卡:第55天 2017、4.2 觉察日记 事实:草长莺飞四月天,岸边杨柳女绿芽冒。清明长假的第一天比上班还...
    瓯姐姐阅读 250评论 0 0