前言

  • Android事件分发机制是Android开发者必须了解的基础
  • 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全、思路不清晰、无源码分析、简单问题复杂化等等
  • 今天,我将全面总结Android的事件分发机制,我能保证这是市面上的最全面、最清晰、最易懂的
  1. 本文秉着“结论先行、详细分析在后”的原则,即先让大家感性认识,再通过理性分析从而理解问题;
  2. 所以,请各位读者先记住结论,再往下继续看分析;
  • 文章较长,阅读需要较长时间,建议收藏等充足时间再进行阅读

目录

Android事件分发机制 详解攻略,您值得拥有_第1张图片


1. 基础认知

1.1 事件分发的对象是谁?

答:点击事件(Touch事件)

  • 定义
    当用户触摸屏幕时(ViewViewGroup派生的控件),将产生点击事件(Touch事件)

Touch事件的相关细节(发生触摸的位置、时间等)被封装成MotionEvent对象

  • 事件类型(4种)
事件类型 具体动作
MotionEvent.ACTION_DOWN 按下View(所有事件的开始)
MotionEvent.ACTION_UP 抬起View(与DOWN对应)
MotionEvent.ACTION_MOVE 滑动View
MotionEvent.ACTION_CANCEL 结束事件(非人为原因)
  • 特别说明:事件列

从手指接触屏幕 至 手指离开屏幕,这个过程产生的一系列事件

注:一般情况下,事件列都是以DOWN事件开始、UP事件结束,中间有无数的MOVE事件,如下图:
Android事件分发机制 详解攻略,您值得拥有_第2张图片

即当一个点击事件(MotionEvent )产生后,系统需把这个事件传递给一个具体的 View 去处理。

1.2 事件分发的本质

答:将点击事件(MotionEvent)传递到某个具体的View & 处理的整个过程

即 事件传递的过程 = 分发过程。

1.3 事件在哪些对象之间进行传递?

答:Activity、ViewGroup、View

  • AndroidUI界面由ActivityViewGroupView 及其派生类组成
    Android事件分发机制 详解攻略,您值得拥有_第3张图片

Android事件分发机制 详解攻略,您值得拥有_第4张图片

1.4 事件分发的顺序

即 事件传递的顺序:Activity -> ViewGroup -> View

即:1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View

Android事件分发机制 详解攻略,您值得拥有_第5张图片

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

Android事件分发机制 详解攻略,您值得拥有_第6张图片

下文会对这3个方法进行详细介绍

###1.6 总结
Android事件分发机制 详解攻略,您值得拥有_第7张图片

  • 至此,相信大家已经对 Android的事件分发有了感性的认知
  • 下面,我将详细介绍Android事件分发机制

2. 事件分发机制 源码分析

  • 请谨记:Android事件分发流程 = Activity -> ViewGroup -> View

即:1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View

Android事件分发机制 详解攻略,您值得拥有_第8张图片

  • 从上可知,要想充分理解Android分发机制,本质上是要理解:
    1. Activity对点击事件的分发机制
    2. ViewGroup对点击事件的分发机制
    3. View对点击事件的分发机制
  • 下面,我将通过源码,全面解析 事件分发机制

即按顺序讲解:Activity事件分发机制、ViewGroup事件分发机制、View事件分发机制

2.1 Activity的事件分发机制

当一个点击事件发生时,事件最先传到ActivitydispatchTouchEvent()进行事件分发

2.1.1 源码分析

/**  * 源码分析:Activity.dispatchTouchEvent()  */     public boolean dispatchTouchEvent(MotionEvent ev) {            // 一般事件列开始都是DOWN事件 = 按下事件,故此处基本是true            if (ev.getAction() == MotionEvent.ACTION_DOWN) {                onUserInteraction();                // ->>分析1            }            // ->>分析2            if (getWindow().superDispatchTouchEvent(ev)) {                return true;                // 若getWindow().superDispatchTouchEvent(ev)的返回true                // 则Activity.dispatchTouchEvent()就返回true,则方法结束。即 :该点击事件停止往下传递 & 事件传递过程结束                // 否则:继续往下调用Activity.onTouchEvent            }            // ->>分析4            return onTouchEvent(ev);        }/**  * 分析1:onUserInteraction()  * 作用:实现屏保功能  * 注:  *    a. 该方法为空方法  *    b. 当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法  */      public void onUserInteraction() {       }      // 回到最初的调用原处/**  * 分析2:getWindow().superDispatchTouchEvent(ev)  * 说明:  *     a. getWindow() = 获取Window类的对象  *     b. Window类是抽象类,其唯一实现类 = PhoneWindow类;即此处的Window类对象 = PhoneWindow类对象  *     c. Window类的superDispatchTouchEvent() = 1个抽象方法,由子类PhoneWindow类实现  */    @Override    public boolean superDispatchTouchEvent(MotionEvent event) {        return mDecor.superDispatchTouchEvent(event);        // mDecor = 顶层View(DecorView)的实例对象        // ->> 分析3    }/**  * 分析3:mDecor.superDispatchTouchEvent(event)  * 定义:属于顶层View(DecorView)  * 说明:  *     a. DecorView类是PhoneWindow类的一个内部类  *     b. DecorView继承自FrameLayout,是所有界面的父类  *     c. FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup  */    public boolean superDispatchTouchEvent(MotionEvent event) {        return super.dispatchTouchEvent(event);        // 调用父类的方法 = ViewGroup的dispatchTouchEvent()        // 即 将事件传递到ViewGroup去处理,详细请看ViewGroup的事件分发机制    }    // 回到最初的调用原处/**  * 分析4:Activity.onTouchEvent()  * 定义:属于顶层View(DecorView)  * 说明:  *     a. DecorView类是PhoneWindow类的一个内部类  *     b. DecorView继承自FrameLayout,是所有界面的父类  *     c. FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup  */  public boolean onTouchEvent(MotionEvent event) {        // 当一个点击事件未被Activity下任何一个View接收 / 处理时        // 应用场景:处理发生在Window边界外的触摸事件        // ->> 分析5        if (mWindow.shouldCloseOnTouch(this, event)) {            finish();            return true;        }                return false;        // 即 只有在点击事件在Window边界外才会返回true,一般情况都返回false,分析完毕    }/**  * 分析5:mWindow.shouldCloseOnTouch(this, event)  */    public boolean shouldCloseOnTouch(Context context, MotionEvent event) {    // 主要是对于处理边界外点击事件的判断:是否是DOWN事件,event的坐标是否在边界内等    if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN            && isOutOfBounds(context, event) && peekDecorView() != null) {        return true;    }    return false;    // 返回true:说明事件在边界外,即 消费事件    // 返回false:未消费(默认)}// 回到分析4调用原处

2.1.2 总结

  • 当一个点击事件发生时,从Activity的事件分发开始(Activity.dispatchTouchEvent()

Android事件分发机制 详解攻略,您值得拥有_第9张图片

  • 方法总结

Android事件分发机制 详解攻略,您值得拥有_第10张图片

那么,ViewGroupdispatchTouchEvent()什么时候返回true / false?请继续往下看ViewGroup事件的分发机制


2.2 ViewGroup事件的分发机制

从上面Activity事件分发机制可知,ViewGroup事件分发机制从dispatchTouchEvent()开始

2.2.1 源码分析

  1. Android 5.0后,ViewGroup.dispatchTouchEvent()的源码发生了变化(更加复杂),但原理相同;
  2. 本文为了让读者容易理解,故采用Android 5.0前的版本
/**  * 源码分析:ViewGroup.dispatchTouchEvent()  */     public boolean dispatchTouchEvent(MotionEvent ev) {     ... // 仅贴出关键代码        // 重点分析1:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件            if (disallowIntercept || !onInterceptTouchEvent(ev)) {              // 判断值1:disallowIntercept = 是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改            // 判断值2: !onInterceptTouchEvent(ev) = 对onInterceptTouchEvent()返回值取反                    // a. 若在onInterceptTouchEvent()中返回false(即不拦截事件),就会让第二个值为true,从而进入到条件判断的内部                    // b. 若在onInterceptTouchEvent()中返回true(即拦截事件),就会让第二个值为false,从而跳出了这个条件判断                    // c. 关于onInterceptTouchEvent() ->>分析1                ev.setAction(MotionEvent.ACTION_DOWN);                  final int scrolledXInt = (int) scrolledXFloat;                  final int scrolledYInt = (int) scrolledYFloat;                  final View[] children = mChildren;                  final int count = mChildrenCount;          // 重点分析2            // 通过for循环,遍历了当前ViewGroup下的所有子View            for (int i = count - 1; i >= 0; i--) {                  final View child = children[i];                  if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE                          || child.getAnimation() != null) {                      child.getHitRect(frame);                      // 判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View                    // 若是,则进入条件判断内部                    if (frame.contains(scrolledXInt, scrolledYInt)) {                          final float xc = scrolledXFloat - child.mLeft;                          final float yc = scrolledYFloat - child.mTop;                          ev.setLocation(xc, yc);                          child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;                          // 条件判断的内部调用了该View的dispatchTouchEvent()                        // 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面的View事件分发机制)                        if (child.dispatchTouchEvent(ev))  {                         mMotionTarget = child;                          return true;                         // 调用子View的dispatchTouchEvent后是有返回值的                        // 若该控件可点击,那么点击时,dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立                        // 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出                        // 即把ViewGroup的点击事件拦截掉                                }                              }                          }                      }                  }              }              boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||                      (action == MotionEvent.ACTION_CANCEL);              if (isUpOrCancel) {                  mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;              }              final View target = mMotionTarget;          // 重点分析3        // 若点击的是空白处(即无任何View接收事件) / 拦截事件(手动复写onInterceptTouchEvent(),从而让其返回true)        if (target == null) {              ev.setLocation(xf, yf);              if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {                  ev.setAction(MotionEvent.ACTION_CANCEL);                  mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;              }                          return super.dispatchTouchEvent(ev);            // 调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()            // 因此会执行ViewGroup的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick(),即自己处理该事件,事件不会往下传递(具体请参考View事件的分发机制中的View.dispatchTouchEvent())            // 此处需与上面区别:子View的dispatchTouchEvent()        }         ... }/**  * 分析1:ViewGroup.onInterceptTouchEvent()  * 作用:是否拦截事件  * 说明:  *     a. 返回true = 拦截,即事件停止往下传递(需手动设置,即复写onInterceptTouchEvent(),从而让其返回true)  *     b. 返回false = 不拦截(默认)  */  public boolean onInterceptTouchEvent(MotionEvent ev) {          return false;  }   // 回到调用原处

2.2.2 总结

  • 结论:Android事件分发总是先传递到ViewGroup、再传递到View
  • 过程:当点击了某个控件时

Android事件分发机制 详解攻略,您值得拥有_第11张图片

  • 核心方法总结

Android事件分发机制 详解攻略,您值得拥有_第12张图片

2.2.3 Demo讲解

  • 布局如下
    Android事件分发机制 详解攻略,您值得拥有_第13张图片

  • 测试代码

布局文件:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>    

核心代码:MainActivity.java

/**  * ViewGroup布局(myLayout)中有2个子View = 2个按钮  */public class MainActivity extends AppCompatActivity {    Button button1,button2;    ViewGroup myLayout;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        button1 = (Button)findViewById(R.id.button1);        button2 = (Button)findViewById(R.id.button2);        myLayout = (LinearLayout)findViewById(R.id.my_layout);        // 1.为ViewGroup布局设置监听事件        myLayout.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Log.d("TAG", "点击了ViewGroup");            }        });        // 2. 为按钮1设置监听事件        button1.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Log.d("TAG", "点击了button1");            }        });        // 3. 为按钮2设置监听事件        button2.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Log.d("TAG", "点击了button2");            }        });    }}
  • 结果测试
    Android事件分发机制 详解攻略,您值得拥有_第14张图片

从上面的测试结果发现:

  • 点击Button时,执行Button.onClick(),但ViewGroupLayout注册的onTouch()不会执行
  • 只有点击空白区域时,才会执行ViewGroupLayoutonTouch()
  • 结论:ButtononClick()将事件消费掉了,因此事件不会再继续向下传递。

2.3 View事件的分发机制

从上面ViewGroup事件分发机制知道,View事件分发机制从dispatchTouchEvent()开始

2.3.1 源码分析

/**  * 源码分析:View.dispatchTouchEvent()  */  public boolean dispatchTouchEvent(MotionEvent event) {          if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&                  mOnTouchListener.onTouch(this, event)) {              return true;          }         return onTouchEvent(event);    }  // 说明:只有以下3个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent()  //     1. mOnTouchListener != null  //     2. (mViewFlags & ENABLED_MASK) == ENABLED  //     3. mOnTouchListener.onTouch(this, event)  // 下面对这3个条件逐个分析/**  * 条件1:mOnTouchListener != null  * 说明:mOnTouchListener变量在View.setOnTouchListener()方法里赋值  */  public void setOnTouchListener(OnTouchListener l) {     mOnTouchListener = l;      // 即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)        } /**  * 条件2:(mViewFlags & ENABLED_MASK) == ENABLED  * 说明:  *     a. 该条件是判断当前点击的控件是否enable  *     b. 由于很多View默认enable,故该条件恒定为true  *//**  * 条件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)

接下来,我们继续看:**onTouchEvent(event)**的源码分析

  1. 详情请看注释
  2. Android 5.0View.onTouchEvent()源码发生了变化(更加复杂),但原理相同;
  3. 本文为了让读者更好理解,所以采用Android 5.0前的版本
/**  * 源码分析:View.onTouchEvent()  */  public boolean onTouchEvent(MotionEvent event) {      final int viewFlags = mViewFlags;      if ((viewFlags & ENABLED_MASK) == DISABLED) {                   return (((viewFlags & CLICKABLE) == CLICKABLE ||                  (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));      }      if (mTouchDelegate != null) {          if (mTouchDelegate.onTouchEvent(event)) {              return true;          }      }      // 若该控件可点击,则进入switch判断中    if (((viewFlags & CLICKABLE) == CLICKABLE ||              (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {                  switch (event.getAction()) {                     // a. 若当前的事件 = 抬起View(主要分析)                    case MotionEvent.ACTION_UP:                          boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;                              ...// 经过种种判断,此处省略                            // 执行performClick() ->>分析1                            performClick();                              break;                      // b. 若当前的事件 = 按下View                    case MotionEvent.ACTION_DOWN:                          if (mPendingCheckForTap == null) {                              mPendingCheckForTap = new CheckForTap();                          }                          mPrivateFlags |= PREPRESSED;                          mHasPerformedLongPress = false;                          postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());                          break;                      // c. 若当前的事件 = 结束事件(非人为原因)                    case MotionEvent.ACTION_CANCEL:                          mPrivateFlags &= ~PRESSED;                          refreshDrawableState();                          removeTapCallback();                          break;                    // d. 若当前的事件 = 滑动View                    case MotionEvent.ACTION_MOVE:                          final int x = (int) event.getX();                          final int y = (int) event.getY();                                  int slop = mTouchSlop;                          if ((x < 0 - slop) || (x >= getWidth() + slop) ||                                  (y < 0 - slop) || (y >= getHeight() + slop)) {                              // Outside button                              removeTapCallback();                              if ((mPrivateFlags & PRESSED) != 0) {                                  // Remove any future long press/tap checks                                  removeLongPressCallback();                                  // Need to switch from pressed to not pressed                                  mPrivateFlags &= ~PRESSED;                                  refreshDrawableState();                              }                          }                          break;                  }                  // 若该控件可点击,就一定返回true                return true;              }               // 若该控件不可点击,就一定返回false            return false;          }/**  * 分析1:performClick()  */      public boolean performClick() {          if (mOnClickListener != null) {              playSoundEffect(SoundEffectConstants.CLICK);              mOnClickListener.onClick(this);              return true;              // 只要我们通过setOnClickListener()为控件View注册1个点击事件            // 那么就会给mOnClickListener变量赋值(即不为空)            // 则会往下回调onClick() & performClick()返回true        }          return false;      }  

2.3.2 总结

  • 每当控件被点击时:

Android事件分发机制 详解攻略,您值得拥有_第15张图片

注:onTouch()的执行 先于 onClick()

  • 核心方法总结

Android事件分发机制 详解攻略,您值得拥有_第16张图片

2.3.3 Demo讲解

下面我将用Demo验证上述的结论

/**  * 结论验证1:在回调onTouch()里返回false  */   // 1. 通过OnTouchListener()复写onTouch(),从而手动设置返回false   button.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                System.out.println("执行了onTouch(), 动作是:" + event.getAction());                           return false;            }        });    // 2. 通过 OnClickListener()为控件设置点击事件,为mOnClickListener变量赋值(即不为空),从而往下回调onClick()    button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                System.out.println("执行了onClick()");            }        });/**  * 结论验证2:在回调onTouch()里返回true  */   // 1. 通过OnTouchListener()复写onTouch(),从而手动设置返回true   button.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                System.out.println("执行了onTouch(), 动作是:" + event.getAction());                           return true;            }        });    // 2. 通过 OnClickListener()为控件设置点击事件,为mOnClickListener变量赋值(即不为空)    // 但由于dispatchTouchEvent()返回true,即事件不再向下传递,故不调用onClick())    button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                System.out.println("执行了onClick()");            }                    });
  • 测试结果
    Android事件分发机制 详解攻略,您值得拥有_第17张图片

2.4 总结

Android事件分发机制 详解攻略,您值得拥有_第18张图片

若您已经看到此处,那么恭喜你,你已经能非常熟悉掌握Android的事件分发机制了

即:ActivityViewGroupView 的事件分发机制


3. 工作流程 总结

  • 在本节中,我将结合源码,梳理出1个事件分发的工作流程总结,具体如下:

Android事件分发机制 详解攻略,您值得拥有_第19张图片

左侧虚线:具备相关性 & 逐层返回

  • 以角色为核心的图解说明

Android事件分发机制 详解攻略,您值得拥有_第20张图片

  • 以方法为核心的图解说明

Android事件分发机制 详解攻略,您值得拥有_第21张图片


4. 核心方法总结

  • 已知事件分发过程的核心方法为:dispatchTouchEvent()onInterceptTouchEvent()onTouchEvent()
    Android事件分发机制 详解攻略,您值得拥有_第22张图片

  • 下面,我将结合总结的工作流程,再次详细讲解该3个方法

4.1 dispatchTouchEvent()

  • 简介

Android事件分发机制 详解攻略,您值得拥有_第23张图片

Android事件分发机制 详解攻略,您值得拥有_第24张图片

  • 返回情况说明

情况1:默认
Android事件分发机制 详解攻略,您值得拥有_第25张图片

Android事件分发机制 详解攻略,您值得拥有_第26张图片

情况2:返回true
Android事件分发机制 详解攻略,您值得拥有_第27张图片

Android事件分发机制 详解攻略,您值得拥有_第28张图片

情况3:返回false
Android事件分发机制 详解攻略,您值得拥有_第29张图片

Android事件分发机制 详解攻略,您值得拥有_第30张图片


4.2 onInterceptTouchEvent()

  • 简介

Android事件分发机制 详解攻略,您值得拥有_第31张图片

注:ActivityView都无该方法

Android事件分发机制 详解攻略,您值得拥有_第32张图片

  • 返回情况说明

情况1:true

Android事件分发机制 详解攻略,您值得拥有_第33张图片

Android事件分发机制 详解攻略,您值得拥有_第34张图片

情况2:false(默认)

Android事件分发机制 详解攻略,您值得拥有_第35张图片

Android事件分发机制 详解攻略,您值得拥有_第36张图片


4.3 onTouchEvent()

  • 简介

Android事件分发机制 详解攻略,您值得拥有_第37张图片

Android事件分发机制 详解攻略,您值得拥有_第38张图片

  • 返回情况说明

情况1:返回true

Android事件分发机制 详解攻略,您值得拥有_第39张图片

[外链图片转存失败(img-XbbYoeF5-1562040906999)(https://user-gold-cdn.xitu.io/2019/6/11/16b467e6cda884fb?w=1140&h=1296&f=png&s=75290)]

情况2:返回false(default)
Android事件分发机制 详解攻略,您值得拥有_第40张图片

Android事件分发机制 详解攻略,您值得拥有_第41张图片

4.4 三者关系

下面,我用一段伪代码来阐述上述3个方法的关系 & 事件传递规则

/**  * 点击事件产生后  */   // 步骤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;   }

5. 常见的事件分发场景

下面,我将通过实例说明常见的事件传递情况 & 流程

5.1 背景描述

  • 讨论的布局如下:

Android事件分发机制 详解攻略,您值得拥有_第42张图片

  • 情景
    1. 用户先触摸到屏幕上View C上的某个点(图中黄区)

    Action_DOWN事件在此处产生

    1. 用户移动手指
    2. 最后离开屏幕

5.2 一般的事件传递情况

一般的事件传递场景有:

  • 默认情况
  • 处理事件
  • 拦截DOWN事件
  • 拦截后续事件(MOVEUP

场景1:默认

  • 即不对控件里的方法(dispatchTouchEvent()onTouchEvent()onInterceptTouchEvent())进行重写 或 更改返回值
  • 那么调用的是这3个方法的默认实现:调用下层的方法 & 逐层返回
  • 事件传递情况:(呈U型)
    1. 从上往下调用dispatchTouchEvent()

    Activity A ->> ViewGroup B ->> View C

    1. 从下往上调用onTouchEvent()

    View C ->> ViewGroup B ->> Activity A

[外链图片转存失败(img-f2SDJbnD-1562040907003)(https://user-gold-cdn.xitu.io/2019/6/11/16b467e6f22ad97a?w=1140&h=1296&f=png&s=75697)]

注:虽然ViewGroup BonInterceptTouchEvent()对DOWN事件返回了false,但后续的事件(MOVE、UP)依然会传递给它的onInterceptTouchEvent()
这一点与onTouchEvent()的行为是不一样的:不再传递 & 接收该事件列的其他事件

场景2:处理事件

View C希望处理该点击事件,即:设置View C为可点击的(Clickable) 或 复写其onTouchEvent()返回true

最常见的:设置Button按钮来响应点击事件

事件传递情况:(如下图)

  • DOWN事件被传递给C的onTouchEvent方法,该方法返回true,表示处理该事件
  • 因为View C正在处理该事件,那么DOWN事件将不再往上传递给ViewGroup B 和 Activity AonTouchEvent()
  • 该事件列的其他事件(Move、Up)也将传递给View ConTouchEvent()

Android事件分发机制 详解攻略,您值得拥有_第43张图片

会逐层往dispatchTouchEvent() 返回,最终事件分发结束

场景3:拦截DOWN事件

假设ViewGroup B希望处理该点击事件,即ViewGroup B复写了onInterceptTouchEvent()返回trueonTouchEvent()返回true
事件传递情况:(如下图)

  • DOWN事件被传递给ViewGroup BonInterceptTouchEvent(),该方法返回true,表示拦截该事件,即自己处理该事件(事件不再往下传递)

  • 调用自身的onTouchEvent()处理事件(DOWN事件将不再往上传递给Activity AonTouchEvent()

  • 该事件列的其他事件(Move、Up)将直接传递给ViewGroup BonTouchEvent()

注:

  1. 该事件列的其他事件(Move、Up)将不会再传递给ViewGroup BonInterceptTouchEvent();因:该方法一旦返回一次true,就再也不会被调用
  2. 逐层往dispatchTouchEvent() 返回,最终事件分发结束

Android事件分发机制 详解攻略,您值得拥有_第44张图片

场景4:拦截DOWN的后续事件

结论

  • ViewGroup 拦截了一个半路的事件(如MOVE),该事件将会被系统变成一个CANCEL事件 & 传递给之前处理该事件的子View
  • 该事件不会再传递给ViewGrouponTouchEvent()
  • 只有再到来的事件才会传递到ViewGrouponTouchEvent()

场景描述
ViewGroup B 无拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件

DOWN事件传递到View ConTouchEvent(),返回了true

实例讲解

  • 在后续到来的MOVE事件,ViewGroup BonInterceptTouchEvent()返回true拦截该MOVE事件,但该事件并没有传递给ViewGroup B ;这个MOVE事件将会被系统变成一个CANCEL事件传递给View ConTouchEvent()
  • 后续又来了一个MOVE事件,该MOVE事件才会直接传递给ViewGroup BonTouchEvent()

后续事件将直接传递给ViewGroup BonTouchEvent()处理,而不会再传递给ViewGroup BonInterceptTouchEvent(),因该方法一旦返回一次true,就再也不会被调用了。

  • View C再也不会收到该事件列产生的后续事件

[外链图片转存失败(img-nW27KLdP-1562040907005)(https://user-gold-cdn.xitu.io/2019/6/11/16b467e71a4547a1?w=1240&h=1369&f=png&s=154789)]

至此,关于Android常见的事件传递情况 & 流程已经讲解完毕。


6. 额外知识

6.1 Touch事件的后续事件(MOVE、UP)层级传递

  • 若给控件注册了Touch事件,每次点击都会触发一系列action事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP等)
  • dispatchTouchEvent()事件分发时,只有前一个事件(如ACTION_DOWN)返回true,才会收到后一个事件(ACTION_MOVE和ACTION_UP)

即如果在执行ACTION_DOWN时返回false,后面一系列的ACTION_MOVE、ACTION_UP事件都不会执行

从上面对事件分发机制分析知:

  • dispatchTouchEvent()、 onTouchEvent() 消费事件、终结事件传递(返回true)
  • 而onInterceptTouchEvent 并不能消费事件,它相当于是一个分叉口起到分流导流的作用,对后续的ACTION_MOVE和ACTION_UP事件接收起到非常大的作用

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

这里给出ACTION_MOVE和ACTION_UP事件的传递结论

  • 结论1
    若对象(Activity、ViewGroup、View)的dispatchTouchEvent()分发事件后消费了事件(返回true),那么收到ACTION_DOWN的函数也能收到ACTION_MOVE和ACTION_UP

黑线:ACTION_DOWN事件传递方向
红线:ACTION_MOVE 、 ACTION_UP事件传递方向

Android事件分发机制 详解攻略,您值得拥有_第45张图片

  • 结论2
    若对象(Activity、ViewGroup、View)的onTouchEvent()处理了事件(返回true),那么ACTION_MOVE、ACTION_UP的事件从上往下传到该View后就不再往下传递,而是直接传给自己的onTouchEvent()& 结束本次事件传递过程。

黑线:ACTION_DOWN事件传递方向
红线:ACTION_MOVE、ACTION_UP事件传递方向

Android事件分发机制 详解攻略,您值得拥有_第46张图片

6.2 onTouch()和onTouchEvent()的区别

  • 该2个方法都是在View.dispatchTouchEvent()中调用
  • onTouch()优先于onTouchEvent执行;若手动复写在onTouch()中返回true(即 将事件消费掉),将不会再执行onTouchEvent()

注:若1个控件不可点击(即非enable),那么给它注册onTouch事件将永远得不到执行,具体原因看如下代码

// &&为短路与,即如果前面条件为false,将不再往下执行//  故:onTouch()能够得到执行需2个前提条件:     // 1. mOnTouchListener的值不能为空     // 2. 当前点击的控件必须是enable的mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&              mOnTouchListener.onTouch(this, event)// 对于该类控件,若需监听它的touch事件,就必须通过在该控件中重写onTouchEvent()来实现

7. 总结

  • 通过阅读本文,相信您已经可以全面了解Android事件分发机制

  • Android事件分发最相关的知识:自定义View系列文章
    自定义View基础 - 最易懂的自定义View原理系列(1)
    自定义View Measure过程 - 最易懂的自定义View原理系列(2)
    自定义View Layout过程 - 最易懂的自定义View原理系列(3)
    自定义View Draw过程- 最易懂的自定义View原理系列(4)

  • 接下来我将继续介绍与Android事件分发最相关的知识:自定义View,有兴趣可以继续关注Carson_Ho的安卓开发笔记


请帮顶 / 评论点赞!因为你们的赞同/鼓励是我写作的最大动力!


欢迎关注carson_ho的微信公众号

  • 名称:carson带你解析Android
  • 作用:通过碎片化时间(上下班、饭前饭后等)带你高效、系统地学习Android知识

具体请看文章:我想给你们介绍一个与众不同的Android微信公众号(福利回赠)

  • 扫一扫关注即可

Android事件分发机制 详解攻略,您值得拥有_第47张图片

Android事件分发机制 详解攻略,您值得拥有_第48张图片

更多相关文章

  1. Android受手机制造商青睐 是福是祸
  2. 解读Android LOG机制的实现
  3. android 广播机制
  4. Android中Handler Runnable与Thread的区别详解

随机推荐

  1. Android 挂断电话流程
  2. 禁止横屏和竖屏切换
  3. Android开发带图标的按钮
  4. Android 禁止应用在模拟器上运行
  5. 使用QT调用FFMPEG库部署到Android设备、
  6. android 在Fragment 中使用ormlite 数据
  7. Android(安卓)获取window状态栏和标题栏
  8. ImageView属性
  9. Android定位详解 兼容网络定位、GPS定位
  10. Android中startService基本使用方法概述