预备知识
触摸事件 :
  1. 安卓中把触摸事件封装成了一个类MotionEvent,用户的一次点击、触摸或者滑动都会产生一系列的MotionEvent
  2. 这个类的内容很简单,就两个东西:事件类型+坐标xy
  3. 事件类型有四种
    1. MotionEvent.ACTION_DOWN 表示用户的手指刚接触到屏幕
    2. MotionEvent.ACTION_MOVE 表示用户的手指正在移动
    3. MotionEvent.ACTION_UP 表示用户的手指从屏幕上抬起
    4. Cancel
  4. 所以一次用户触摸屏幕可能会产生这些事件:
    1. 点击屏幕然后松开,Down->Up
    2. 点击屏幕,然后滑动一段距离,松开屏幕 ,Down->Move->…->Move->Up
 
事件分发方法:
在事件分发的过程中,主要涉及到三个方法
  1. dispatchTouchEvent(MotionEvent event)
  2. onInterceptTouchEvent(MotionEvent event)
  3. onTouchEvent(MotionEvent event)
 
 
假设:
  1. 只考虑最重要的四个触摸事件,即:DOWN,MOVE,UP和CANCEL
  2. 一个手势(gesture)是一个事件列
    1. 以一个DOWN事件开始(当用户触摸屏幕时产生)
    2. 后跟0个或多个MOVE事件(当用户四处移动手指时产生)
    3. 最后跟一个单独的UP或CANCEL事件(当用户手指离开屏幕或者系统告诉你手势(gesture)由于其他原因结束时产生)
  3. 当我们说到“手势剩余部分”时指的是手势后续的MOVE事件和最后的UP或CANCEL事件
  4. 不考虑多点触摸手势(我们只假设用一个手指)
  5. 忽略多个MOVE事件可以被归为一组这一实际情况
  6. 假设文中的view都没有注册onTouchListener
  7. 假设一种视图层次:
    1. 最外层是一个ViewGroup A,包含一个或多个子view(children),其中一个子view是ViewGroup B,ViewGroupB中又包含一个或多个子view,其中一个子view是 View C,C不是一个ViewGroup
    2. 忽略同层级view之间可能的交叉叠加
  8. 假设情况:
    1. 用户首先触摸到的屏幕上的点是C上的某个点,该点被标记为触摸点(touch point),DOWN事件就在该点产生
    2. 然后用户移动手指并最后离开屏幕,此过程中手指是否离开C的区域无关紧要,关键是手势(gesture)是从哪里开始的
  9. 假设不考虑dispatchTouchEvent方法
 
假设不考虑onInterceptTouchEvent,同时没有重写事件分发方法时:
  1. DOWN事件被传到C的onTouchEvent方法中,该方法返回false,表示“我不关心这个手势(gesture)”
  2. 因此,DOWN事件被传到B的onTouchEvent方法中,该方法同样返回false,表示B也不关心这个手势
  3. 同样,因为B不关心这个手势,DOWN事件被传到A的onTouchEvent方法中,该方法也返回false
  4. 由于没有view关心这个手势(gesture),它们将不再会从“手势剩余部分”中接收任何事件
(一个更好的情况描述是打log看哪些方法被调用)
 
假设不考虑onInterceptTouchEvent,但重写事件分发(加上处理事件)
假设C实际上是关心这个手势(gesture)的,原因可能是C被设置成可点击的(clickable)或者你覆写了C的onTouchEvent方法
  1. DOWN事件被传递给C的onTouchEvent方法,该方法可以做任何它想做的事情,最后返回true。
  2. 因为C说它正在处理这个手势(gesture),则DOWN事件将不再被传递给B和A的onTouchEvent方法。
  3. 因为C说它正在处理这个手势(gesture),所以“手势剩余部分”的事件也将传递给C的onTouchEvent方法,此时该方法返回true或false都无关紧要了,但是为保持一致最好还是返回true。
 
从这里可以看出,各个View的onTouchEvent方法对DOWN事件的处理,代表了该View对以此DOWN开始的整个手势(gesture)的处理意愿,返回true代表愿意处理该gesture,返回false代表不愿意处理该gesture
 
加上onInterceptTouchEvent但不拦截
  1. onInterceptTouchEvent方法它只存在于ViewGroup中,普通的View中没有这个方法
  2. 在任何一个view的onTouchEvent被调用之前,它的父辈们将先获得拦截这个事件的一次机会
  3. 换句话说,它们可以窃取该事件。在刚才的“处理事件”部分中,我们遗漏了这一过程
 
现在我们把它加上,情况是这样:
  1. DOWN事件被传给A的onInterceptTouchEvent,该方法返回false,表示它不想拦截。
  2. DOWN又被传递给B的onInterceptTouchEvent,它也不想拦截,因此该方法也返回false。
  3. 现在,DOWN事件被传递到C的onTouchEvent方法,该方法返回true,因为它想处理以该事件为首的手势(gesture)。
  4. 现在,该手势的下一个事件MOVE到来了。这个MOVE事件再一次被传递给A的onInterceptTouchEvent方法,该方法再一次返回false,B也同样如此。
  5. 然后,MOVE事件被传递给C的onTouchEvent,就像在前一部分中一样。
  6. “手势剩余部分”中其他事件的处理过程和上面一样,假如A和B的onInterceptTouchEvent方法继续返回false的话
 
这里有两点需要注意:
  1. 虽然ViewGroup A和B的onInterceptTouchEvent方法对DOWN事件返回了false,后续的事件依然会传递给它们的onInterceptTouchEvent方法,这一点与onTouchEvent的行为是不一样的
  2. 假如DOWN事件传给C的onTouchEvent方法时,它返回了false,DOWN事件会继续向上传递给B和A的onTouchEvent,即使它们在onInterceptTouchEvent方法中说它们不想拦截这个DOWN事件,两者是独立的
 
由此可见,DOWN事件的处理实际上经历了一下一上两个过程,下是指A->B的onInterceptTouchEvent,上是指C->B->A的onTouchEvent,当然,任意一步的方法中返回true,都能阻止它继续传播。
 
onInterceptTouchEvent拦截事件
让我们更进一步,假设B没有拦截DOWN事件,但它拦截了接下来的MOVE事件。原因可能是B是一个scrolling view。当用户仅仅在它的区域内点击(tap)时,被点击到的元素应当能处理该点击事件。但是当用户手指移动了一定的距离后,就不能再视该手势(gesture)为点击了——很明显,用户是想scroll。这就是为什么B要接管该手势(gesture)。
 
下面是事件被处理的顺序:
  1. DOWN事件被依次传到A和B的onInterceptTouchEvent方法中,它们都返回的false,因为它们目前还不想拦截。
  2. DOWN事件传递到C的onTouchEvent方法,返回了true。
  3. 在后续到来MOVE事件时,A的onInterceptTouchEvent方法仍然返回false。
  4. B的onInterceptTouchEvent方法收到了该MOVE事件,此时B注意到用户手指移动距离已经超过了一定的threshold(或者称为slop)。因此,B的onInterceptTouchEvent方法决定返回true,从而接管该手势(gesture)后续的处理。
  5. 然后,这个MOVE事件将会被系统变成一个CANCEL事件,这个CANCEL事件将会传递给C的onTouchEvent方法。
  6. 现在,又来了一个MOVE事件,它被传递给A的onInterceptTouchEvent方法,A还是不关心该事件,因此onInterceptTouchEvent方法继续返回false。
  7. 此时,该MOVE事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。事实上,该MOVE以及“手势剩余部分”都将传递给B的onTouchEvent方法(于是根本不会传给子view)(除非A决定拦截“手势剩余部分”)。
  8. C再也不会收到该手势(gesture)产生的任何事件了
 
下面的一些小事情可能会令你感到吃惊:
  1. 如果一个ViewGroup拦截了最初的DOWN事件,该事件仍然会传递到该ViewGroup的onTouchEvent方法中。
  2. 另一方面,如果ViewGroup拦截了一个半路的事件(比如,MOVE),这个事件将会被系统变成一个CANCEL事件,并传递给之前处理该手势(gesture)的子View,而且不会再传递(无论是被拦截的MOVE还是系统生成的CANCEL)给ViewGroup的onTouchEvent方法。只有再到来的事件才会传递到ViewGroup的onTouchEvent方法中。
 
从此开始,你可以更进一步:
  1. 比如对覆写 requestDisallowInterceptTouchEvent,C可以用该方法阻止B窃取事件
  2. 如果你想更加疯狂一点,你可以在你自己的ViewGroup中直接覆写dispatchTouchEvent方法,并对传递进来的事件做任何你想做的处理
但这样的话你可能会破坏一些约定,所以应当小心
 

假设不考虑事件的类型
同时考虑第一个dispatch方法,并且在三个方法中打上log,能看到事件传递过程是这样的(Android群英传里写得很好)
 
ViewGroup能说三句话:我收到了,我转发了,我尝试处理一下
View只能说两句话:我收到了,我尝试处理一下
一个事件传递进来
  1. ViewGroupA首先收到事件,说一声,我收到了
  2. 但是他先不处理,它先转发给子View,然后说,我转发了
  3. ViewGroupB然后收到事件,也说一声,我收到了
  4. 但是他也先不处理,它先转发给子View,然后说,我转发了
  5. ViewC最后收到事件,那也说一声,我收到了
  6. (--------上面是事件传递过程,下面是事件处理过程--------)
  7. 他直接尝试处理,说,我尝试处理一下
  8. (结果发现自己无法处理,没有消耗事件,于是事件原路返回)
  9. ViewGroupB收到返回的事件,那就处理一下呗,于是说,我尝试处理一下
  10. ViewGroupB也收到返回的事件,也说,我尝试处理一下
三句话对应的ViewGroup的方法是(感觉对应得有点怪,因为这个中文名只是为了好记,其实并不是本意)
  1. 我收到了:dispatchTouchEvent [派遣,发送]
  2. 我转发了:onInterceptTouchEvent [拦截]
  3. 我尝试处理一下:onTouchEvent
 
然后这三个方法都有返回值
  1. 前两句话是事件传递过程的
    1. 返回true,表示拦截,到此中断,就不往下走了;
    2. 返回false,表示不拦截,继续往下走
  2. 第三句话是事件处理过程的(完全一模一样,只是意义不同)
    1. 返回true,表示拦截,到此中断,就不往下走了;
    2. 返回false,表示不拦截,继续往下走
  3. 默认情况下,三个方法都是返回false
    1. 而每个地方其实都可以改成true,这样,回路就直接中断了
    2. 但是如果在onInterceptTouchEvent这个方法中返回true,那么不是直接结束回路,而是会跳到对应的onTouchEvent回路也就是事件处理回路中,并且所有外层的onTouchEvent都会被执行
    3. 在 onTouchEvent方法中返回true,就确实是直接中断
 
而在重写方法时,虽然dispatchTouchEvent是第一步,但是我们一般不会重写它,所以我们一般关注后两个方法
 

论事件的流向的复杂性
 
事件的流向跟这几个因素有关:
  1. 这个View是什么;两种情况:View、ViewGroup;会影响回调方法数
  2. 这个方法是什么;三种情况:;两类情况:事件传递过程、事件处理过程;会影响后续怎么跳
  3. 这个方法返回什么;两种情况:true、false;会影响是否结束回路
  4. 这个事件的类型;四种情况:DOWN,MOVE,UP和CANCEL
所以这块的逻辑真是有点乱。。。
 

伪代码解释法
 
很多人用这段伪代码来理清三个分发方法间的关系(我个人是觉得一下子拔高了理解门槛):
public boolean dispatchTouchEvent(MotionEvent event) {

    boolean consume = false;

    if (onInterceptTouchEvent(event)) {

        consume = onTouchEvent(event);

    } else {

        consume = child.dispatchTouchEvent(event);

    }

    return consume;
}
这段解释倒是不错:
  1. 在dispatchTouchEvent中,先调用ViewGroup自身的onInterceptTouchEvent方法,判断自己是否要拦截
    1. 如果这时候自己拦截,那就调用自己的onTouchEvent方法
      1. 如果onTouchEvent方法返回了True,那么这次的事件就算消耗了,事件传递到此为止
      2. 如果返回了False,证明这次没有消耗这次MotionEvent,那么这次的事件就会往上返回,由上一级继续处理;
    2. 如果当前ViewGroup的onInterceptTouchEvent返回了False,那就会调用它的子view的dispatchTouchEvent方法,这样这个事件就传递下去了
  2. 如果它的子View处理不了,那么还会回来调用ViewGroup的onTouchEvent方法,当然这一点是没有在这一段伪代码里体现的
 
这就是ViewGroup层的事件分发,当然不是这么简单,这只不过是通过简单的方式去理解,其实在真实的事件分发中,有很多问题需要注意:
  1. 一个完成的事件序列以Down开始,中间可能包含若干个Move,然后以Up结束
  2. 一个view一旦拦截某个事件,当前事件所在的完整事件序列将都会由这个view去处理
    1. 反应在真实的代码中,就是一旦view拦截了down事件,那么此后的move和up事件都将不调用onInterceptTouchEvent,而直接由它处理
    2. 这就也意味着在onInterceptTouchEvent处理事件是不合适的,因为有可能来了事件,却直接跳过onInterceptTouchEvent方法
    3. 这个也意味着,一旦一个ViewGroup没有拦截ACTION_DOWN,那么这个事件序列的其他Action,它都将收不到,所以在处理ACTION_DOWN的时候,尤其需要谨慎
    4. ( 哦,所以这就很清晰了,dispatch只是入口,它什么都不干,然后intercept是一个第一次拦截的标志,只会被调用一次,最后ontouch才是真正处理事情,根据不同的event做不同的处理 )
  3. onTouchEvent中是要判断MotionEvent的Action,因为一次点击操作就会调用两次onTouchEvent方法,一次是ACTION_DOWN,一次是ACTION_UP,如果手滑一下,还会有若干个ACTION_MOVE
  4. ViewGroup默认不拦截任何事件,源码中ViewGroup的onInterceptTouchEvent方法默认返回的是false
  5. 整个事件分发,看起来都是由外向内传递的,父View将事件传递给子View,理论上来看,子View是没有办法影响到父View的事件处理的,但是有一个标示位,requestDisallowInterceptTouchEvent方法,通过这个方法 ,子View能够影响父view的事件处理,这个可以用于解决父view和子view的滑动冲突

Android事件分发机制理解的更多相关文章

  1. [转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

    Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分 ...

  2. android的事件分发机制理解

    android的事件分发机制理解 1.事件触发主要涉及到哪些层面的哪些函数(个人理解的顺序,可能在某一层会一次回调其它函数) activity中的dispatchTouchEvent .layout中 ...

  3. 【转】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...

  4. Android事件分发机制(下)

    这篇文章继续讨论Android事件分发机制,首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子 ...

  5. Android事件分发机制(上)

    Android事件分发机制这个问题不止一个人问过我,每次我的回答都显得模拟两可,是因为自己一直对这个没有很好的理解,趁现在比较闲对这个做一点总结 举个例子: 你当前有一个非常简单的项目,只有一个Act ...

  6. Android事件分发机制源码分析

    Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...

  7. 【自己定义控件】android事件分发机制

    自己定义Viewgrou中我们或许会常常碰到这种情况,2个子控件的事件冲突导致滑动没实用了.滑动反应非常慢,点击没用了,要划非常多次才移动一点点等等.或许我们第一反应就是百度,google去搜索下答案 ...

  8. Android 事件分发机制具体解释

    很多其它内容请參照我的个人网站: http://stackvoid.com/ 网上非常多关于Android事件分发机制的解释,大多数描写叙述的都不够清晰,没有吧来龙去脉搞清晰,本文将带你从Touch事 ...

  9. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

随机推荐

  1. log4net按时间日期,文件大小和个数生成日志文件

    从启动模板生成的基于ABP的应用默认使用的log4net日志框架,当然你也可以使用其他的日志框架. ABP默认的log4net.config配置文件配置的很简单,将所有的日志都写到了一个txt文件中, ...

  2. dojo Provider(script、xhr、iframe)源码解析

    总体结构 dojo/request/script.dojo/request/xhr.dojo/request/iframe这三者是dojo提供的provider.dojo将内部的所有provider构 ...

  3. 浅谈 SOLID 原则的具体使用

    SOLID 是面向对象设计5大重要原则的首字母缩写,当我们设计类和模块时,遵守 SOLID 原则可以让软件更加健壮和稳定.那么,什么是 SOLID 原则呢?本篇文章我将谈谈 SOLID 原则在软件开发 ...

  4. Java8的新特性以及与C#的比较

    函数式接口 VS 委托 在C中,可以使用函数指针来存储函数的入口,从而使得函数可以像变量一样赋值.传递和存储,使得函数的调用变得十分灵活,是实现函数回调的基础.然而函数指针不存在函数的签名信息,甚至可 ...

  5. Ajax跨域访问XML数据的另一种方式——使用YQL查询语句

    XML数据默认是不能在客户端通过Ajax跨域请求读取的,一般的做法是在服务器上写一个简单的代理程序,将远程XML的数据先读到本地服务器,然后客户端再从本地服务器通过Ajax来请求.由于我们不能对数据源 ...

  6. [翻译]AKKA笔记 - ACTOR MESSAGING - REQUEST AND RESPONSE -3

    上次我们看Actor消息机制,我们看到开火-忘记型消息发出(意思是我们只要发个消息给Actor但是不期望有响应). 技术上来讲, 我们发消息给Actors就是要它的副作用. 这就是这么设计的.除了不响 ...

  7. 小议map排序问题

    map有序无序?如果说有序, 这个顺序是怎么定义的? 安装put的先后顺序吗? 还是被put元素的内容呢? 经观察,应该是后者,跟put先后顺序无关, 跟内部实现有关(可能是hash排序的, 非大小排 ...

  8. vs2013中的“任务列表”菜单

    以前在java项目中经常用到todo. 现在vs2013也完美支持了. 首先,对于目前不确定而尚需完善的代码,在前面加 //TODO:by who --注释文字,比如: //TODO:lhl--类目I ...

  9. Redis Geo: Redis新增位置查询功能

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/144.html 移动互联网增进了人与人之间的联系,其中基于位置信息的服务( ...

  10. js笔记——理解js中的call及apply

    call及apply在js里经常碰得到,但一直感觉很陌生,不能熟练使用.怎样才能熟练应用呢? 为什么存在call和apply? 在javascript OOP中,我们经常会这样定义: function ...