View的事件体系二(事件分发)

一. 传递规则

  1. MotionEvent 的分发主要由下面三个方法来完成:

    • public boolean dispatchTouchEvent(MotionEvent ev)

      对于一个根ViewGroup,点击时间产生后,会首先调用这个方法。它将根据onInterceptTouchEvent 的返回值,来决定是否对子View继续分发。

    • public boolean onInterceptTouchEvent(MotionEvent ev)

      dispatchTouchEvent中被调用,并获取返回值。

      • 如果返回 trueViewGroup将拦截当前事件,事件将由ViewGroup来处理,它的onTouchEvent将会被调用。
      • 如果返回 falseViewGroup会将当前时间传递给子View,接着子View的 dispatchTouchEvent 将会被调用,知道事件被最终处理。
    • public boolean onTouchEvent(MotionEvent ev)

      当时间分发到View时,如果View注册了OnTouchListener,那么该接口的onTouch方法会被回调,回调的结果将影响后面的处理过程:

      • 如果onTouch返回true:View的onTouchEvent被调用
      • 如果onTouch返回false:View的onTouchEvent不会被调用
  2. 当一个点击事件产生后,其传递过程如下:

    Activity -> Window -> View

    • 当View的onTouchEvent 返回false,那么它的父容器的onTouchEvent将会被调用,以此类推。
    • 如果所有的View都没有处理这个事件,那么最终会传递个Activity处理。
  3. 事件传递细则:
    1. 同一个事件序列,是指手指触摸屏幕的那一刻开始,到手指离开屏幕的那一刻结束。
      • 事件序列以down事件开始,以up事件结束。
    2. 正常情况下,一个事件只能被一个View拦截且消耗。
    3. 如果某个View拦截了事件,那么整个事件序列都只能由它来处理,它的onInterceptTouchEvent 也将不会再被调用。
    4. 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件,那么同一事件序列中的其它事件都不会交给它来处理,并且事件将交由它的父元素去处理,此时父元素的onTouchEvent会被调用。
      • 一旦决定要处理事件,就必须消耗掉事件,否则父元素将重新参与处理。
    5. 如果View不消耗除了down事件以外的其它事件,那么这个点击事件将会消失,此时父元素的onTouchEvent不会被调用。
      • 并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity管理。
    6. ViewGroup 默认不拦截事件。ViewGrouponInterceptTouchEvent默认实现返回false。
    7. View没有实现onInterceptTouchEvent方法,一旦有点击事件传递给它,它的onTouchListener 就会被调用。
    8. View的onTouchEvent默認返回true,即默认会消耗事件。除非View是不可点击 clickable == false, 而且不可长按 longClickable == false
      • longClickable默认一般都是false
      • 有些View的clickable默认为true,比如Button需要交互的控件。
      • 有些View的clickable默认为false,比如TextView这种无需交互的控件。
      • View的enable属性,不影响onTouchEvent的默认返回值。
    9. onClick会发生的前提:当前View是clickable,而且它收到了 down 和 up事件。
    10. 事件传递过程总是先传给父元素,再由父元素分发给子View。
      • 通过requestDisallowInterceptTouchEvent方法,可以在子元素中干预父元素的事件分发过程。但ACTION_DOWN事件除外。

二. 分发过程分析

  1. Activity对点击事件的分发源码:

    //Activity.java
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

    事件首先会交给Window进行分发。

    • 如果Window所包含的所有View都返回了false,事件将交回给Activity来处理,Activity的onTouchEvent 将会被调用。
    • 如果Window中有View处理了事件,getWindow().superDispatchTouchEvent(ev) 将会返回true,此时事件已经被处理,分发结束。
  2. Window处理事件
    接口Window的唯一实现类是PhoneWindowsuperDispatchTouchEvent的实现如下:

    public boolean superDispatchTouchEvent(MotionEvent event){
        return mDecor.superDispatchTouchEvent(event);
    }

    DecorView是Window中最顶层的View,我们通过setContentView设置的View是DecorView的一个子View。

    从这里开始,事件已经传递到顶级View了。

  3. 顶级View对点击事件的分发

    分发过程我们在上一个小节已经介绍过了,这里再总结一下。
    实际上分发过程类似于遍历一个View树:

    1. 每当分发到一个非叶子节点,就会调用dispatchTouchEvent:
      • 如果该节点的onInterceptTouchEvent返回true,表示拦截事件,则事件由该节点处理。
      • 如果该节点的onInterceptTouchEvent返回false,则将事件分发给其儿子节点。
    2. 当一个节点接收到事件分发时,可以选择是否自己来处理,通过onTouchEvent的返回值分别:

      • true:自己处理事件,分发结束
      • false:不处理事件,事件将继续分发

      此外,每个节点的onTouchEvent都有默认返回值:

      • 非叶节点或根,即ViewGroup,那么onTouchEvent默认返回false
      • 叶节点一般默认返回true。
      • 一些叶节点天生自带交互属性,比如Button,默认返回true。
    3. 如果所有的节点都没有处理事件,最终将交回给Activity处理。
  4. 非ViewGroup的View对点击事件的处理

    1. 判断onTouchListener是否被用户注入。如果已注入,判断onTouch的返回值:
      • onTouch返回true:onTouchEvent将不会被调用。

    2,如果onTouchListener为空,或者其onTouch返回false,onTouchEvent将会被调用。

    1. onTouchEvent被调用时:
      • 如果clickable或longClickable有一个为true,将消耗事件,然后返回true。
      • 当事件是ACTION_UP时,将触发performClick方法。如果View注入了OnClickListener,那么performClick 内部会调用它的onClick方法。

猜你喜欢

转载自blog.csdn.net/cangely/article/details/80088151