点击上方“蓝字”关注我们

1,touch 事件是如何从驱动层传递给

Framework 层的 InputManagerService;

2,WMS 是如何通过 ViewRooImple 将事件传递到目标窗口;

3,touch 事件到达 DecorView 后,是如何一步步传递到内部的子 View 中的。

其中与上层软件开发息息相关的就是第 3 条,也是本课时的重点关注内容。

注意:不同版本间的代码会有区别,本文是基于 Android-28 的源码上进行分析。

思路梳理

在深入分析事件分发源码之前,需要先弄清楚 2 个概念。

ViewGroup

ViewGroup 是一组 View 的组合,在其内部有可能包含多个子 View,当手指触摸屏幕上时,手指所在的区域既能在 ViewGroup 显示范围内,也可能在其内部 View 控件上。

因此它内部的事件分发的重心是处理当前 Group 和子 View 之间的逻辑关系:

1,当前 Group 是否需要拦截 touch 事件;

2,是否需要将 touch 事件继续分发给子 View;

3,如何将 touch 事件分发给子 View。

View

View 是一个单纯的控件,不能再被细分,内部也并不会存在子 View,所以它的事件分发的重点在于当前 View 如何去处理 touch 事件,并根据相应的手势逻辑进行一些列的效果展示(比如滑动,放大,点击,长按等)。

1,是否存在 TouchListener;

2,是否自己接收处理 touch 事件(主要逻辑在 onTouchEvent 方法中)。

事件分发核心 dispatchTouchEvent

整个 View 之间的事件分发,实质上就是一个大的递归函数,而这个递归函数就是 dispatchTouchEvent 方法。在这个递归的过程中会适时调用 onInterceptTouchEvent 来拦截事件,或者调用 onTouchEvent 方法来处理事件。

先从宏观角度,纵览整个 dispatch 的源码如下:

如代码中的注释,dispatch 主要分为 3 大步骤:

步骤 1:判断当前 ViewGroup 是否需要拦截此 touch 事件,如果拦截则此次 touch 事件不再会传递给子 View(或者以 CANCEL 的方式通知子 View)。

步骤 2:如果没有拦截,则将事件分发给子 View 继续处理,如果子 View 将此次事件捕获,则将 mFirstTouchTarget 赋值给捕获 touch 事件的 View。

步骤 3:根据 mFirstTouchTarget 重新分发事件。

接下来详细的看下每一个步骤:

步骤 1 的具体代码如下

图中红框标出了是否需要拦截的条件:

如果事件为 DOWN 事件,则调用 onInterceptTouchEvent 进行拦截判断;

或者 mFirstTouchTarget 不为 null,代表已经有子 View 捕获了这个事件,子 View 的 dispatchTouchEvent 返回 true 就是代表捕获 touch 事件。

如果在上面步骤 1 中,当前 ViewGroup 并没有对事件进行拦截,则执行步骤 2。

步骤 2 具体代码如下

仔细看上述的代码可以看出:

图中 ① 处表明事件主动分发的前提是事件为 DOWN 事件;

图中 ② 处遍历所有子 View;

图中 ③ 处判断事件坐标是否在子 View 坐标范围内,并且子 View 并没有处在动画状态;

图中 ④ 处调用 dispatchTransformedTouchEvent 方法将事件分发给子 View,如果子 View 捕获事件成功,则将 mFirstTouchTarget 赋值给子 View。

步骤 3 具体代码如下

步骤 3 有 2 个分支判断。

分支 1:

如果此时 mFirstTouchTarget 为 null,说明在上述的事件分发中并没有子 View对事件进行了捕获操作。这种情况下,直接调用 dispatchTransformedTouchEvent 方法,并传入 child 为 null,最终会调用 super.dispatchTouchEvent 方法。实际上最终会调用自身的 onTouchEvent 方法,进行处理 touch 事件。也就是说:如果没有子 View 捕获处理 touch 事件,ViewGroup 会通过自身的 onTouchEvent 方法进行处理。

分支 2:

mFirstTouchTarget 不为 null,说明在上面步骤 2 中有子 View 对 touch

事件进行了捕获,则直接将当前以及后续的事件交给 mFirstTouchTarget 指向的 View 进行处理。

事件分发流程代码演示

定义如下布局文件:

DownInterceptedGroup 和 CaptureTouchView 是两个自定义 View,它们的源码分别如下:

手指触摸 CaptureTouchView 并滑动一段距离后抬起,最终打印 log 如下:

上图中在 DOWN 事件中,DownInterceptGroup 的 onInterceptTouchEvent 被触发一次;然后在子 View CaptureTouchView 的 dispatchTouchEvent 中返回 true,代表它捕获消费了这个 DOWN 事件。这种情况下 CaptureTouchView 会被添加到父视图(DownInterceptGroup)中的 mFirstTouchTarget 中。因此后续的 MOVE 和 UP 事件都会经过 DownInterceptGroup 的 onInterceptTouchEvent 进行拦截判断。详细源码可以参考:CaptureTouchView.java

为什么 DOWN 事件特殊

所有 touch 事件都是从 DOWN 事件开始的,这是 DOWN 事件比较特殊的原因之一。另一个原因是 DOWN 事件的处理结果会直接影响后续 MOVE、UP 事件的逻辑。

在步骤 2 中,只有 DOWN 事件会传递给子 View 进行捕获判断,一旦子 View 捕获成功,后续的 MOVE 和 UP 事件是通过遍历 mFirstTouchTarget 链表,查找之前接受 ACTION_DOWN 的子 View,并将触摸事件分配给这些子 View。也就是说后续的 MOVE、UP 等事件的分发交给谁,取决于它们的起始事件 Down 是由谁捕获的。

mFirstTouchTarget 有什么作用

mFirstTouchTarget 的部分源码如下:

可以看出其实 mFirstTouchTarget 是一个 TouchTarget 类型的链表结构。而这个 TouchTarget 的作用就是用来记录捕获了 DOWN 事件的 View,具体保存在上图中的 child 变量。可是为什么是链表类型的结构呢?因为 Android 设备是支持多指操作的,每一个手指的 DOWN 事件都可以当做一个 TouchTarget 保存起来。在步骤 3 中判断如果 mFirstTouchTarget 不为 null,则再次将事件分发给相应的 TouchTarget。

容易被遗漏的 CANCEL 事件

在上面的步骤 3 中,继续向子 View 分发事件的代码中,有一段比较有趣的逻辑:

上图红框中表明已经有子 View 捕获了 touch 事件,但是蓝色框中的 intercepted boolean 变量又是 true。这种情况下,事件主导权会重新回到父视图 ViewGroup 中,并传递给子 View 的分发事件中传入一个 cancelChild == true。

看一下 dispatchTransformedTouchEvent 方法的部分源码如下:

因为之前传入参数 cancel 为 true,并且 child 不为 null,最终这个事件会被包装为一个 ACTION_CANCEL 事件传给 child。

什么情况下会触发这段逻辑呢?

总结一下就是:当父视图的 onInterceptTouchEvent 先返回 false,然后在子 View 的 dispatchTouchEvent 中返回 true(表示子 View 捕获事件),关键步骤就是在接下来的 MOVE 的过程中,父视图的 onInterceptTouchEvent 又返回 true,intercepted 被重新置为 true,此时上述逻辑就会被触发,子控件就会收到 ACTION_CANCEL 的 touch 事件。

实际上有个很经典的例子可以用来演示这种情况:

当在 Scrollview 中添加自定义 View 时,ScrollView 默认在 DOWN 事件中并不会进行拦截,事件会被传递给 ScrollView 内的子控件。只有当手指进行滑动并到达一定的距离之后,onInterceptTouchEvent 方法返回 true,并触发 ScrollView 的滚动效果。当 ScrollView 进行滚动的瞬间,内部的子 View 会接收到一个 CANCEL 事件,并丢失touch焦点。

比如以下代码:

CaptureTouchView 是一个自定义的 View,其源码如下:

CaptureTouchView 的 onTouchEvent 返回 true,表示它会将接收到的 touch 事件进行捕获消费。

上述代码执行后,当手指点击屏幕时 DOWN 事件会被传递给 CaptureTouchView,手指滑动屏幕将 ScrollView 上下滚动,刚开始 MOVE 事件还是由 CaptureTouchView 来消费处理,但是当 ScrollView 开始滚动时,CaptureTouchView 会接收一个 CANCEL 事件,并不再接收后续的 touch 事件。具体打印 log 如下:

因此,我们平时自定义View时,尤其是有可能被ScrollView或者ViewPager嵌套使用的控件,不要遗漏对CANCEL事件的处理,否则有可能引起UI显示异常。

总结

判断是否需要拦截 —> 主要是根据 onInterceptTouchEvent 方法的返回值来决定是否拦截;

在 DOWN 事件中将 touch 事件分发给子 View —> 这一过程如果有子 View 捕获消费了 touch 事件,会对 mFirstTouchTarget 进行赋值;

最后一步,DOWN、MOVE、UP 事件都会根据 mFirstTouchTarget 是否为 null,决定是自己处理 touch 事件,还是再次分发给子 View。

然后介绍了整个事件分发中的几个特殊的点。

DOWN 事件的特殊之处:事件的起点;决定后续事件由谁来消费处理;

mFirstTouchTarget 的作用:记录捕获消费 touch 事件的 View,是一个链表结构;

CANCEL 事件的触发场景:当父视图先不拦截,然后在 MOVE 事件中重新拦截,此时子 View 会接收到一个 CANCEL 事件。

android控件的touch事件_Android touch 事件分发时序相关推荐

  1. android 控件随手指移动_Android 实习生面试经历记录

    code小生,一个专注 Android 领域的技术平台 公众号回复 Android 加入我的安卓技术群 作者:念人远乡 链接:https://www.jianshu.com/p/3cd5ef51eed ...

  2. android控件向内弧度_android给View设置边框 填充颜色 弧度

    Android开发技巧--设置系统状态栏颜色 开门见山,先来三张效果图: 然后我们再来讲如何实现以及如何快速地实现. 如何实现 实现设置系统状态栏颜色需要至少在Android 4.4.2(API 19 ...

  3. android控件的touch事件_聊聊Android嵌套滑动

    聊聊Android嵌套滑动 最近工作中遇到了需求是使用 Bottom-Sheet 交互的弹窗,使用了 design 包里面的 CoordinatorLayout 和 BottomSheetBehavi ...

  4. Android 控件 RecyclerView 看这篇就够了

    [Android 控件 RecyclerView] 概述 RecyclerView是什么 从Android 5.0开始,谷歌公司推出了一个用于大量数据展示的新控件RecylerView,可以用来代替传 ...

  5. Android 控件 RecyclerView

    [Android 控件 RecyclerView] 概述 RecyclerView是什么 从Android 5.0开始,谷歌公司推出了一个用于大量数据展示的新控件RecylerView,可以用来代替传 ...

  6. Android 控件架构及View、ViewGroup的测量

    附录:示例代码地址 控件在Android开发的过程中是必不可少的,无论是我们在使用系统控件还是自定义的控件.下面我们将讲解一下Android的控件架构,以及如何实现自定义控件. 1.Android控件 ...

  7. Android控件系列之RadioButtonRadioGroup

    2019独角兽企业重金招聘Python工程师标准>>> 学习目的: 1.掌握在Android中如何建立RadioGroup和RadioButton 2.掌握RadioGroup的常用 ...

  8. android让一个控件跟上面控件对其,学个明白--Android控件架构

    Android控件架构 1.什么是View? View是Android中所有控件的基类.View是界面层的控件的一种抽象,它代表了一个控件.在Android中每个控件都会在界面中占得一块矩形的区域.在 ...

  9. 自定义控件android.r,Android控件架构与自定义控件

    前言 最近在开发的路上越走越远了,每天在看各位大神公众号更新内容是自定义View的时候,一些小的内容有点模具,决定回过头来温习一下过往的内容.此篇也是根据android群英传来总结的一篇文章. 1 A ...

最新文章

  1. PHP MySQL Update
  2. 码栈开发手册(四)---编码方式开发(日期相关函数)
  3. Boost:将帧传输到GPU以及如何应用用OpenCL编写的naive optical flow
  4. PHP 设计模式之代理模式
  5. mongodb 物理删除数据
  6. 程序型语言VS.编译型语言
  7. java 1.7 linux rpm,linux下用rpm 安装jdk 7的jdk-7u79-linux-x64.rpm
  8. GIT上fork的项目获取最新源代码
  9. dhtmlxTree 10分钟做一个树
  10. 开源项目的名称背后都有哪些故事?
  11. 一秒点击手机屏幕次数_抓住夏天的尾巴|与自动点击评论器邂逅一次
  12. Python之字符串格式化
  13. 实现音乐播放器歌词显示效果
  14. R语言绘图-解决坐标轴测度问题
  15. HTML5期末大作业:仿悦世界游戏网站设计——仿悦世界游戏官网(6页) HTML+CSS+JavaScript web网页设计实例作业
  16. 内网安全-隧道穿透漫游(二)
  17. Kalman实际应用总结
  18. 【Leetcode】天堂硅谷·数字经济算法编程大赛(虚拟)
  19. ResNet的改进系列:ResNext和Res2Net
  20. spark count统计元素个数

热门文章

  1. XML的概述,.Dom4解析和SAX解析
  2. CentOS7编译安装libc++和libc++abi
  3. spring中的context:include-filter和context:exclude-filter的区别
  4. 使用 logback + slf4j 进行日志记录
  5. 文本框获取焦点时,去掉边框
  6. Competitive
  7. Linux 系统安装 AutoFs 挂载服务
  8. 1 Oracle数据库环境搭建
  9. 【PL/SQL】用星号拼出金字塔
  10. 【MySQL】二进制分发安装