事件分发机制

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

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

关键方法:

  • 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事件分发机制的资料请关注程序员的世界其它相关文章!

Android事件分发机制全面解析的更多相关文章

  1. 详解Android的四大应用程序组件

    Android的一个核心特性就是一个应用程序可作为其他应用程序中的元素,可为其他应用程序提供数据。例如,如果程序需要......

  2. Android 代码规范大全

    前言虽然我们项目的代码时间并不长,也没经过太多人手,但代码的规范性依然堪忧,目前存在较多的比较自由的「代码规范」,这......

  3. Android 简单服务定位器模式实现

    依赖注入(Dependency Injection)和服务定位器(Service Locator)是实现控制反转(I......

  4. Android自定义相机聚焦和显示框

    本文实例为大家分享了Android自定义相机聚焦和显示框的具体代码,供大家参考,具体内容如下先看使用效果,白色圆框,......

  5. Android 中 WebView 的基本用法详解

    加载 URL (网络或者本地 assets 文件夹下的 html 文件)加载 html 代码Native 和 Jav......

  6. Android实现简单画图画板

    本文实例为大家分享了Android实现简单画图画板的具体代码,供大家参考,具体内容如下效果如图:布局文件:MainA......

  7. Android非异常情况下的Activity生命周期分析

    Activity非异常情况下的生命周期是指,用户正常参与UI交互的情况下,Activity所经过的生命周期的改变;一......

  8. Android事件分发机制三:事件分发工作流程

    前言 很高兴遇见你~ 本文是事件分发系列的第三篇。 在前两篇文章中,Android事件分发机制一:......

  9. 【Android初级】使用setContentView实现页面的转换效果(附源码)

    一提到Android中页面的切换,你是不是只想到了startActivity启动另一个Activity?其实在And......

  10. Android实现微信摇一摇功能

    本文实例为大家分享了Android实现微信摇一摇功能的具体代码,供大家参考,具体内容如下1、初始化界面设置摇一摇界面......

随机推荐

  1. C#异步和多线程以及Thread、ThreadPool、Task区别和使用方法

    本文的目的是为了让大家了解什么是异步?什么是多线程?如何实现多线程?对于当前C#当中三种实现多线程的方法如何实现和使......

  2. Jquery 格式化时间

    我们常常会通过datetime得到时间,但是网页前台往往会显示不同的时间如:2013-12-152013年12月23......

  3. Python初学者详细笔认知笔记

    首先编程语言有很多种,类别也比较多,和数学,外语都有很大联系,学编程外语三级以上水平更好一点。编程语言主要可以分为以......

  4. 浅谈JavaScript代码性能优化2

    一.减少判断层级 从下图代码中可以明显看出,同样的效果判断层级的减少可以优化性能二.减少作用域链查找层级 简单解释下......

  5. python 装饰器的基本使用

    知识点简单的装饰器带有参数的装饰器带有自定义参数的装饰器类装饰器装饰器嵌套@functools.wrap装饰器使用基......

  6. 用python制作个音乐下载器

    前言某个夜深人静的夜晚,我打开了自己的文件夹,发现了自己写了许多似乎很无聊的代码。于是乎,一个想法油然而生:“生活已......

  7. 解决python3 中的np.load编码问题

    由于在Python2 中的默认编码为ASCII,但是在Python3中的默认编码为UTF-8。问题:所以在使用np.......

  8. JAVA中JSONObject对象和Map对象之间的相互转换

    1.由json字符串转换成Map对象如json字符串:{"contend":[{"bi......

  9. 深入浅出Java线程池:使用篇

    前言很高兴遇见你~借助于很多强大的框架,现在我们已经很少直接去管理线程,框架的内部都会为我们自动维护一个线程池。例如......

  10. php的curl携带header请求头信息实现http访问的方法

    导读:curl请求时添加请求头信息可以模拟真人操作,不容易被当成是爬虫机器人(采集),从而可以绕过Incapsula......