Android事件分发机制全面解析

事件分发机制

事件分发机制的两个阶段:

  • 分发:事件从父视图往子视图分发,被拦截后不再传递,进入回溯阶段
  • 回溯:事件从子视图往父视图回溯,被消费后不再回溯

关键方法:

  • ViewGroup.dispatchTouchEvent 往子视图分发事件
  • ViewGroup.onInterceptTouchEvent 返回 true 表示拦截分发事件,不再传递,进入当前视图 onTouchEvent
  • View.dispatchTouchEvent 默认事件分发,调用 onTouchEvent
  • View.onTouchEvent 通常重载此方法处理事件,返回 true 表示消费事件,不再传递,返回 false 往上回溯
  • ViewParent.requestDisallowInterceptTouchEvent(true) 可以确保事件分发到子视图前不被拦截

假设视图层次为 A.B.C.D,事件分发回溯默认过程为:

A.dispatchTouchEvent
 B.dispatchTouchEvent
  C.dispatchTouchEvent
   D.dispatchTouchEvent
   D.onTouchEvent
  C.onTouchEvent
 B.onTouchEvent
A.onTouchEvent

假设 B 拦截了事件:

A.dispatchTouchEvent
 B.dispatchTouchEvent -> B.onInterceptTouchEvent
 B.onTouchEvent
A.onTouchEvent

假设 C.onTouchEvent 消费了事件:

A.dispatchTouchEvent
 B.dispatchTouchEvent
  C.dispatchTouchEvent
   D.dispatchTouchEvent
   D.onTouchEvent
  C.onTouchEvent

事件分发机制伪代码:

class Activity {
  fun dispatchTouchEvent(ev) {
    if (parent.dispatchTouchEvent(ev)) {
      return true
    }
    return onTouchEvent(ev)
  }
  fun onTouchEvent(ev):Boolean {...}
} 

class ViewGroup : View {
  fun dispatchTouchEvent(ev) {
    var handled = false
    if (!onInterceptTouchEvent(ev)) {
      handled = child.dispatchTouchEvent(ev)
    }
    return handled || super.dispatchTouchEvent(ev)
  }
  fun onInterceptTouchEvent(ev):Boolean {...}
  fun onTouchEvent(ev):Boolean {...}
} 

class View {
  fun dispatchTouchEvent(ev) {
    var result = false
    if (handleScrollBarDragging(ev)) {
      result = true
    }
    if (!result && mOnTouchListener.onTouch(ev)) {
      result = true
    }
    if (!result && onTouchEvent(ev)) {
      result = true
    }
    return result
  }
  fun onTouchEvent(ev):Boolean {...}
}

ViewGroup.dispatchTouchEvent 源码分析

1.开始:ACTION_DOWN 事件开始一个新的事件序列,清除之前触摸状态
2.拦截:

2.1. 非 ACTION_DOWN 事件如果当前没有子视图消费事件,表示事件序列已被拦截
2.2. 事件未被拦截且子视图未申请禁止拦截时,再通过 onInterceptTouchEvent 尝试拦截事件

3.分发:如果事件未被拦截也未被取消,就遍历子视图分发事件,并寻找当前事件的触摸目标

3.1. 在触摸目标链表中找到了可以消费当前事件的视图触摸目标 -> 将其标记为当前触摸目标,延迟到步骤4分发事件给它
3.2. 一个不在触摸目标链表中的视图消费了事件 -> 将其标记为当前触摸目标,并设置为触摸目标链表表头
3.3. 未找到消费当前事件的视图,但触摸目标链表不为空 -> 将触摸目标链表末端标记为当前触摸目标

4.分发:触摸目标链表不为空,则遍历触摸目标链尝试传递事件或取消触摸目标(事件被拦截)
5.回溯:触摸目标链表为空(当前没有子视图消耗事件序列),则将事件转发给基类 dispatchTouchEvent 处理
注:触摸目标(ViewGourp.TouchTarget) 描述一个被触摸的子视图和它捕获的指针ids

public boolean dispatchTouchEvent(MotionEvent ev) {
  // 省略代码 ...
  boolean handled = false;
  if (onFilterTouchEventForSecurity(ev)) {

    if (actionMasked == MotionEvent.ACTION_DOWN) {
      // 1. `ACTION_DOWN` 事件开始一个新的事件序列,清除之前触摸状态 ...
    }
    // 省略代码 ...
    final boolean intercepted;
    // 2. 拦截
    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
      final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
      if (!disallowIntercept) {
        // 2.2. 事件未被拦截且子视图未申请禁止拦截时,再通过 onInterceptTouchEvent 尝试拦截事件
        intercepted = onInterceptTouchEvent(ev);
        // 省略代码 ...
      } else {
        intercepted = false;
      }
    } else {
      // 2.1. 非 `ACTION_DOWN` 事件如果当前没有子视图消费事件,表示事件序列已被拦截
      intercepted = true;
    }
    // 省略代码 ...
    if (!canceled && !intercepted) {
      // 省略代码 ...
          // 3. 分发:如果事件未被拦截也未被取消,就遍历子视图分发事件,并寻找当前事件的触摸目标
          for (int i = childrenCount - 1; i >= 0; i--) {
            // 省略代码 ...
            newTouchTarget = getTouchTarget(child);
            if (newTouchTarget != null) {
              // 3.1. 在触摸目标链表中找到了可以消费当前事件的视图触摸目标 -> 将其标记为当前触摸目标,延迟到步骤4分发事件给它
              // 省略代码 ...
              break;
            }
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
              // 省略代码 ...
              // 3.2. 一个不在触摸目标链表中的视图消费了事件 -> 将其标记为当前触摸目标,并设置为触摸目标链表表头
              newTouchTarget = addTouchTarget(child, idBitsToAssign);
              alreadyDispatchedToNewTouchTarget = true;
              break;
            }
            // 省略代码 ...
          }

        if (newTouchTarget == null && mFirstTouchTarget != null) {
          // 3.3. 未找到消费当前事件的视图,但触摸目标链表不为空 -> 将触摸目标链表末端标记为当前触摸目标
          newTouchTarget = mFirstTouchTarget;
          while (newTouchTarget.next != null) {
            newTouchTarget = newTouchTarget.next;
          }
          newTouchTarget.pointerIdBits |= idBitsToAssign;
        }
      // 省略代码 ...
    }

    // Dispatch to touch targets.
    if (mFirstTouchTarget == null) {
      // 5. 回溯:触摸目标链表为空(当前没有子视图消耗事件序列),则将事件转发给基类 dispatchTouchEvent 处理
      handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
    } else {
      // 省略代码 ...
      // 4. 分发:触摸目标链表不为空,则遍历触摸目标链尝试传递事件或取消触摸目标(事件被拦截)
      TouchTarget target = mFirstTouchTarget;
      while (target != null) {
        final TouchTarget next = target.next;
        // 省略代码 ...
          if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
            handled = true;
          }
        // 省略代码 ...
        target = next;
      }
    }
    // 省略代码 ...
  }
  // 省略代码 ...
  return handled;
}

View.dispatchTouchEvent 和 View.onTouchEvent 源码分析

  • 滚动条消费鼠标事件
  • OnTouchListener 消费触摸事件
  • onTouchEvent 消费触摸事件

TouchDelegate 消费触摸事件

public boolean dispatchTouchEvent(MotionEvent event) {
  // 省略代码 ...
  boolean result = false;

  // 省略代码 ...
  if (onFilterTouchEventForSecurity(event)) {
    // 滚动条消费鼠标事件
    if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
      result = true;
    }
    // OnTouchListener 消费触摸事件
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {
      result = true;
    }
    // View默认的事件处理逻辑,事件可能在其中被设置的 TouchDelegate 消费
    if (!result && onTouchEvent(event)) {
      result = true;
    }
  }
  // 省略代码 ...
  return result;
} 

public boolean onTouchEvent(MotionEvent event) {
  // 省略代码 ...
  if (mTouchDelegate != null) {
    // TouchDelegate 消费触摸事件
    if (mTouchDelegate.onTouchEvent(event)) {
      return true;
    }
  }
  // 省略代码 ...
  return false;
}

以上就是Android事件分发机制全面解析的详细内容,更多关于Android事件分发机制的资料请关注我们其它相关文章!

时间: 2021-03-25

android事件分发机制的实现原理

android中的事件处理,以及解决滑动冲突问题都离不开事件分发机制,android中的事件流,即MotionEvent都会经历一个从分发,拦截到处理的一个过程.即dispatchTouchEvent(),onInterceptEvent()到onTouchEvent()的一个过程,在dispatchTouchEvent()负责了事件的分发过程,在dispatchTouchEvent()中会调用onInterceptEvent()与onTouchEvent(),如果onInterceptEven

如何自己实现Android View Touch事件分发流程

Android Touch事件分发是Android UI中的重要内容,Touch事件从驱动层向上,经过InputManagerService,WindowManagerService,ViewRootImpl,Window,到达DecorView,经View树分发,最终被消费. 本文尝试通过对其中View部分的事件分发,也是与日常开发联系最紧密的部分,进行重写.说是重写,其实是对Android该部分源码进行大幅精简而不失要点,且能够独立运行,以一窥其全貌,而不陷入到源码繁杂的细节中. 以下类均为

Android View 事件分发机制详解

Android开发,触控无处不在.对于一些 不咋看源码的同学来说,多少对这块都会有一些疑惑.View事件的分发机制,不仅在做业务需求中会碰到这些问题,在一些面试笔试题中也常有人问,可谓是老生常谈了.我以前也看过很多人写的这方面的文章,不是说的太啰嗦就是太模糊,还有一些在细节上写的也有争议,故再次重新整理一下这块内容,十分钟让你搞明白View事件的分发机制. 说白了这些触控的事件分发机制就是弄清楚三个方法,dispatchTouchEvent(),OnInterceptTouchEvent(),o

Android从源码的角度彻底理解事件分发机制的解析(上)

其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识.也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用ImageView?等等--对于这些问题,我并没有给出非常详细的回答,因为我知道如果想要彻底搞明白这些问题,掌握Android事件分发机

Android View的事件分发详解

1.前言 近两天学习了一下view的事件分发,把自己的理解总结了一遍,只表达了自己认为需要明白的地方,毕竟是菜鸟一枚,不对的地方还请大神们多指教! 2.三个方法 public boolean dispatchTouchEvent(MotionEvent ev) 用于事件的分发,返回结果受以下两个方法的影响,表示是否消耗了事件. public boolean onInterceptTouchEvent(MotionEvent ev) 事件是否被拦截,返回true表示拦截,false表示不拦截 pu

Android从源码的角度彻底理解事件分发机制的解析(下)

记得在前面的文章中,我带大家一起从源码的角度分析了Android中View的事件分发机制,相信阅读过的朋友对View的事件分发已经有比较深刻的理解了. 还未阅读过的朋友,请先参考Android从源码的角度彻底理解事件分发机制的解析. 那么今天我们将继续上次未完成的话题,从源码的角度分析ViewGroup的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子VewGroup,是And

解析Android点击事件分发机制

开头说说初衷 网上关于点击事件分发的文章一搜一大堆,标题一看,不是"30分钟让你弄明白XXX"就是"这是讲解XXX最好的文章",满怀憧憬与信心,忍不住兴奋的点进去一看,发现不是代码就全是图,我基本上看完了所有相关的文章,结果硬是看了三个小时也没搞懂.所以最后还是决定自己去试一试,看一看点击事件分发到底是怎么个流程,我写的肯定不会比其他文章好多少,但是呢,带着一个初学者的心,去分析这个东西,自己能弄明白的同时,也让想学习这个的人看了之后有些许收获,那就足够了. 运行的

Android View的事件分发机制

一.Android View框架提供了3个对事件的主要操作概念. 1.事件的分发机制,dispatchTouchEvent.主要是parent根据触摸事件的产生位置,以及child是否愿意负责处理该系列事件等状态,向其child分发事件的机制. 2.事件的拦截机制,onInterceptTouchEvent.主要是parent根据它内部的状态.或者child的状态,来把事件拦截下来,阻止其进一步传递到child的机制. 3.事件的处理机制,onTouchEvent.主要是事件序列的接受者(可以是

Android事件分发机制的详解

Android事件分发机制 我们只考虑最重要的四个触摸事件,即:DOWN,MOVE,UP和CANCEL.一个手势(gesture)是一个事件列,以一个DOWN事件开始(当用户触摸屏幕时产生),后跟0个或多个MOVE事件(当用户四处移动手指时产生),最后跟一个单独的UP或CANCEL事件(当用户手指离开屏幕或者系统告诉你手势(gesture)由于其他原因结束时产生).当我们说到"手势剩余部分"时指的是手势后续的MOVE事件和最后的UP或CANCEL事件. 在这里我也不考虑多点触摸手势(我

javascript 中事件冒泡和事件捕获机制的详解

javascript 中事件冒泡和事件捕获机制的详解 二者作用:描述事件触发时序问题 事件捕获:从document到触发事件的那个节点,即自上而下的去触发事件---由外到内 事件冒泡:自下而上的去触发事件---由内到外 绑定事件方法的第三个参数,就是控制事件触发顺序是否为事件捕获 true,事件捕获:false,事件冒泡 一般默认false,即事件冒泡 Jquery的e.stopPropagation会阻止冒泡,意思就是到DOM为止,祖先级的事件就不要触发了 下面是我尝试的例子: <!DOCTY

Android事件分发机制(下) View的事件处理

综述 在上篇文章Android中的事件分发机制(上)--ViewGroup的事件分发中,对ViewGroup的事件分发进行了详细的分析.在文章的最后ViewGroup的dispatchTouchEvent方法调用dispatchTransformedTouchEvent方法成功将事件传递给ViewGroup的子View.并交由子View进行处理.那么现在就来分析一下子View接收到事件以后是如何处理的. View的事件处理 对于这里描述的View,它是ViewGroup的父类,并不包含任何的子元

Android事件分发机制(上) ViewGroup的事件分发

综述 Android中的事件分发机制也就是View与ViewGroup的对事件的分发与处理.在ViewGroup的内部包含了许多View,而ViewGroup继承自View,所以ViewGroup本身也是一个View.对于事件可以通过ViewGroup下发到它的子View并交由子View进行处理,而ViewGroup本身也能够对事件做出处理.下面就来详细分析一下ViewGroup对时间的分发处理. MotionEvent 当手指接触到屏幕以后,所产生的一系列的事件中,都是由以下三种事件类型组成.

Android View 绘制机制的详解

View 绘制机制一. View 树的绘图流程 当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android framework 处理.绘制是从根节点开始,对布局树进行 measure 和 draw.整个 View 树的绘图流程在ViewRoot.java类的performTraversals()函数展开,该函数所做 的工作可简单概况为是否需要重新计算视图大小(measure).是否需要重新安置视图的位置(layout).以及是否需要重绘(draw),流程图如下: Vie