原文链接: https://www.jianshu.com/p/38015afcdb58

参考资料:

  • Android事件分发机制详解:史上最全面、最易懂
  • android中的事件传递和处理机制
  • 用一张图告诉你Android中的事件传递机制
  • 要点提炼|开发艺术之View

1. 基础认知

(1)事件分发的对象是什么?

答:Touch事件; 包括点击,长按,滑动等相关事件;比如ACTION_DOWNACTION_MOVEACTION_UP;

(2)事件分发的本质是什么?

答:事件传递的过程;

(3)事件分发(传递)的顺序?事件消费(处理)的顺序 ?

  • 事件分发顺序:Activity ->( PhoneWindow -> DecorView -> )ViewGroup -> View (从外到内);
  • 事件消费顺序:View -> ViewGroup ->( DecorView -> PhoneWindow) -> Activity (从内到外);

View事件分发机制_第1张图片

2. 源码分析

2.1 Activity 事件分发

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

    //源码分析:Activity.dispatchTouchEvent()    public boolean dispatchTouchEvent(MotionEvent ev) {            // 一般事件列开始都是DOWN事件 = 按下事件,故此处基本是true            if (ev.getAction() == MotionEvent.ACTION_DOWN) {               onUserInteraction();//仅了解,实现屏保功能            }            // ->>分析2            if (getWindow().superDispatchTouchEvent(ev)){                return true;                // 若getWindow().superDispatchTouchEvent(ev)的返回true                // 则Activity.dispatchTouchEvent()就返回true,则方法结束。即 :该点击事件停止往下传递 & 事件传递过程结束                // 否则:继续往下调用Activity.onTouchEvent            }            // ->>分析4            return onTouchEvent(ev);        }/**  * 分析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消费时  */  public boolean onTouchEvent(MotionEvent event) {        // ->> 分析5        if (mWindow.shouldCloseOnTouch(this, event)) {            finish();            return true;//点击事件发生在Window边界外        }        return false;//点击事件发生在Window边界内    }
View事件分发机制_第2张图片

2.2 ViewGroup 事件分发

/**  * 源码分析: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;  }   // 回到调用原处
View事件分发机制_第3张图片

2.3 View 事件分发

/**  * 源码分析: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)
/ *  * 源码分析: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;      }  
View事件分发机制_第4张图片

onTouch()onTouchEvent()onClick()的关系?

  • onTouch返回true ,dispatchTouchEvent直接返回true;
  • onTouch返回false -> onTouchEvent -> performClick -> onClick();
  • 涉及到俩个监听器:OnTouchListener(抽象)和OnClickListener(具体)

3 三个回调方法

View事件分发机制_第5张图片

三个回调方法关系的伪码(也是Activity、ViewGroup、View事件分发机制源码的简化版源码)

boolean dispatchTouchEvent(MotionEvent event){    if(this instanceof View ){        if(mOnTouchListener.onTouch(this, event))             return ture;        return onTouchEvent(event); //View的dispatchTouchEvent返回值和onTouchEvent一致    }else if(this instanceof ViewGroup ){    //ViewGroup和Activity的dispatchTouchEvent返回值取决于下级View的dispatchTouchEvent方法和当前View的onTouchEvent方法        if(!onInterceptTouchEvent(event)){            if (child.dispatchTouchEvent(event))                return true;        }        return onTouchEvent(event);    } else if(this instanceof Activity ){        if (child.dispatchTouchEvent(event))             return true;        return onTouchEvent(event);    }}

4 事件分发机制 流程图

View事件分发机制_第6张图片

View事件分发机制_第7张图片

(1)理解事件分发机制的一个通俗例子:

 有个公司有总经理、项目经理、程序员三人;甲方把项目先交给总经理,然后总经理把项目交给项目经理,项目经理把项目交给程序员处理(事件分发)。如果程序员能完成这个项目,在完成项目后就向项目经理报告,项目经理向总经理报告,总经理向甲方报告(dispatchTouchEvent逐层向上返回true)。如果程序员不能完成这个项目,就把这个项目交给上级项目经理处理,经理处理如果也完成不了,就交给总经理处理,总经理自己无论能否完成都需要向甲方报告。(事件消费)

(2)事件分发机制的一些结论:

  • 一般一个事件序列只被一个View消费;
  • 当onInterceptTouchEvent()方法返回一个true,不再被调用;
  • 一个View一旦开始处理事件,如果它不消费ACTION_DOWN事件(onTouchEvent返回false),后续事件都不会分发给该View处理,并将事件重新交给它的父元素处理,即父元素的onTouchEvent会被调用;
  • 只有ViewGroup有onIntercept()方法,View没有;
  • 只要View的clickable或longClickable有一个为true,View的onTouchEvent默认消费(返回true);

5 .举例

View事件分发机制_第8张图片

ViewGroupA.java

public class ViewGroupA extends LinearLayout {    public ViewGroupA(Context context) {        super(context);    }    public ViewGroupA(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    public ViewGroupA(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.d("DavidHuang------>","ViewGroupA # dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.d("DavidHuang------>","ViewGroupA # onInterceptTouchEvent");        return super.onInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d("DavidHuang------>","ViewGroupA # onTouchEvent");        return super.onTouchEvent(event);    }}

ViewGroupB.java

public class ViewGroupB extends LinearLayout {    public ViewGroupB(Context context) {        super(context);    }    public ViewGroupB(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    public ViewGroupB(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.d("DavidHuang------>","ViewGroupB # dispatchTouchEvent");        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.d("DavidHuang------>","ViewGroupB # onInterceptTouchEvent");        return super.onInterceptTouchEvent(ev);//        return true;    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d("DavidHuang------>","ViewGroupB # onTouchEvent");//        return true;        return super.onTouchEvent(event);    }}

ViewC.java

public class ViewC extends View {    public ViewC(Context context) {        super(context);    }    public ViewC(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);    }    public ViewC(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        Log.d("DavidHuang------>","ViewC # dispatchTouchEvent");        return super.dispatchTouchEvent(event);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d("DavidHuang------>","ViewC # onTouchEvent");//        return false;        return super.onTouchEvent(event);    }

activity_main.java

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/my_layout"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:focusableInTouchMode="true"    android:orientation="vertical">    <com.example.davidhuang.dispatchtouchevent.ViewGroupA        android:layout_width="match_parent"        android:layout_height="match_parent"        android:gravity="center"        android:background="#ff0033">        <com.example.davidhuang.dispatchtouchevent.ViewGroupB            android:layout_width="200dp"            android:layout_height="200dp"            android:gravity="center"            android:background="#336699">            <com.example.davidhuang.dispatchtouchevent.ViewC                android:layout_width="100dp"                android:layout_height="100dp"                android:clickable="true"                android:background="#ffff00"/>        com.example.davidhuang.dispatchtouchevent.ViewGroupB>    com.example.davidhuang.dispatchtouchevent.ViewGroupA>LinearLayout>

(1) 默认情况

//ACTION_DOWNViewGroupA # dispatchTouchEventViewGroupA # onInterceptTouchEventViewGroupB # dispatchTouchEventViewGroupB # onInterceptTouchEventViewC # dispatchTouchEventViewC # onTouchEvent//ACTION_UPViewGroupA # dispatchTouchEventViewGroupA # onInterceptTouchEventViewGroupB # dispatchTouchEventViewGroupB # onInterceptTouchEventViewC # dispatchTouchEventViewC # onTouchEvent

 ACTION_DOWN事件从ViewGroupA传递到ViewC,ViewC消费了这个事件;后续事件也交给ViewC处理;

(2)ViewC不处理

 修改ViewC代码如下:

    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d("DavidHuang------>","ViewC # onTouchEvent");        return false;//        return super.onTouchEvent(event);    }
//ACTION_DOWNViewGroupA # dispatchTouchEventViewGroupA # onInterceptTouchEventViewGroupB # dispatchTouchEventViewGroupB # onInterceptTouchEventViewC # dispatchTouchEventViewC # onTouchEventViewGroupB # onTouchEventViewGroupA # onTouchEvent

 既然ViewC不处理事件,事件向上交付给上级ViewGroupB处理;ViewGroupB默认是不处理事件的,又交给ViewGroupB的上级ViewGroupA处理;ViewGroupA默认也不处理,交给Activity处理。

 既然ABC都不处理ACTION_DOWN事件,所以后续事件不会被ABC接收,故没有任何输出。

(3)ViewGroupB处理事件

 修改ViewGroupB代码如下:

    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d("DavidHuang------>","ViewGroupB # onTouchEvent");        return true;//表示当前View处理事件//        return super.onTouchEvent(event);    }
//ACTION_DOWNViewGroupA # dispatchTouchEventViewGroupA # onInterceptTouchEventViewGroupB # dispatchTouchEventViewGroupB # onInterceptTouchEventViewC # dispatchTouchEventViewC # onTouchEventViewGroupB # onTouchEvent//ACTION_UPViewGroupA # dispatchTouchEventViewGroupA # onInterceptTouchEventViewGroupB # dispatchTouchEventViewGroupB # onTouchEvent

 既然ACTION_DOWN是ViewGroupB处理的,后续事件ACTION_UP直接传递给ViewGroupB处理,不会传递给ViewC,也不会再调用ViewGroupB#onInterceptTouchEvent方法。

(4)ViewGroupB拦截自己处理事件

 修改ViewGroupB代码如下:

    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.d("DavidHuang------>","ViewGroupB # onInterceptTouchEvent");//        return super.onInterceptTouchEvent(ev);        return true;    }
//ACTION_DOWNViewGroupA # dispatchTouchEventViewGroupA # onInterceptTouchEventViewGroupB # dispatchTouchEventViewGroupB # onInterceptTouchEventViewGroupB # onTouchEvent//ACTION_UPViewGroupA # dispatchTouchEventViewGroupA # onInterceptTouchEventViewGroupB # dispatchTouchEventViewGroupB # onTouchEvent

 ViewGroupB拦截自己处理事件ACTION_DOWN,因此ViewGroupB # onInterceptTouchEvent返回true;同一事件序列中,当onInterceptTouchEvent()方法返回一个true,该方法不再被调用;所以虽然ACTION_UP是直接传递给ViewGroupB#onTouchEvent处理的,但并不会调用onInterceptTouchEvent方法。

更多相关文章

  1. 第七章 Android 动画机制与使用技巧
  2. Android ViewPager多页面滑动切换以及单页卡内添加事件
  3. Android消息处理机制
  4. Android中的文本框,图片以及点击事件的设置
  5. Android中的Binder机制一(实名Binder)
  6. Android的IPC机制Binder的各个部分
  7. Android消息处理机制(二)
  8. Android事件拦截机制
  9. Android系统的Binder机制之四——系统Service

随机推荐

  1. 这里有一份BAT大厂Android面试超详细知识
  2. Android 源码编译AIDL 使用实例讲解及And
  3. Android 打造炫目的圆形菜单 秒秒钟高仿
  4. Android中多媒体文件、文档以及各类文件
  5. Android——查询通话记录和删除记录
  6. Android 超简单的录制屏幕视频及生成GIF
  7. Android耗电优化实践 (二)- 检测错误的UI绘
  8. 安卓热点话题总结性剖析:《Android在多屏
  9. android应用中去android市场去评分的功能
  10. Android Map Api 使用和开发(1) 添加地图