事件分发机制

一般情况下,事件列都是从用户按下(ACTION_DOWN)的那一刻产生的
即当一个点击事件(MotionEvent )产生后,系统需把这个事件传递给一个具体的 View 去处理

事件分发的对象:点击事件(Touch事件)

事件分发的本质
将点击事件(MotionEvent)传递到某个具体的View & 处理的整个过程
即:事件传递的过程 = 分发过程

事件分发的顺序
谨记:Activity -> ViewGroup -> View
即:1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View

事件分发过程由哪些方法协作完成
dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()

dispatchTouchEvent() : 负责事件分发的,
调用时机:事件传递给当前View,该方法就会被调用
onTouchEvent(): 处理此事件,返回值是否消耗此事件
调用时机:在dispatchTouchEvent()内部调用
onInterceptTouchEvent():分发过程中是否拦截事件,只有ViewGroup()有
调用时机:在ViewGroup的dispatchTouchEvent()内部调用

Activity的事件分发机制
当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发
Activity.dispatchTouchEvent() ->getWindow().superDispatchTouchEvent() –>true ->mDecor.superDispatchTouchEvent(event)(即ViewGroup的dispatchTouchEvent()),即将事件传递到ViewGroup去处理
–>false ->onTouchEvent(ev)(即事件未被Activity下任何一个View接受/处理,应用场景:处理发生在Window边界外的触摸事件,如Dialog类型的activity点击外侧弹框消失)

ViewGroup事件的分发机制
从上面Activity事件分发机制可知,ViewGroup事件分发机制从dispatchTouchEvent()开始
dispatchTouchEvent(MotionEvent ev) ->onInterceptTouchEvent(ev)(询问是否拦截事件) –>true ->不允许事件继续向子View传递 ->调用ViewGroup父类dispatchTouchEvent(ev)
接着会执行ViewGroup的onTouch() ->> onTouchEvent()(通过setOnClickListener为ViewGroup注册1个点击事件) ->> performClick() ->> onClick(),即自己处理该事件,事件不会往下传递 –false ->允许事件继续向子View传递->遍历ViewGroup中的所有子View,找到被点击的相应子View -> 调用子View的dispatchTouchEvent(ev)
即实现了事件从ViewGroup到View的传递

View事件的分发机制
从上面ViewGroup事件分发机制知道,View事件分发机制从dispatchTouchEvent()开始
boolean dispatchTouchEvent(MotionEvent event) -> View.onTouch(this,event) –>true ->事件被消费,不再继续往下传递 ->dispatchTouchEvent()返回true(事件不再往下传递,不调用onClick())

                                   -->false ->事件无被消费,继续往下传递 -> onTouchEvent() ->performClick() ->onClick()(手动回调setOnClickListener()为控件注册点击事件)

View事件源码:
public boolean dispatchTouchEvent(MotionEvent event) {

    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    } 
    return onTouchEvent(event);  

}
/**

  • 条件3:mOnTouchListener.onTouch(this, event)
  • 说明:即 回调控件注册Touch事件时的onTouch();需手动复写设置,具体如下(以按钮Button为例)
    */

button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {

        return false;  
    }  
});

// 若在onTouch()返回true,就会让上述三个条件全部成立,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
// 若在onTouch()返回false,就会使得上述三个条件不全部成立,从而使得View.dispatchTouchEvent()中跳出If,执行onTouchEvent(event)

View的事件优先级,可以参考view源码
优先级onTouch() > onTouchEvent() – >onClick()
重点:onTouch()和onTouchEvent()的区别
相同点:该2个方法都是在View.dispatchTouchEvent()中调用
onTouchEvent():控制事件分发
onTouch():处理触摸事件
onTouchEvent()和onTouch()方法优先级及控制关系
①如果onTouch()方法返回值是true(事件被消费)时,则onTouchEvent()方法将不会被执行;
②只有当onTouch()方法返回值是false(事件未被消费,向下传递)时,onTouchEvent方法才被执行。

performClick()在onTouchEvent(ev)中的ACTION_UP判断中

由此可见,给View设置监听OnTouchListener时,重写的onTouch()方法,其优先级比onTouchEvent()要高,假如onTouch方法返回false,会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。
可以看出,平时我们使用的OnClickListener,其优先级最低,即处于事件传递的尾端

小总结:dispatchTouchEvent返回true,即当前事件被消费(即事件已被View/ViewGroup接收&处理),后续事件停止分发,逐层往上返回(若无上层,则结束),后续事件会继续分发到该View

三者关系:
伪代码
/**

  • 点击事件产生后
    */
    // 步骤1:调用dispatchTouchEvent()
    public boolean dispatchTouchEvent(MotionEvent ev) {

    boolean consume = false; //代表 是否会消费事件

    // 步骤2:判断是否拦截事件
    if (onInterceptTouchEvent(ev)) {
    // a. 若拦截,则将该事件交给当前View进行处理
    // 即调用onTouchEvent ()方法去处理点击事件
    consume = onTouchEvent (ev) ;

    } else {

    // b. 若不拦截,则将该事件传递到下层
    // 即 下层元素的dispatchTouchEvent()就会被调用,重复上述过程
    // 直到点击事件被最终处理为止
    consume = child.dispatchTouchEvent (ev) ;
    }

    // 步骤3:最终返回通知 该事件是否被消费(接收 & 处理)
    return consume;

}

后续事件是指后面的move,up

场景分析:
场景一、默认
事件传递情况:
1、从上往下调用dispatchTouchEvent()
Activity A –> ViewGroup B –>View C
2、从下往上调用onTouchEvent()
View C –> ViewGroup B –> Activity A

场景二、拦截DOWN事件
假如ViewGroup B希望处理该点击事件,即ViewGroup B复写onInterceptTouchEvent()返回true,并且onTouchEvent()返回true(设置点击事件也是返回true了)
事件传递情况:
DOWN事件被传递给ViewGroup B的onInterceptTouchEvent(),该方法返回true,表示拦截该事件,即自己处理该事件(事件不再往下传递)
调用自身的onTouchEvent()处理事件(DOWN事件将不再往上传递给Activity A的onTouchEvent())
该事件列的其他事件(Move、Up)将直接传递给ViewGroup B的onTouchEvent()

场景三、拦截DOWN的后续事件
场景描述:ViewGroup B 无拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件
实例讲解:
1、在后续到来的move事件,ViewGroup B的onInterceptTouchEvent()返回true拦截该move事件,但该事件并没有传递给ViewGroup B;这个move事件将会被系统变为一个Cancel事件
传递给View C的onTouchEvent()
2、后续又来了一个MOVE事件,该MOVE事件才会直接传递给ViewGroup B 的onTouchEvent()
注:后续事件将直接传递给ViewGroup B 的onTouchEvent()处理,而不会再传递给ViewGroup B 的onInterceptTouchEvent(),因该方法一旦返回一次true,就再也不会被调用了
3、View C再也不会收到该事件列产生的后续事件

请记住:接收了ACTION_DOWN事件的函数不一定能收到后续事件(ACTION_MOVE、ACTION_UP)

若 ViewGroup 拦截了一个半路的事件(如MOVE),该事件将会被系统变成一个CANCEL事件 & 传递给之前处理该事件的子View;