Android 事件分发梳理

作者: Jimmy_gjf | 来源:发表于2018-03-05 17:49 被阅读69次

前言

本文适合对触摸事件有粗略了解的新手梳理逻辑使用,资深玩家请绕路通行。

内容整理自《Android开发艺术探索》,对书中内容摘要概括,未提及源码分析部分。事件分发图解参考自wangkuiwu' Homepage

本文区分ViewGroup与View,下文中使用的View均代表无子元素的单个View,非ViewGroup。

流程梳理

首先,我们要明确在事件分发中涉及到的重要的三个方法:

  1. [分发事件] public boolean dispatchTouchEvent (MotionEvent event)

    如果事件能够传递给当前View(或ViewGroup),那么此方法一定会被调用。返回结果受当前View(或ViewGroup)的onTouchEvent和下级View(或ViewGroup)的dispatchTouchEvent方法的影响,表示是否继续分发当前事件。

  2. [拦截事件] public boolean onInterceptTouchEvent (MotionEvent event)

    在dispatchTouchEvent方法内部调用,用来判断是否拦截这个事件,如果当前ViewGroup拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。

  3. [处理事件] public boolean onTouchEvent (MotionEvent event)
    在dispatchTouchEvent方法内部调用,用来处理点击事件。返回结果表示是否消耗当前事件。如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。

可以用伪代码形象的表示这三个方法之间的关系:

public boolean dispatchTouchEvent(MotionEvent event) ( 
      boolean consume = false;
      
      if (onInterceptTouchEvent(event)) {
          consume = onTouchEvent(event);
     } else {
          consume = child.dispatchTouchEvent(event);
     }
            
       return consume;
  }

同一个事件序列是指从手指触摸屏幕开始,到手指离开屏幕为止,这个过程中产生的一系列事件。

在View中包含以下两个方法:

public boolean dispatchTouchEvent(MotionEvent ev); 
public boolean onTouchEvent(MotionEvent ev);

在ViewGroup中包含以下三个方法:

public boolean dispatchTouchEvent(MotionEvent ev); 
public boolean onInterceptTouchEvent(MotionEvent ev); 
public boolean onTouchEvent(MotionEvent ev);

在Activity中包含以下两个方法:

public boolean dispatchTouchEvent(MotionEvent ev); 
public boolean onTouchEvent(MotionEvent ev);

Activity事件分发

触摸屏幕后,点击事件的分发顺序是Activity - window(PhoneWindow) - 顶层ViewGroup(DecorView) - ... - View。Activity通过dispatchTouchEvent分发事件,如果子View中都未处理事件,那么事件将交给Activity的onTouchEvent方法进行处理。这部分暂时不重点展开,了解即可。

image.png

ViewGroup事件分发

屏幕快照 2018-03-05 下午4.26.56.png

点击事件达到ViewGroup后,调用ViewGroup的dispatchTouchEvent方法,然后:

  1. 如果ViewGroup拦截事件(即onInterceptTouchEvent返回true),则事件由ViewGroup处理。
    • ViewGroup设置了onTouchListener:
      调用onTouchListener的onTouch方法,如果onTouch消耗了事件(onTouch返回true),则onTouchEvent不会再被调用。如果onTouch未消耗事件,继续调用onTouchEvent方法。
    • ViewGroup未设置onTouchListener:
      调用onTouchEvent方法,如果设置了onClickListener,则onClick会被调用。
  2. 如果ViewGroup不拦截事件(即onInterceptTouchEvent返回false),则事件会传递给它所在的点击事件链上的子View(或ViewGroup),这时子View(或ViewGroup)的dispatchTouchEvent会被调用。到此为止,事件己经从ViewGroup传递给了下一层View(或ViewGroup),接下来的传递过程均如此循环,完成整个事件的分发。

如果ViewGroup拦截了事件,但是最终没有消耗事件,那么事件会被交给上一级的ViewGroup进行处理。

View事件分发

屏幕快照 2018-03-05 下午4.32.24.png

因为View(不包含ViewGroup)是单独的元素,它没有子元素因此无法向下传递事件,所以它只能选择是否自己处理事件。点击事件通过View的dispatchTouchEvent方法进行分发,然后:

  1. 如果View设置了onTouchListener:

    • 如果onTouchListener的onTouch方法返回true,那么onTouchEvent不会被调用。
    • 如果onTouchListener的onTouch方法返回false,那么onTouchEvent会被调用。
  2. 如果View未设置onTouchListener:

    • 调用onTouchEvent方法。

View中不包含onInterceptTouchEvent方法,onTouchListener的优先级高于onTouchEvent。

onTouchEvent方法的实现:

只要View的CLICKABLE和LONG_CLICKABLE有一个为true,那么它就会消耗这个事件,即onTouchEvent方法返回true,不管它是不是DISABLE的状态。反之,返回false,不消耗事件。

ACTION_UP事件发生时,会触发performClick方法,如果View设置了onClickListener,那么performClick方法内部会调用它的onClick方法。即onClick方法是在onTouchEvent中回调的。

View的LONG_CLICKABLE属性: 默认为false,可以通过setLongClickable和setOnLongClickListener改变LONG_CLICKABLE属性的值。

View的CLICKABLE属性: 与具体的View相关,比如Button默认是可点击的,所以CLICKABLE默认为true, TextView默认是不可点击的,所以CLICKABLE默认为false。可以通过setClickable和setOnClickListener改变CLICKABLE属性的值。

如果View最终没有消耗事件,那么事件会被交给上一级的ViewGroup进行处理。

事件分发图解

image
image
image
image
image

相关文章

网友评论

本文标题:Android 事件分发梳理

本文链接:https://www.haomeiwen.com/subject/okyaxftx.html