Android Touch 事件的分发和消费机制

         最近再看自定义控件的知识,查阅了很多资料,也有很多收获。对于自定义控件,Android  Touch 事件的分发和消费机制也是必须去了解的。
  • Touch 事件的传递方式以及相关方法
      首先我们先看一个Window的View层级图,当一个触摸事件发生时, Activity 的 dispatchTouchEvent(MotionEvent ev) 方法会以 隧道方式 (从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递) 将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。我们暂且不对该图细究,只需要知道 当一个触摸事件发生的时候,会按照Activity -> Window -> View的顺序依次往下传递。


       其次我们需要明确Android与Touch事件相关的三个重要的方法,分别是dispatchTouchEvent(MotionEven   ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev),很多博客都用到一张表我们来看一下。

Touch事件相关方法

功能

Activity

ViewGroup

View

public boolean dispatchTouchEvent(MotionEvent ev)

事件分发

Yes

Yes

Yes

public boolean onInterceptTouchEvent(MotionEvent ev)

事件拦截

No

Yes

No

public boolean onTouchEvent(MotionEvent ev)

事件响应

Yes

Yes

Yes

从上表中可以得出结论:
(1)Activity 具有事件分发和事件响应的功能,而不具备事件拦截的功能。
(2)ViewGroup 具有事件分发、事件拦截、事件响应的功能。
(3)View  具有事件分发和事件响应的功能,而不具备事件拦截的功能。
1、事件分发
public boolean dispatchTouchEvent(MotionEvent ev);

      Android中调用这个方法,用来传递和分发触摸事件,此方法返回值为布尔类型的值,通过返回true或false,来决定触摸事件是否向下传递。此外可以在子类重写dispatchTouchEvent方法,通过返回true或false来控制事件的分发和消费。(着重关注View和ViewGroup的事件分发的功能,下同)View 中事件的分发和ViewGroup事件的分发是有区别的,也就是两个类中的dispatchTouchEvent方法内容不一样。如果对此不太了解的可以参考郭霖大神的两边博客 Android事件分发机制完全解析,带你从源码的角度彻底理解(上)Android事件分发机制完全解析,带你从源码的角度彻底理解(下)这两篇博客对view事件分发和viewGroup事件分发介绍的非常详细。

2、事件的拦截
public boolean onInterceptTouchEvent(MotionEvent ev)
      该方法是在事件分发的 dispatchTouchEvent 方法内部进行调用。此方法返回值也是布尔类型,

通过返回true或false,来判断在触摸事件传递过程中,是否拦截某个事件。从上表中可以看到,只有ViewGroup具有事件拦截的功能,ViewGroup源码中

 onInterceptTouchEvent(MotionEvent ev)方法默认返回false,表示对所有的事件都不拦截。不过可以在ViewGroup的子类当中重写此方法,通过返回ture或false来判断触摸事件的拦截与否。
3、事件的响应
public boolean onTouchEvent(MotionEvent ev)
      用来处理传递过来的触摸事件,查看ViewGroup源码,并没有发现onTouchEvent(MotionEvent ev),其实ViewGroup继承自View,可以在View源码中查看到相关方法。返回值为布尔类型,表示 是否消费当前事件,也就是是否响应当前事件。如果返回true,表示会响应和消费当前事件;如果为false,表示不会响应该触摸事件。

  • Touch事件
   分析完三个重要的方法,我们看一下常见的touch事件:
  • Down:一次触摸事件的第一个MotionEvent对象,即手指初次接触屏幕。
  • Up:通常为一次触摸事件的最后一个MotionEvent对象,即手指离开屏幕。
  • Move:通常多次发生在一次触摸事件之中。表示触摸点发生了移动,我们通常把手指放到屏幕上,实际也会触发该事件,因为人手总是在轻微抖动的。
  • Cancel:常用于取消某个触摸事件,一般是由程序逻辑来指定该事件,用于取消某次触摸事件。
  • OutSide:当触摸点发生在响应事件的View之外时,传递的事件,通常由程序逻辑来指定。
     其中比较重要的三个事件为Down、Up和Move,一次完整的触摸事件可以由一个ACTION_DOWN、零个或一个或多个ACTION_MOVE、一个ACTION_UP组成。Android默认首先处理ACTION_DOWNS即按下事件,通过Android事件的分发机制,找到需要消费触摸事件的目标View后,后续的Move和Up事件,都由此控件进行响应处理。
  • Touch 案例
先看一张图

       分别自定义两个布局FatherLayout和ChildLayout都继承RelativeLayout,再自定义一个ChildButtonView继承自Button,各个类中分别重写上图中定义的方法,并返回相应的布尔类型的值。为了观察方便,制作一张表格,如下:
  • 情况1

dispatchTouchEvent

onInterceptTouchEvent

onTouchEvent

FatherLayout

return 

super.dispatchTouchEvent(ev);

return

 super.onInterceptTouchEvent(ev);

 return 
super.onTouchEvent(event);

ChildLayout

return 

super.dispatchTouchEvent(ev);

return 

super.onInterceptTouchEvent(ev);

 return 
super.onTouchEvent(event);

ChildButtonView

return 

super.dispatchTouchEvent(ev);

    return
super.onTouchEvent(event);


   点击ChildButtonView按钮,Log日志如下图所示:

   

 连同连同Log日志用一张图来说明上述ACTION_DOWN触摸事件分发流程,如下:


情况1小结:

  1. 从Log日志可以看出,Touch事件先处理ACTION_DOWN事件,用于定位目标View,之后的move和up事件,都会在此view中处理。
  2. dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent都是调用父类方法得到的返回值,其中super.onInterceptTouchEvent(ev)默认返回false即不会拦截触摸事件。
  3. 因为 ChildButtonView继承自Button,重写的onTouchEvent方法中调用super.onTouchEvent(event)方法,默认接收并消费ACTION_DOWN事件,并返回true。即定位此View为目标View,后续的Move和Up事件,也会传到这里进行事件的处理。 
  • 情况2



  • dispatchTouchEvent

    onInterceptTouchEvent

    onTouchEvent

    FatherLayout

    return super.dispatchTouchEvent(ev);

    return super.onInterceptTouchEvent(ev);

     return super.onTouchEvent(event);

    ChildLayout

    return super.dispatchTouchEvent(ev);

    return super.onInterceptTouchEvent(ev);

     return super.onTouchEvent(event);

    ChildButtonView

    return super.dispatchTouchEvent(ev);

        return false

点击ChildButtonView按钮,Log日志如下图所示:

连同Log日志用一张图来说明上述ACTION_DOWN触摸事件分发流程,如下:

情况2小结:
  1. 从Log日志可以看出,Touch事件仍旧是先处理ACTION_DOWN事件。
  2. ChildButtonView 重写的onTouchEvent方法中返回值为false,表示ChildButtonView 不会处理并消费该ACTION_DOWN事件
  3. 因为FatherLayout、ChildLayout重写的onTouchEvent方法中调用super.onTouchEvent(event)方法,因为相对布局默认不会消费ACTION_DOWN事件,所以会返回一个false,ACTION_DOWN事件此时会如紫色的箭头那样标示的一样继续向上传递,一直传到Activity中的OntouchEvent方法中。
  • 情况3

    • dispatchTouchEvent

      onInterceptTouchEvent

      onTouchEvent

      FatherLayout

      return

      super.dispatchTouchEvent(ev);

      return

       super.onInterceptTouchEvent(ev);

       return true;

      ChildLayout

      return 

      super.dispatchTouchEvent(ev);

      return 

      super.onInterceptTouchEvent(ev);

       return    super.onTouchEvent(event);

      ChildButtonView

      return 

      super.dispatchTouchEvent(ev);

          return false
点击ChildButtonView按钮,Log日志如下图所示:

 连同连同Log日志用一张图来说明上述ACTION_DOWN触摸事件分发流程,如下:

情况3小结:
  1. FatherLyout中OntouchEvent方法中,让其返回ture,表示有FatherLayout接收并消费ACTION_DOWN事件。后续的Move和up事件也会在此View中处理。
  • 情况4

dispatchTouchEvent

onInterceptTouchEvent

onTouchEvent

FatherLayout

return super.dispatchTouchEvent(ev);

return super.onInterceptTouchEvent(ev);

 return true;

ChildLayout

return super.dispatchTouchEvent(ev);

return super.onInterceptTouchEvent(ev);

    return   true;

ChildButtonView

return super.dispatchTouchEvent(ev);

    return false
点击ChildButtonView按钮,Log日志如下图所示:


 连同连同Log日志用一张图来说明上述ACTION_DOWN触摸事件分发流程,如下:

情况1、2、3、4小结:
通过综合情况1、2、3、4,如果在重写的onTouchEvent方法中,返回true,那么当前View会默认接受并消费该ACTION_DOWN事件,并且不会再上传。后续的Move和Up事件也会在此View中处理。
  • 情况5(主要讨论OnInterceptTouchEvent方法中的返回值,此时不在讨论onTouchEvent方法中的返回值)

dispatchTouchEvent

onInterceptTouchEvent

onTouchEvent

FatherLayout

return super.dispatchTouchEvent(ev);

return super.onInterceptTouchEvent(ev);

 return true;

ChildLayout

return super.dispatchTouchEvent(ev);

return true;

    return   true;

ChildButtonView

return super.dispatchTouchEvent(ev);

    return false
点击ChildButtonView按钮,Log日志如下图所示:


 连同连同Log日志用一张图来说明上述ACTION_DOWN触摸事件分发流程,如下:

情况5小结:
  1. ChildLayout中重写的OnInteceptTouchEvent方法,返回值为true,表示会将该ACTION_DOWN拦截,不在向下进行传递,并且会将该ACTION_DOWN事件,交由自己调用onTouchEvent方法进行判断处理。
  2. 如果将FatherLayout中的OnInterceptTouchEvent方法返回值改为true,与情况5类似,不在进行详细分析。
  • 情况6(主要讨论dispatchTouchEvent方法中的返回值,此时不在讨论onInterceptTouchEvent和onTouchEvent方法中的返回值)

dispatchTouchEvent

onInterceptTouchEvent

onTouchEvent

FatherLayout

return super.dispatchTouchEvent(ev);

return super.onInterceptTouchEvent(ev);

 return true;

ChildLayout

return super.dispatchTouchEvent(ev);

return super.onInterceptTouchEvent(ev);

    return   true;

ChildButtonView

return ture;

    return false
点击ChildButtonView按钮,Log日志如下图所示:

 连同连同Log日志用一张图来说明上述ACTION_DOWN触摸事件分发流程,如下:

情况6小结:
  1. 如果在ChildButtonView重写的dispatchTouchEvent方法中,直接返回false,那么不会调用onTouchEvent方法。同样的在FatherLayout和ChildLayout中既不会调用onTouchEvent方法,也不会调用onInterceptTouchEvent方法。
至此基本所有情况,基本探讨完毕,不过还有一些特殊情况,后边的博客会继续探讨。由于本人水平有限,文中难免会有错误指出,如能指出,不胜感激!

猜你喜欢

转载自blog.csdn.net/qingxindai/article/details/77422197
今日推荐