Android 开发收集的一些东西AndroidAndroid

Android事件分发机制——两分钟看懂

2017-02-07  本文已影响1040人  程序猿的猫

事件分发机制相关的3个方法

  1. dispatchTouchEvent() 分发
  2. onInterceptTouchEvent() 拦截
  3. onTouchEvent() 消耗

ViewGroup有1.2.3
View只有1.3

ViewGroup里面的dispatchTouchEvent方法十分的复杂,下面是它的简化版源码:

public boolean dispatchTouchEvent(Motion e){     
    boolean result = false;     
    if(onInterceptTouchEvent(e)){
          //如果当前View截获事件,那么事件就会由当前View处理,即调用onTouchEvent()
          result = onTouchEvent(e);
     }else{
          //如果不截获那么交给其子View来分发
          result = child.dispatchTouchEvent(e);
          //如果子View不消耗,调用自己的onTouchEvent()
          if(result == false){
              result = onTouchEvent(e);
          }
     }     
    return result;
}

具体的用户操作示意图

图片右上方为布局示例
当用户点击途中View视图的时候,就会进行下面的流程

注:
本流程是在用户不重写dispatchTouchEvent方法的基础上的流程(一般也不需要修改)

并且省略了Activity层(Activity层在这里我们可以把它看成一个ViewGroup)
面试的时候我们只要讲出这个流程就够了

Android事件分发机制图解.png

在用户点击View(这个View指代具体的Button,TextView等等)之后

  1. 首先会传递给ViewGroup1(可能是LinearLayout等各种布局或者容器),ViewGroup1的dispatchTouchEvent方法调用onInterceptTouchEvent,在没有重写或者返回false的情况下会调用ViewGroup2的dispatchTouchEvent方法,如果返回值是true,表示我(ViewGroup1)需要拦截这个点击事件,接下来直接调用他自己的onTouchEvent方法,不会再向下传递。
  2. ViewGroup2和ViewGroup1一样。
  3. 如果前两层都没有拦截事件,那么就会走到View层的dispatchTouchEvent,接着调用onTouchEvent。
    onTouchEvent在没有重写或者返回false的情况下表示没有消耗这个点击事件会继续向上层传递给ViewGroup2,如果返回true,表明view层消耗了这个请求,直接结束整个流程。
  4. ViewGroup2的onTouchEvent也是和View层的一样,通过返回值判断是否消耗事件。
  5. ViewGroup1和ViewGroup2一样。

总结:

整个事件分发过程就像是由dispatchTouchEvent控制的递归,流程下行方向需要考虑onInterceptTouchEvent方法是否拦截请求,上行方向需要考虑onTouchEvent是否消耗事件(当然重写dispatchTouchEvent,直接返回true也可以消耗事件,这里考虑的话容易混乱)。


下面为拓展内容,有时间再继续看

上面我们从总体上理解了事件分发机制,但是实际开发当中只有在自定义View中我们会重写OnTouchEvent方法更多的我们是通过setOnClickListener里面的OnClick或者setOnTouchListener里面的onTouch的方式来处理点击事件的,那么OnTouch OnTouchEvent OnClick之间是什么关系呢?他们是怎样分工怎样相互协作的呢?

为了直观的解释问题,我简单的写了一个小的Demo,结合起来一起看方便理解。
Demo地址:https://github.com/LinLeyang/EventDispatch

首先界面上还是和上面一样的嵌套关系,如图所示


viewtu

其中的View都是自定义的控件,我们重写对应方法进行检验

首先第一轮什么都不做处理走正常的流程

第一轮 OnTouch onInterceptTouchEvent OnTouchEvent OnClick
ViewGroup1 FALSE FALSE SUPER NO
ViewGroup2 FALSE FALSE SUPER NO
View FALSE NONE SUPER NO

FALSE TRUE 是返回值
SUPER是返回父类方法
NONE是没有这个方法
NO是没有设置监听

点击TextView

正常流程调用,但是发现非常智能的一点就是发现没人消耗的Down事件,直接结束了。MOVE和UP都不处理了

第二轮 OnTouch onInterceptTouchEvent OnTouchEvent OnClick
ViewGroup1 FALSE FALSE SUPER HAVE
ViewGroup2 FALSE FALSE SUPER HAVE
View FALSE NONE SUPER HAVE
点击TextView
点击ViewGroup2

点击的最底层消耗事件。

第三轮 OnTouch onInterceptTouchEvent OnTouchEvent OnClick
ViewGroup1 FALSE FALSE SUPER HAVE
ViewGroup2 FALSE FALSE FALSE HAVE
View FALSE NONE SUPER HAVE
点击ViewGroup2

发现一个问题就是一个View的OnTouchEvent可能开始能调用到,但是后面的一系列事件可能与他无缘。

第四轮 OnTouch onInterceptTouchEvent OnTouchEvent OnClick
ViewGroup1 FALSE FALSE SUPER NO
ViewGroup2 FALSE FALSE SUPER NO
View FALSE NONE DOWN_TRUE NO
点击TextView

TextView只消耗DOWN事件,发现后面的事件也默然被他消耗了,这里体现了事件处理问题的惯性。

第五轮 OnTouch onInterceptTouchEvent OnTouchEvent OnClick
ViewGroup1 FALSE DOWN_FALSE SUPER HAVE
ViewGroup2 FALSE FALSE SUPER HAVE
View FALSE NONE SUPER HAVE

DOWN_FALSE的意思是默认TRUE只有对DOWN事件展示FALSE


点击TextView

我们第一次看到了3这个action状态,就是说CANCLE状态,虽然ViewGroup1拦截了事件,但是因为之前TextView消耗了DOWN事件,所以ViewGroup1并没有拦截到第二个事件而是向TextView发送了一个CANCLE事件(意思是告诉它由于一些原因后面的事件不用你监听了),VIewGroup1从第三个MOVE事件开始拦截的。并且可以发现最终也没有调用OnClick事件,因为没有DOWN,算不上点击。

总结:

  1. 事件会通过dispatchTouchEvent由Activity->ViewGroup->View
    在没有ViewGroup拦截onInterceptTouchEvent的情况下,逐层分发事件
    直到View层,然后反过来寻找消耗者,消耗者可以通过OnTouch > OnTouchEvent OnClick消耗事件,事件一旦被消耗就返回true结束这个事件传递。
    Tips:OnTouchEvent必须要调用super方法才能调用到OnClick

  2. 针对Down事件,Down事件相当于事件的起点,如果一个view不消耗Down事件,那么其他事件他也无法消耗,反之当一个View消耗了DOWN事件的时候,后续的一系列事件默认都会由它消耗,除非上层的ViewGroup拦截,拦截后会发出CANCLE事件发给消耗DOWN事件的VIew。

上一篇下一篇

猜你喜欢

热点阅读